代码之家  ›  专栏  ›  技术社区  ›  Qbyte

高效的屏幕外UIView渲染和镜像

  •  3
  • Qbyte  · 技术社区  · 7 年前

    困难在于:

    • UIView 方法 drawHierarchy(in:afterScreenUpdates:) 总是打电话 draw(_ rect:) 因此,如果要将所有更改合并到视图层次结构中,对于大型层次结构来说效率非常低。您必须在每次屏幕更新时重新绘制它,或者观察所有视图的所有更改属性。 Draw view hierarchy documentation
    • 这个 UIView snapshotView(afterScreenUpdates:) 也没有太大帮助,因为我还没有找到一种方法来获得一个正确的视图层次结构图纸,如果这个层次结构是“屏幕外”。 Snapshot view documentation

    “屏幕外”:此视图层次结构的根视图不是应用程序UI的一部分。它没有superview。

    下面你可以看到我的想法的视觉表现:

    example

    0 回复  |  直到 7 年前
        1
  •  0
  •   CentrumGuy    7 年前

    下面是我将如何着手做这件事。首先,我将复制您试图复制的视图。我为此写了一个小扩展:

    extension UIView {
        func duplicate<T: UIView>() -> T {
            return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
        }
    
        func copyProperties(fromView: UIView, recursive: Bool = true) {
            contentMode = fromView.contentMode
            tag = fromView.tag
    
            backgroundColor = fromView.backgroundColor
            tintColor = fromView.tintColor
    
            layer.cornerRadius = fromView.layer.cornerRadius
            layer.maskedCorners = fromView.layer.maskedCorners
    
            layer.borderColor = fromView.layer.borderColor
            layer.borderWidth = fromView.layer.borderWidth
    
            layer.shadowOpacity = fromView.layer.shadowOpacity
            layer.shadowRadius = fromView.layer.shadowRadius
            layer.shadowPath = fromView.layer.shadowPath
            layer.shadowColor = fromView.layer.shadowColor
            layer.shadowOffset = fromView.layer.shadowOffset
    
            clipsToBounds = fromView.clipsToBounds
            layer.masksToBounds = fromView.layer.masksToBounds
            mask = fromView.mask
            layer.mask = fromView.layer.mask
    
            alpha = fromView.alpha
            isHidden = fromView.isHidden
            if let gradientLayer = layer as? CAGradientLayer, let fromGradientLayer = fromView.layer as? CAGradientLayer {
                gradientLayer.colors = fromGradientLayer.colors
                gradientLayer.startPoint = fromGradientLayer.startPoint
                gradientLayer.endPoint = fromGradientLayer.endPoint
                gradientLayer.locations = fromGradientLayer.locations
                gradientLayer.type = fromGradientLayer.type
            }
    
            if let imgView = self as? UIImageView, let fromImgView = fromView as? UIImageView {
                imgView.tintColor = .clear
                imgView.image = fromImgView.image?.withRenderingMode(fromImgView.image?.renderingMode ?? .automatic)
                imgView.tintColor = fromImgView.tintColor
            }
    
            if let btn = self as? UIButton, let fromBtn = fromView as? UIButton {
                btn.setImage(fromBtn.image(for: fromBtn.state), for: fromBtn.state)
            }
    
            if let textField = self as? UITextField, let fromTextField = fromView as? UITextField {
                if let leftView = fromTextField.leftView {
                    textField.leftView = leftView.duplicate()
                    textField.leftView?.copyProperties(fromView: leftView)
                }
    
                if let rightView = fromTextField.rightView {
                    textField.rightView = rightView.duplicate()
                    textField.rightView?.copyProperties(fromView: rightView)
                }
    
                textField.attributedText = fromTextField.attributedText
                textField.attributedPlaceholder = fromTextField.attributedPlaceholder
            }
    
            if let lbl = self as? UILabel, let fromLbl = fromView as? UILabel {
                lbl.attributedText = fromLbl.attributedText
                lbl.textAlignment = fromLbl.textAlignment
                lbl.font = fromLbl.font
                lbl.bounds = fromLbl.bounds
            }
    
           if recursive {
                for (i, view) in subviews.enumerated() {
                    if i >= fromView.subviews.count {
                        break
                    }
    
                    view.copyProperties(fromView: fromView.subviews[i])
                }
            }
        }
    }
    

    let duplicateView = originalView.duplicate()
    duplicateView.copyProperties(fromView: originalView)
    parentView.addSubview(duplicateView)
    

    然后,我将屏蔽重复视图,以仅获取所需的特定部分

    let mask = UIView(frame: CGRect(x: 0, y: 0, width: yourNewWidth, height: yourNewHeight))
    mask.backgroundColor = .black
    duplicateView.mask = mask
    

    最后,我将使用CGAffineTransform将其缩放到您想要的任何大小

    duplicateView.transform = CGAffineTransform(scaleX: xScale, y: yScale)
    

    copyProperties函数应该可以很好地工作,但是如果需要将更多内容从一个视图复制到另一个视图,可以对其进行更改。

    祝你好运,让我知道事情的进展:)

        2
  •  0
  •   GaétanZ    7 年前

    我会复制我希望显示的内容,并根据需要进行裁剪。

    假设我有一个 ContentViewController ContentViewModel . 比如:

    struct ContentViewModel {
        let actionTitle: String?
        let contentMessage: String?
        // ...
    }
    
    class ContentViewController: UIViewController {
        func display(_ viewModel: ContentViewModel) { /* ... */ }
    }
    

    用一个 ClippingView (或是简单的 UIScrollView

    class ClippingView: UIView {
    
        var contentOffset: CGPoint = .zero // a way to specify the part of the view you wish to display
        var contentFrame: CGRect = .zero // the actual size of the clipped view
    
        var clippedView: UIView?
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            clipsToBounds = true
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
            clippedView?.frame = contentFrame
            clippedView?.frame.origin = contentOffset
        }
    }
    

    还有一个视图控制器容器,我将裁剪我的内容的每个实例,并在每次发生事件时更新所有实例:

    class ContainerViewController: UIViewController {
    
        let contentViewControllers: [ContentViewController] = // 3 in your case
    
        override func viewDidLoad() {
            super.viewDidLoad()
            contentViewControllers.forEach { viewController in
                 addChil(viewController)
                 let clippingView = ClippingView()
                 clippingView.clippedView = viewController.view
                 clippingView.contentOffset = // ...
                 viewController.didMove(to: self)
            }
        }
    
        func somethingChange() {
            let newViewModel = ContentViewModel(...)
            contentViewControllers.forEach { $0.display(newViewModel) }
        }
    } 
    

    在你的情况下,这个场景能起作用吗?