Отправка информации об источнике данных только тогда, когда UIView выложен?

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

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

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

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

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

Это работает. Но просто не кажется правильным. Альс Мой код 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. Ранее сегодня я задал еще один вопрос, в котором перечислены другие методы, которые я использовал для отправки источника данных. Взгляните и на это: Невозможно разместить Subviews в пользовательском UIView в Swift.

Отправка информации об источнике данных только тогда, когда UIView выложен?

Используйте автоматическую компоновку вместо того, чтобы пытаться рассчитать размеры. Как я прокомментировал ваш другой вопрос, UIStackView, вероятно, будет делать именно то, что вы хотите, без необходимость любых вычислений кадров.

DonMag 19.06.2019 17:44

@DonMag Мне все еще нужна информация об источнике данных для этого. Например, сколько UIViews мне нужно создать, а затем добавить их в stackView. Мой главный вопрос здесь заключается в том, в какой момент я должен отправить информацию об источнике данных в дочерний uiview из контроллера. Все, кроме 'viewDidLayoutSubviews()'

Osama Naeem 19.06.2019 17:53

По сути, при использовании ограничений автоматического макета вы можете добавить свои подпредставления и настроить ограничения в viewDidLoad(). После этого автоматический макет обрабатывает фактический размер для вас — не имеет значения, является ли устройство маленьким или большим, портретным или альбомным. Вы не показали, что вы пытаетесь сделать с помощью "анимации", но похоже, что вы хотите запустить любую анимацию в viewDidAppear().

DonMag 19.06.2019 19:35

@DonMag Полностью понял. В основном причина, по которой я использую фреймы вместо UIStackView и автоматического макета, заключается в том, что я вычисляю фреймы по ширине фрейма и количеству элементов в массиве. Также я использую структуру, называемую сегментами, которая содержит два UIViews/нижний и верхний вид. Нижнему и верхнему представлениям присваиваются одни и те же кадры, но ширина верхнего представления равна 0. Затем я анимирую верхний вид от 0 до значения bottomView.width. Просто казалось, что кадры будут работать лучше в том, как я анимирую и делаю это. Как вы думаете?

Osama Naeem 19.06.2019 20:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
39
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Она имеет:

  • подкласс Segment: UIView, который содержит «нижний сегмент» и «верхний сегмент»
  • подкласс ATCStorySegmentsView: UIView с UIStackView для размещения желаемого количества сегментов
  • подкласс 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
    }

}

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

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

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

Большое спасибо. Это прояснило многие концепции. Огромное спасибо!

Osama Naeem 20.06.2019 12:05

Другие вопросы по теме

Похожие вопросы

Как я могу улучшить интерфейс параметров настройки моего приложения с помощью списка?
Как запретить пользователю регистрироваться, если электронная почта не подтверждена
Чем получение изображения пользовательского интерфейса из firebase отличается от извлечения метки пользовательского интерфейса в текстовом формате и каковы потенциальные проблемы, возникающие при извлечении изображения?
Изменение размера ScrollView на основе размера TableView
Декодирование JSON в кодируемые объекты — с условием
Xamarin iOS, как проверить, отключены ли службы определения местоположения или местоположение устройства на уровне приложения
Что произойдет, если я изменю идентификатор Apple, связанный с учетной записью Apple Developer?
UITableViewRowAction устарел в iOS 13.0
Как я могу прокрутитьToTop в SwiftUI
Проблема при обновлении значения в таблице sqlite с запросом на обновление