我如何在UITableViewCell的左侧创建重新排序控件?

20

我正在开发一个新闻阅读应用程序,希望用户可以选择显示/隐藏新闻类别(例如头条新闻、商业、科技、体育等),并像BBC Android新闻应用程序一样重新排序。

请参见下面的图片:

进入图像描述

我的问题是:

  • 如何在单元格左侧创建重新排序控件? (默认情况下,在编辑模式下,它位于单元格右侧)
  • 我有一个复选框自定义控件。如何将其放在单元格的右侧?
10个回答

38

答案 [如果您没有使用任何附件(详细披露等)]

(0) 我假设您已经设置好了表格,等等。

(1) 只有在您的表格单元格始终可拖动时,此解决方案才有效。将此添加到您的Table View Controller .m文件中的viewDidLoad中。

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self setEditing:YES];
}

(2) 为了让您可以重新排序单元格,请在tableView:cellForRowAtIndexPath:方法中添加cell.showsReorderControl = YES;

(3) 确保您有tableView:canMoveRowAtIndexPath:和tableView:moveRowAtIndexPath:toIndexPath:这两个方法。

- (BOOL)tableView:(UITableView *)tableview canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES; 
}

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}

(4) 因为您希望重新排序控件位于左侧,所以我们必须去掉通常在那里的删除圆圈,tableView:editingStyleForRowAtIndexPath: 方法可以实现此功能。

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewCellEditingStyleNone;
}

(5) 最后一步,魔法发生的地方 - 添加tableView:willDisplayCell:forRowAtIndexPath:方法,搜索单元格的子视图,缩小到私有的UITableViewCellReorderControl,最后覆盖它。

- (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    for(UIView* view in cell.subviews)
    {
        if([[[view class] description] isEqualToString:@"UITableViewCellReorderControl"])
        {
            // Creates a new subview the size of the entire cell
            UIView *movedReorderControl = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetMaxX(view.frame), CGRectGetMaxY(view.frame))];
            // Adds the reorder control view to our new subview
            [movedReorderControl addSubview:view];
            // Adds our new subview to the cell
            [cell addSubview:movedReorderControl];
            // CGStuff to move it to the left
            CGSize moveLeft = CGSizeMake(movedReorderControl.frame.size.width - view.frame.size.width, movedReorderControl.frame.size.height - view.frame.size.height);
            CGAffineTransform transform = CGAffineTransformIdentity;
            transform = CGAffineTransformTranslate(transform, -moveLeft.width, -moveLeft.height);
            // Performs the transform
            [movedReorderControl setTransform:transform];
        }
    }
}

我花了将近十个小时来尝试解决配件问题,但是我无法做到。我需要更多的经验和知识。简单地对披露按钮上的重新排序控件执行相同的操作并不起作用。所以我希望现在这个方法能够起作用;如果我在将来弄清楚了,我一定会更新这个内容。
嗨,Hoang Van Ha,这是我第一次回答,我只学习Objective-C一个月,所以我还是个新手。我一直在尝试使用我的应用程序做同样的事情,并且已经搜索了几个小时如何做到这一点。当涉及到这个问题时,苹果的文档并没有提供太多帮助。当人们简单地链接到文档时,我感到非常沮丧。我希望我的答案能帮到你。
感谢b2Cloud,因为我使用了他们从this article中的一些代码,并为我的答案进行了调整。

了不起的Luke先生...我需要左侧同时具备删除控件和重新排序控件,这可行吗? - Harish
3
你知道如何使整个单元格成为重新排序控件,这样你就不只需要抓住小图标了吗? - kyleplattner
这应该是被采纳的答案。我已经搜索了一段时间,这个方法非常有效。谢谢Luke。 - macandyp
1
我在这个解决方案中遇到了一个问题,函数willDisplayCell可能会多次为同一个单元格调用,例如当您将单元格滚动出窗口然后再滚回来时。 当发生这种情况并且您再次调用transform时,图标会消失到左侧。我解决这个问题的方法是,不使用transforms,而是将新视图的Y位置设置为固定位置:CGRect newFrame = movedReorderControl.frame; newFrame.origin.x = -220; movedReorderControl.frame = newFrame; - David Martinez
2
我要指出的是,@David提出的问题也会导致多个UIView添加到单元格中。每次滚动TableView时都会发生这种情况。这是由于创建了不再使用但仍然存在的视图而导致的内存泄漏。我已经对单元格进行了子类化,因此我添加了一个属性来跟踪创建的视图,并在willDisplayCell中重用它。 - Doug Gerecht
请注意,由于触及非公共类,苹果可能会拒绝您的应用程序。 - Bartłomiej Semańczyk

24

不要寻找 UITableViewCellReorderControl, 它是非公开的API,可能在未来更改,我建议在 UITableView 上使用 UILongPressGestureRecognizer 并实现自己的排序逻辑。

