问题
I am having trouble understanding this. I have been able to do such a project earlier on but now a similar approach isn't working for me.
Here is the issue:
I have a UIPageViewController that has 4 storyViewControllers
.
Each storyViewControllers
has a custom containerView
. I want to add 'n' number of UIViews
on the containerView
. I am sending the dataSource
or 'n' from the view controller. However, I am not able to correctly lay out the subviews on the container view.
Essentially I want to know when to send the datasource info from the view controller. Obviously I would like to send it once the custom container view has been added.
I am using viewDidLayoutSubviews
. This makes it work. However, I don't think it's the correct way. Now every time the view controller lays out subviews my delegate will be called.
I have tried doing it in viewDidLoad()
but that doesn't work either.
This works. But just doesn't seem right. Als
My storyViewController
code
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
}
}
In the 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
}
This works the way it should. But when I scroll the UIPageViewController, the animations don't always start from the beginning. Since it relies on laying out subviews. Sometimes if I slowly scroll through the page controller then the subviews are laid out again and the animation starts from start. Other times when the views are already loaded, the animations starts from where I left out.
Question I want to know what is the best way to send datasource from view controller to the containerView? That datasource is what will be needed to generate the amount of views to be added on the containerView.
This is the result I get, if I send the datasource from viewDidLayoutSubviews
. I have asked another question earlier today that lists other methods I used to send datasource. Take a look at that as well: Not able to lay out Subviews on a custom UIView in Swift
回答1:
This is a very basic example...
It has:
- a
Segment: UIView
sub-class that holds a "bottomSegment" and a "topSegment" - a
ATCStorySegmentsView: UIView
sub-class with aUIStackView
to layout the desired number of segments - a
StoryViewController: UIViewController
sub-class that addsATCStorySegmentsView
at the top of its view, and then tells that view to animate the first segment onviewDidAppear()
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
}
}
Result (the white bar animates across the gray bar when the view appears):
With 5 segments:
Note that, as auto-layout is designed to do, it also handles size-changes, such as when the device is rotated:
来源:https://stackoverflow.com/questions/56671161/sending-datasource-information-only-when-uiview-has-been-laid-out