У меня проблемы с пониманием этого. Я мог сделать такой проект раньше, но теперь подобный подход не работает для меня.
Вот проблема:
У меня есть 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.
@DonMag Мне все еще нужна информация об источнике данных для этого. Например, сколько UIViews мне нужно создать, а затем добавить их в stackView. Мой главный вопрос здесь заключается в том, в какой момент я должен отправить информацию об источнике данных в дочерний uiview из контроллера. Все, кроме 'viewDidLayoutSubviews()'
По сути, при использовании ограничений автоматического макета вы можете добавить свои подпредставления и настроить ограничения в viewDidLoad()
. После этого автоматический макет обрабатывает фактический размер для вас — не имеет значения, является ли устройство маленьким или большим, портретным или альбомным. Вы не показали, что вы пытаетесь сделать с помощью "анимации", но похоже, что вы хотите запустить любую анимацию в viewDidAppear()
.
@DonMag Полностью понял. В основном причина, по которой я использую фреймы вместо UIStackView и автоматического макета, заключается в том, что я вычисляю фреймы по ширине фрейма и количеству элементов в массиве. Также я использую структуру, называемую сегментами, которая содержит два UIViews/нижний и верхний вид. Нижнему и верхнему представлениям присваиваются одни и те же кадры, но ширина верхнего представления равна 0. Затем я анимирую верхний вид от 0 до значения bottomView.width. Просто казалось, что кадры будут работать лучше в том, как я анимирую и делаю это. Как вы думаете?
Это очень простой пример...
Она имеет:
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 сегментами:
Обратите внимание, что автоматический макет также обрабатывает изменения размера, например, когда устройство поворачивается:
Большое спасибо. Это прояснило многие концепции. Огромное спасибо!
Используйте автоматическую компоновку вместо того, чтобы пытаться рассчитать размеры. Как я прокомментировал ваш другой вопрос,
UIStackView
, вероятно, будет делать именно то, что вы хотите, без необходимость любых вычислений кадров.