我在 HPReorderTableView 中解决了一般情况 (响应单元格上任意部分的长按)。这是一个可以替代UITableView的插件。它可以轻松地修改以响应单元格特定部分的触摸操作,例如通过实现reorderGestureRecognizer委托的gestureRecognizer:shouldReceiveTouch: 方法。


7
请注意,这个人之所以有23.7k的声望是有原因的。其他答案都很糟糕,并且将来会对你造成不利影响。同时请记住,苹果公司发布UI指南也有其重要原因,尽量遵循这些指南 - 用户期望右侧的控制。 - amergin
这是真的。UITableView的编辑行为是不可定制的,原因是它在历史上从未被定制过,现在也无法添加。相反,他们有更通用的UICollectionView,可以轻松地作为UITableView的替代品,并允许您自定义其行为。 - DrMickeyLauer

20

现在,对于 iOS 9 及更高版本,有一个更加简便的解决方案。

tableView.semanticContentAttribute = .forceRightToLeft

编辑:

正如Roland所提到的,您应该注意,如果您通过leadingtrailing约束而不是leftright来对齐单元格的子视图,则此设置将翻转一切。


1
如果您通过“leading”和“trailing”锚点而不是“left”和“right”来对齐单元格的子视图,则此设置将翻转所有内容。 - Roland
它还在移动控制和单元格内容之间添加了一些愚蠢的空格... - Starwave
@Starwave,你解决了这个问题吗? - SPS

4
卢克·杜伯特在iOS7上提出了很棒的答案,但是需要稍加改动: ```html

Luke Dubert, 对于 iOS 7 的建议需要稍作修改:

```
UIView *cellSubview = cell.subviews[0];
    for(UIView* view in cellSubview.subviews)
    {
        if([[[view class] description] isEqualToString:@"UITableViewCellReorderControl"])
        {

3

我在使用Swift 4.2对iOS 12进行了测试,而不是使用view.self.description == "UITableViewCellReorderControl",请使用view.self.description.contains("UITableViewCellReorderControl")

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    for view in cell.subviews {
        if view.self.description.contains("UITableViewCellReorderControl") {
            let movedReorderControl = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.maxX, height: view.frame.maxY))
            movedReorderControl.addSubview(view)
            cell.addSubview(movedReorderControl)
            let moveLeft = CGSize(width: movedReorderControl.frame.size.width - view.frame.size.width, height: movedReorderControl.frame.size.height - view.frame.size.height)
            var transform: CGAffineTransform = .identity
            transform = transform.translatedBy(x: -moveLeft.width, y: -moveLeft.height)
            movedReorderControl.transform = transform
        }
    }
}

我在寻找一种调整重新排序控件位置的方法。不,我并不想将它完全移动到左边,但我能够使用你的代码实现我所需的功能。谢谢! - Phontaine Judd

2

受Luke解决方案的启发,我找到了一种可靠且直接的方法来实现这一点:

@implementation CustomTableViewCell

-(void) layoutSubviews {
    [super layoutSubviews];

    // set the frames of the other subviews here if necessary, for example: 
    // self.textLabel.frame = CGRectMake(80, 0, self.textLabel.frame.size.width, self.textLabel.frame.size.height);

    for (UIView* view in self.subviews) {
        if ([[[view class] description] containsString:@"UITableViewCellReorderControl"]) {
            [view setFrame:CGRectMake(20, 0, view.frame.size.width, view.frame.size.height)];
        }
    }
}

@end

只需创建自定义的UITableViewCell,覆盖其子视图并修改每个视图的位置,在所有已知的情况下都可以很好地工作。

在willDisplayCellAtIndexPath下执行UITableViewReorderControl定制不会百分之百地起作用,第一个重新排序按钮偶尔不会正确定位。


1

这个解决方案在iOS7上对我无效,所以我创建了一个适用于两者的版本,希望它有用:

- (void)moveReorderControl:(UITableViewCell *)cell subviewCell:(UIView *)subviewCell
{
    if([[[subviewCell class] description] isEqualToString:@"UITableViewCellReorderControl"]) {
    static int TRANSLATION_REORDER_CONTROL_Y = -20;

        //Code to move the reorder control, you change change it for your code, this works for me
        UIView* resizedGripView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,    CGRectGetMaxX(subviewCell.frame), CGRectGetMaxY(subviewCell.frame))];
        [resizedGripView addSubview:subviewCell];
        [cell addSubview:resizedGripView];

        //  Original transform
        const CGAffineTransform transform = CGAffineTransformMakeTranslation(subviewCell.frame.size.width - cell.frame.size.width, TRANSLATION_REORDER_CONTROL_Y);
        //  Move custom view so the grip's top left aligns with the cell's top left

        [resizedGripView setTransform:transform];
   }
}

