【问题标题】:Shared ancestor between two views两个视图之间的共享祖先
【发布时间】:2014-05-05 04:22:16
【问题描述】:

在两个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 项目的单元测试可能会有所帮助)

【问题讨论】:

  • 您唯一的选择是将superview 链向上移动到顶部,然后找到两个分歧点。我不知道有任何 API(superview 除外)会有所帮助。
  • 我不了解 API,但我不会一直走到顶峰;而是以交替方式上升,为我找到的每个 UIView 保留一个计数器,并在每次访问它时将该计数器增加 1。第一个将计数器设置为 2 的 UIView 是最低的共同祖先。例如,您可以使用 hashmap 将视图映射到它们的计数器。这是我能想到的最简单的事情,实施起来也不难。

标签: ios algorithm uiview uikit


【解决方案1】:

我的有点长,并且没有使用 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
}

【讨论】:

    【解决方案2】:

    Swift 5 版本的Carl Lindberg's solution

    func nearestCommonSuperviewWith(other: UIView) -> UIView? {
        var nearestAncestor: UIView? = self
    
        while let testView = nearestAncestor, !other.isDescendant(of: testView) {
            nearestAncestor = testView.superview
        }
    
        return nearestAncestor
    }
    

    【讨论】:

      【解决方案3】:

      斯威夫特 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
      
          }
      } 
      

      【讨论】:

      • 抛开空间复杂度,我认为是 O(a+b) (其中 a 和 b 是每个视图到根视图的距离。)我认为这是最有效,时间明智。除非 LCA 是根视图,否则该算法不必从 a 和 b 都走整条路径到根,它会在此之前找到 LCA。不错!
      【解决方案4】:

      我相信,时间复杂度和空间复杂度将通过以下方法最小化

      Step1:计算每个的深度。假设v1v2是视图,d1d2是对应的深度

      Step2:如果是d1 == d2,写一个for循环(index &lt; d1 or d2),取v1.superViewv2.superView比较。如果它们相等,则返回。

      Step3:如果d1 &gt; d2,取差值(d1-d2),执行while循环,取v1.superView,递减d1值。如果(d1 == d2),while 循环应该退出。之后,重复Step1。

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

      【讨论】:

      • 使用哈希表怎么样?
      • @Cris: hashtable 实现相同的逻辑?
      • 更简单的逻辑。 NSSet 可能比 HashTable 更干净。对于一个视图,将所有的超级视图放在 NSSet 中。对于第二个视图,递归遍历每个视图及其父视图。如果一个视图存在于 NSSet 中,那么它就是一个共同的祖先。
      • 上述方法的时间和空间复杂度是多少?我希望,你对此一无所知!
      • 时间复杂度是线性的 (2N)。空间复杂度将是 N(指向视图的指针),其中 N 是视图数。
      【解决方案5】:

      使用 -isDescendantOfView: 并不难。

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

      【讨论】:

        【解决方案6】:

        功能性替代方案:

        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]];
        }
        

        【讨论】:

          【解决方案7】:

          斯威夫特 2.0:

              let view1: UIView!
              let view2: UIView!
              let sharedSuperView = view1.getSharedSuperview(withOtherView: view2)
          
          /**
          * A set of helpful methods to find shared superview for two given views
          *
          * @author Alexander Volkov
          * @version 1.0
          */
          extension UIView {
          
              /**
              Get nearest shared superview for given and otherView
          
              - parameter otherView: the other view
              */
              func getSharedSuperview(withOtherView otherView: UIView) {
                  (self.getViewHierarchy() as NSArray).firstObjectCommonWithArray(otherView.getViewHierarchy())
              }
          
              /**
              Get array of views in given view hierarchy
          
              - parameter view:        the view whose hierarchy need to get
              - parameter accumulator: the array to accumulate views in
          
              - returns: the list of views from given up to the top most view
              */
              class func getHierarchyForView(view: UIView?, var accumulator: [UIView]) -> [UIView] {
                  if let superview = view?.superview {
                      accumulator.append(view!)
                      return UIView.getHierarchyForView(superview, accumulator: accumulator)
                  }
                  return accumulator
              }
          
              /**
              Get array of views in the hierarchy of the current view
          
              - returns: the list of views from cuurent up to the top most view
              */
              func getViewHierarchy() -> [UIView] {
                  return UIView.getHierarchyForView(self, accumulator: [])
              }
          
          }
          

          【讨论】:

            【解决方案8】:

            这是一个较短的版本,作为 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;
            }
            

            【讨论】:

              【解决方案9】:

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

              这是我的:

              + (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;
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-03-14
                相关资源
                最近更新 更多