Отправлять информацию источника данных только тогда, когда UIView был выложен? - PullRequest
0 голосов
/ 19 июня 2019

У меня проблемы с пониманием этого. Ранее я мог делать такой проект, но теперь подобный подход не работает для меня.

Вот проблема: У меня есть UIPageViewController, который имеет 4 storyViewControllers. Каждый storyViewControllers имеет пользовательский containerView. Я хочу добавить 'n' число UIViews на containerView. Я посылаю dataSource или 'n' с контроллера представления. Однако я не могу правильно выложить подпредставления в представлении контейнера.

По сути, я хочу знать, когда отправлять информацию об источнике данных из контроллера представления. Очевидно, я хотел бы отправить его после добавления пользовательского представления контейнера.

Я использую viewDidLayoutSubviews. Это заставляет это работать. Однако я не думаю, что это правильный путь. Теперь каждый раз, когда контроллер представления выкладывает подпредставления, будет вызываться мой делегат.

Я пытался сделать это в viewDidLoad(), но это тоже не работает.

Это работает. Но просто не кажется правильным. Als Мой storyViewController код

    override func viewDidLoad() {
    super.viewDidLoad()
    segmentContainerView = ATCStorySegmentsView()
    view.addSubview(segmentContainerView)
    configureSegmentContainerView()
    segmentContainerView.translatesAutoresizingMaskIntoConstraints = false

}

  override func viewDidLayoutSubviews() {
    segmentContainerView.delegate = self
     DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.segmentContainerView.startAnimation() // I am also animating these views that are laid on the containerView. Doing them here starts the animation randomly whenever I scroll through the UIPageController
    }
}

В containerView:

    var delegate: ATCSegmentDataSource? {
    didSet {
        addSegments()
    }
}

private func addSegments() {
    let numberOfSegment = delegate?.numberOfSegmentsToShow()
    guard let segmentQuantity = numberOfSegment else { return }
    layoutIfNeeded()
    setNeedsLayout()
    for i in 0..<segmentQuantity {
        let segment = Segment()
        addSubview(segment.bottomSegment)
        addSubview(segment.topSegment)
        configureSegmentFrame(index: i, segmentView: segment)
        segmentsArray.append(segment)
    }


}

private func configureSegmentFrame(index: Int, segmentView: Segment) {
    let numberOfSegment = delegate?.numberOfSegmentsToShow()
    guard let segmentQuantity = numberOfSegment else { return }

    let widthOfSegment : CGFloat = (self.frame.width - (padding * CGFloat(segmentQuantity - 1))) / CGFloat(segmentQuantity)

    let i = CGFloat(index)

    let segmentFrame = CGRect(x: i * (widthOfSegment + padding), y: 0, width: widthOfSegment, height: self.frame.height)
    segmentView.bottomSegment.frame = segmentFrame
    segmentView.topSegment.frame = segmentFrame
    segmentView.topSegment.frame.size.width = 0

}

Это работает так, как должно. Но когда я прокручиваю UIPageViewController, анимация не всегда начинается с самого начала. Так как он опирается на выкладку подпредставлений. Иногда, если я медленно прокручиваю страницу контроллера, тогда подпредставления выкладываются снова, и анимация начинается с начала. В других случаях, когда представления уже загружены, анимация начинается с того места, где я не учел.

Вопрос Я хочу знать, каков наилучший способ отправки источника данных из контроллера представления в containerView? Этот источник данных - это то, что необходимо для генерации количества представлений, добавляемых в containerView.

Это результат, который я получаю, если отправляю источник данных из viewDidLayoutSubviews. Ранее сегодня я задал еще один вопрос, в котором перечислены другие методы, которые я использовал для отправки источника данных. Взгляните также на это: Невозможно выложить подпредставления в пользовательском UIView в Swift

enter image description here

1 Ответ

0 голосов
/ 19 июня 2019

Это очень простой пример ...

Имеет:

  • Segment: UIView подкласс, который содержит "bottomSegment" и "topSegment"
  • a ATCStorySegmentsView: UIView подкласс с UIStackView для размещения желаемого количества сегментов
  • a StoryViewController: UIViewController подкласс, который добавляет ATCStorySegmentsView наверху своего представления, а затем сообщает этому представлению анимировать первый сегмент на viewDidAppear()