//This method is due to the move cells icons is on right by default, we need to move it.
- (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (tableView.tag == MEETING_BLOCK_TABLE_TAG) {
        for(UIView* subviewCell in cell.subviews)
        {
            if ([ROCFunctions isIOS7]) {
                if([[[subviewCell class] description] isEqualToString:@"UITableViewCellScrollView"]) {
                    for(UIView* subSubviewCell in subviewCell.subviews) {
                        [self moveReorderControl:cell subviewCell:subSubviewCell];
                    }
                }
            }
            else{
                [self moveReorderControl:cell subviewCell:subviewCell];
            }
        }
    }
}

0

感谢以上的每一个人。这是我的解决方案。已在iOS7和iOS9上进行了测试。

@implementation StoryPreviewCell
-(void)layoutSubviews{
    [super layoutSubviews];

    //subviews contains first level and second level views. 
    NSMutableArray *subviews = [NSMutableArray array];
    [subviews addObjectsFromArray:self.subviews];
    for (NSInteger i=0; i<self.subviews.count; i++) {
        UIView *firstLevelView = self.subviews[i];
        if(firstLevelView.subviews.count) [subviews addObjectsFromArray:firstLevelView.subviews];
    }

    for(UIView* view in subviews) {
        if([[[view class] description] isEqualToString:@"UITableViewCellReorderControl"]) {
            UIView *reorderControl = view;
            CGFloat leftMoveDistance = 100;//can customize
            CGAffineTransform transform = CGAffineTransformTranslate(CGAffineTransformIdentity, -leftMoveDistance, 0);
            reorderControl.transform = transform;
        }
    }
}
@end

0
对于使用Xamarin的人,这是基于@luke-dubert答案的解决方案,但要注意在重用时删除额外添加的视图:
在TableViewSource中:
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
{
    (cell as GraphBarLineCell)?.SetEditionMode(tableView.Editing);
}

在单元格中:
private UIView _reorderControlContainer;

public void SetEditionMode(bool editing)
    {
        _title.Frame = editing ? TitleFrameForEditing : TitleFrame;

        if (editing)
        {
            var reorderControl = Subviews.FirstOrDefault(x => x.Class.Name.Equals("UITableViewCellReorderControl"));

            if (reorderControl != null)
            {
                _reorderControlContainer?.RemoveFromSuperview();

                // Creates a new subview the size of the entire cell
                _reorderControlContainer = new UIView(new CGRect(0, 0, reorderControl.Frame.GetMaxX(), reorderControl.Frame.GetMaxY()));

                // Adds the reorder control view to our new subview
                _reorderControlContainer.AddSubview(reorderControl);

                // Adds our new subview to the cell
                AddSubview(_reorderControlContainer);

                // CGStuff to move it to the left
                var moveLeft = new CGSize(_reorderControlContainer.Frame.Size.Width - reorderControl.Frame.Size.Width,
                    _reorderControlContainer.Frame.Size.Height - reorderControl.Frame.Size.Height);

                var transform = CGAffineTransform.MakeIdentity();
                transform = CGAffineTransform.Translate(transform, -moveLeft.Width, -moveLeft.Height);
                _reorderControlContainer.Transform = transform;

                // Align the icon with the title
                var icon = reorderControl.Subviews[0];
                icon.Frame = new CGRect(10, 25, icon.Frame.Width, icon.Frame.Height);
            }
        }
    }

-1
我找到了一个更简单的解决方案,不需要任何绝对手动定位,甚至可以在编辑模式下使用动画过渡。应该适用于iOS6和7。
在UIView的某个地方定义此类别:
@interface UIView (ClassSearch)

- (instancetype)subviewOfClassMatching:(NSString*)partialName;

@end

@implementation UIView (ClassSearch)

-(instancetype)subviewOfClassMatching:(NSString *)partialName
{
    if ([[[self class] description] rangeOfString:partialName options:NSCaseInsensitiveSearch].location != NSNotFound) {
        return self;
    }

    for (UIView* v in self.subviews) {
        id match = [v subviewOfClassMatching:partialName];
        if (match) {
            return match;
        }
    }

    return nil;
}

@end

然后,在您的UITableViewCell子类中,像这样覆盖willTransitionToState:

-(void)willTransitionToState:(UITableViewCellStateMask)state
{
    [super willTransitionToState:state];

    if (state == UITableViewCellStateShowingEditControlMask) {
        UIView* reorderControl = [self subviewOfClassMatching:@"ReorderControl"];
        UIView* reorderContainer = [[UIView alloc] initWithFrame:self.bounds];
        CGAffineTransform t = CGAffineTransformMakeScale(-1, 1);
        reorderContainer.transform = t;
        [self addSubview:reorderContainer];
        [reorderContainer addSubview:reorderControl];
    }
}

请注意,单元格需要配置为仅显示重新排序控件,否则需要进行更多的工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接