两个视图之间的共享祖先

时间:2014-03-26 15:57:13

标签: ios algorithm uiview uikit

在两个UIView个实例之间找到最低共同祖先的最有效方法是什么?

如果没有实施Lowest Common Ancestor,是否可以使用任何UIKit API来查找它?

NSViewancestorSharedWithView:,所以我怀疑这可能会在以后加入iOS。

我目前正在使用这种快速而肮脏的解决方案,如果给定的视图不是兄弟或直接祖先,则效率很低。

- (UIView*)lyt_ancestorSharedWithView:(UIView*)aView
{
    if (aView == nil) return nil;
    if (self == aView) return self;
    if (self == aView.superview) return self;
    UIView *ancestor = [self.superview lyt_ancestorSharedWithView:aView];
    if (ancestor) return ancestor;
    return [self lyt_ancestorSharedWithView:aView.superview];
}

(对于那些实施类似方法的人来说,Lyt项目的单元测试可能会有所帮助)

9 个答案:

答案 0 :(得分:6)

使用-isDescendantOfView:。

并不太难
- (UIView *)my_ancestorSharedWithView:(UIView *)aView
{
    UIView *testView = self;
    while (testView && ![aView isDescendantOfView:testView])
    {
        testView = [testView superview];
    }
    return testView;
}

答案 1 :(得分:4)

功能替代方案:

