Эффективный рендеринг и зеркалирование вне экрана UIView - PullRequest
0 голосов
/ 20 января 2019

У меня есть «внеэкранная» иерархия UIView, которую я хочу визуализировать в разных местах моего экрана.Кроме того, должна быть возможность отображать только части этой иерархии представлений, и они должны отражать все изменения, внесенные в эту иерархию.

Сложности:

  • Метод UIView drawHierarchy(in:afterScreenUpdates:)всегда вызывает draw(_ rect:) и поэтому очень неэффективно для больших иерархий, если вы хотите включить все изменения в иерархию представления.Вам придется перерисовывать его при каждом обновлении экрана или наблюдать за всеми изменяющимися свойствами всех представлений. Рисование документации по иерархии видов
  • Метод UIView snapshotView(afterScreenUpdates:) также мало чем помогает, поскольку я не нашел способа получить правильный чертеж иерархии видов, если эта иерархия "отключена".экран». Документация по представлению моментального снимка

"Вне экрана": корневое представление этой иерархии представлений не является частью пользовательского интерфейса приложения.У него нет суперпредставления.

Ниже вы можете увидеть визуальное представление моей идеи:

example

Ответы [ 2 ]

0 голосов
/ 25 февраля 2019

Я бы продублировал контент, который хотел бы отобразить, и обрезал его, как я хочу.

Допустим, у меня есть 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) }
    }
} 

Может ли этот сценарий работать в вашем случае?

0 голосов
/ 25 февраля 2019

Вот как бы я это сделал.Во-первых, я бы продублировал вид, который вы пытаетесь продублировать.Я написал небольшое расширение для этого:

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 должна работать хорошо, но вы можете изменить ее, если необходимо, чтобы скопировать еще больше вещей из одногосмотреть на другого.

Удачи, дайте мне знать, как это происходит:)

...