class Segment: UIView {

    let topSegment: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .white
        return v
    }()

    let bottomSegment: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .gray
        return v
    }()

    var startConstraint: NSLayoutConstraint = NSLayoutConstraint()
    var endConstraint: NSLayoutConstraint = NSLayoutConstraint()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func commonInit() -> Void {
        addSubview(bottomSegment)
        addSubview(topSegment)

        // start constraint has width of Zero
        startConstraint = topSegment.widthAnchor.constraint(equalTo: bottomSegment.widthAnchor, multiplier: 0.0)

        // end constraint has width of bottomSegment
        endConstraint = topSegment.widthAnchor.constraint(equalTo: bottomSegment.widthAnchor, multiplier: 1.0)

        NSLayoutConstraint.activate([

            // bottomSegment constrained to all 4 sides
            bottomSegment.topAnchor.constraint(equalTo: topAnchor),
            bottomSegment.bottomAnchor.constraint(equalTo: bottomAnchor),
            bottomSegment.leadingAnchor.constraint(equalTo: leadingAnchor),
            bottomSegment.trailingAnchor.constraint(equalTo: trailingAnchor),

            // topSegment constrained top, bottom and leading
            topSegment.topAnchor.constraint(equalTo: topAnchor),
            topSegment.bottomAnchor.constraint(equalTo: bottomAnchor),
            topSegment.leadingAnchor.constraint(equalTo: leadingAnchor),

            // activate topSegemnt width constraint
            startConstraint,

            ])
    }

    func showTopSegment() -> Void {
        // deactivate startConstraint
        startConstraint.isActive = false
        // activate endConstraint
        endConstraint.isActive = true
    }

}

protocol ATCSegmentDataSource {
    func numberOfSegmentsToShow() -> Int
}

class ATCStorySegmentsView: UIView {

    let theStackView: UIStackView = {
        let v = UIStackView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.axis = .horizontal
        v.alignment = .fill
        v.distribution = .fillEqually
        v.spacing = 4
        return v
    }()

    var segmentsArray: [Segment] = [Segment]()

    var delegate: ATCSegmentDataSource? {
        didSet {
            addSegments()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func commonInit() -> Void {
        addSubview(theStackView)

        NSLayoutConstraint.activate([

            // constrain stack view to all 4 sides
            theStackView.topAnchor.constraint(equalTo: topAnchor),
            theStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
            theStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
            theStackView.trailingAnchor.constraint(equalTo: trailingAnchor),

            ])
    }

    private func addSegments() {
        let numberOfSegment = delegate?.numberOfSegmentsToShow()
        guard let segmentQuantity = numberOfSegment else { return }

        // add desired number of Segment subviews to teh stack view
        for _ in 0..<segmentQuantity {
            let seg = Segment()
            seg.translatesAutoresizingMaskIntoConstraints = false
            theStackView.addArrangedSubview(seg)
            segmentsArray.append(seg)
        }
    }

    func startAnimation() -> Void {

        // this will animate changing the topSegment's width
        self.segmentsArray.first?.showTopSegment()
        UIView.animate(withDuration: 1.5, animations: {
            self.layoutIfNeeded()
        })

    }


}

class StoryViewController: UIViewController, ATCSegmentDataSource {

    let segmentContainerView: ATCStorySegmentsView = ATCStorySegmentsView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .red

        view.addSubview(segmentContainerView)

        segmentContainerView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([

            // constrain segmentContainerView to top (safe-area) + 20-pts "padding",
            segmentContainerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),

            // leading and trailing with "padding" of 20-pts
            segmentContainerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
            segmentContainerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),

            // constrain height to 10-pts
            segmentContainerView.heightAnchor.constraint(equalToConstant: 10.0),

            ])

        // set the delegate
        segmentContainerView.delegate = self

    }

    override func viewDidAppear(_ animated: Bool) {
        segmentContainerView.startAnimation()
    }

    func numberOfSegmentsToShow() -> Int {
        return 3
    }

}

Результат (белая полоса анимируется на серой полосе при появлении представления):

enter image description here

enter image description here

С 5 сегментами:

enter image description here

Обратите внимание, что, поскольку автоматическое размещение предназначено для этого, оно также обрабатывает изменения размера, например, когда устройство поворачивается:

enter image description here

...