Swift(假设使用您最喜欢的OrderedSet

extension UIView {

    func nearestCommonSuperviewWith(other: UIView) -> UIView {
        return self.viewHierarchy().intersect(other.self.viewHierarchy()).first
    }

    private func viewHierarchy() -> OrderedSet<UIView> {
        return Set(UIView.hierarchyFor(self, accumulator: []))
    }

    static private func hierarchyFor(view: UIView?, accumulator: [UIView]) -> [UIView] {
        guard let view = view else {
            return accumulator
        }
        return UIView.hierarchyFor(view.superview, accumulator: accumulator + [view])
    }
}

Objective-C(在UIView上作为类别实现,假设存在firstObjectCommonWithArray方法)

+ (NSArray *)hierarchyForView:(UIView *)view accumulator:(NSArray *)accumulator
{
    if (!view) {
        return accumulator;
    }
    else {
        return [self.class hierarchyForView:view.superview accumulator:[accumulator arrayByAddingObject:view]];
    }
}

- (NSArray *)viewHierarchy
{
    return [self.class hierarchyForView:self accumulator:@[]];
}

- (UIView *)nearestCommonSuperviewWithOtherView:(UIView *)otherView
{
    return [[self viewHierarchy] firstObjectCommonWithArray:[otherView viewHierarchy]];
}

答案 2 :(得分:3)

这是一个更短的版本,作为UIView的一个类别:

- (UIView *)nr_commonSuperview:(UIView *)otherView
{
    NSMutableSet *views = [NSMutableSet set];
    UIView *view = self;

    do {
        if (view != nil) {
            if ([views member:view])
                return view;
            [views addObject:view];
            view = view.superview;
        }

        if (otherView != nil) {
            if ([views member:otherView])
                return otherView;
            [views addObject:otherView];
            otherView = otherView.superview;
        }
    } while (view || otherView);

    return nil;
}

答案 3 :(得分:1)

您的实现仅在一次迭代中检查两个视图级别。

这是我的:

+ (UIView *)commonSuperviewWith:(UIView *)view1 anotherView:(UIView *)view2 {
    NSParameterAssert(view1);
    NSParameterAssert(view2);
    if (view1 == view2) return view1.superview;

    // They are in diffrent window, so they wont have a common ancestor.
    if (view1.window != view2.window) return nil;

    // As we don’t know which view has a heigher level in view hierarchy,
    // We will add these view and their superview to an array.
    NSMutableArray *mergedViewHierarchy = [@[ view1, view2 ] mutableCopy];
    UIView *commonSuperview = nil;

    // Loop until all superviews are included in this array or find a view’s superview in this array.
    NSInteger checkIndex = 0;
    UIView *checkingView = nil;
    while (checkIndex < mergedViewHierarchy.count && !commonSuperview) {
        checkingView = mergedViewHierarchy[checkIndex++];

        UIView *superview = checkingView.superview;
        if ([mergedViewHierarchy containsObject:superview]) {
            commonSuperview = superview;
        }
        else if (checkingView.superview) {
            [mergedViewHierarchy addObject:superview];
        }
    }
    return commonSuperview;
}

答案 4 :(得分:1)

斯威夫特3:

extension UIView {
    func findCommonSuperWith(_ view:UIView) -> UIView? {

        var a:UIView? = self
        var b:UIView? = view
        var superSet = Set<UIView>()
        while a != nil || b != nil {

            if let aSuper = a {
                if !superSet.contains(aSuper) { superSet.insert(aSuper) }
                else { return aSuper }
            }
            if let bSuper = b {
                if !superSet.contains(bSuper) { superSet.insert(bSuper) }
                else { return bSuper }
            }
            a = a?.superview
            b = b?.superview
        }
        return nil

    }
} 

答案 5 :(得分:0)

Swift 2.0:

rebuild

答案 6 :(得分:0)

我相信,使用以下方法可以最大限度地缩短时间复杂度和空间复杂度

Step1 :计算每个的深度。让我们考虑v1v2是视图,d1d2是相应的深度

第2步:如果d1 == d2,请写一个for循环(index < d1 or d2),选择v1.superViewv2.superView并进行比较。返回,如果他们是平等的。

Step3 :如果d1 > d2,请取差(d1-d2),执行while循环,取v1.superView并减小d1值。 while循环应退出,如果(d1 == d2)。之后,重复Step1。

Step4 :如果d2 > d1,请取差(d2-d1),执行while循环,取v2.superView并递减d2值。 while循环应退出,如果(d2 == d1)。之后,重复Step1。

答案 7 :(得分:0)

Carl Lindberg's solution的Swift 5版本:

func nearestCommonSuperviewWith(other: UIView) -> UIView? {
    var nearestAncestor: UIView? = self

    while let testView = nearestAncestor, !other.isDescendant(of: testView) {
        nearestAncestor = testView.superview
    }

    return nearestAncestor
}

答案 8 :(得分:0)

我的数据库更长了一点,并且没有使用UIKit isDescendant函数。

方法1:使用在树中查找LCA的方法。时间复杂度:O(N),空间复杂度:(1)

func findCommonSuper(_ view1:inout UIView, _ view2:inout UIView) -> UIView? {
    var level1 = findLevel(view1)
    var level2 = findLevel(view2)
    if level1 > level2 {
        var dif = level1-level2
        while dif > 0 {
            view1 = view1.superview!
            dif -= 1
        }
    } else if level1 < level2 {
        var dif = level2-level1
        while dif > 0 {
            view2 = view2.superview!
            dif -= 1
        }
    }
    while view1 != view2  {
        if view1.superview == nil || view2.superview == nil {
            return nil
        }
        view1 = view1.superview!
        view2 = view2.superview!
    }
    if view1 == view2 {
        return view1
    }
    return nil
}

func findLevel(_ view:UIView) -> Int {
    var level = 0
    var view = view
    while view.superview != nil {
        view = view.superview!
        level += 1
    }
    return level
}

方法2:插入一个视图的祖先进行设置,然后迭代第二个视图的祖先。时间复杂度:O(N),空间复杂度:O(N)

func findCommonSuper2(_ view1:UIView, _ view2:UIView) -> UIView? {
    var set = Set<UIView>()
    var view = view1
    while true {
        set.insert(view)
        if view.superview != nil {
            view = view.superview!
        } else {
            break
        }
    }

    view = view2
    while true {
        if set.contains(view) {
            return view
        }
        if view.superview != nil {
            view = view.superview!
        } else {
            break
        }
    }
    return nil
}