#ios #swift #uiviewcontroller #uiscrollview #uistackview
#iOS #swift #uiviewcontroller #uiscrollview #uistackview
Вопрос:
Я пытаюсь создать страницу статистики для своего приложения, которая будет содержать различные диаграммы, которые создаются динамически в зависимости от типа данных, имеющихся у пользователя. Для этого я укладываю несколько ViewControllers в соответствии с этим руководством: https://swiftwithmajid.com/2019/02/27/building-complex-screens-with-child-viewcontrollers /
Я столкнулся с проблемой, когда ViewController View добавляется в основной StackView как arrangedSubView, но вместо того, чтобы укладывать представления по вертикали и позволять мне прокручивать их все, он просто накладывает их друг на друга в z-направлении.
Вот код StackViewController:
class StackViewController: UIViewController {
private let scrollView = UIScrollView()
private let stackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(stackView)
setupConstraints()
stackView.axis = .vertical
}
private func setupConstraints() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
stackView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor)
])
}
}
extension StackViewController {
func add(_ child: UIViewController) {
addChild(child)
stackView.addArrangedSubview(child.view)
print(child.view!)
child.didMove(toParent: self)
}
func remove(_ child: UIViewController) {
guard child.parent != nil else {
return
}
child.willMove(toParent: nil)
stackView.removeArrangedSubview(child.view)
child.view.removeFromSuperview()
child.removeFromParent()
}
}
Здесь я создаю каждый контроллер представления и добавляю его в StackViewController.
На данный момент я запускаю цикл и добавляю копии одного контроллера представления снова и снова:
class PrototypeViewController: StackViewController {
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0...10 {
setupUI()
}
}
private func setupUI() {
let storyboard = UIStoryboard(name: "ConsistencyGraph", bundle: .main)
let consistencyGraphVC = storyboard.instantiateViewController(identifier: "ConsistencyGraphVC") as! ConsistencyGraphVC
add(consistencyGraphVC)
consistencyGraphVC.setupUI(name: "sessionName", consistencyPercentage: 30, ballsHit: 10)
}
}
Вот код контроллера представления:
class ConsistencyGraphVC: UIViewController {
@IBOutlet weak var mainView: UIView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var ballsHitLabel: UILabel!
@IBOutlet weak var pieChartView: PieChartView!
override func viewDidLoad() {
super.viewDidLoad()
}
open func setupUI(name: String, consistencyPercentage: Double, ballsHit: Int) {
displayName(name:name)
drawPieChart(consistencyPercentage: consistencyPercentage)
displayBallsHit(ballsHit: ballsHit)
}
private func displayName(name: String) {
let prefix = "Consistency: "
let title = prefix name
titleLabel.text = title
}
private func displayBallsHit(ballsHit: Int) {
ballsHitLabel.text = String(ballsHit)
}
private func drawPieChart(consistencyPercentage: Double) {
let maxPercent:Double = 100
let remainingPercent = maxPercent - consistencyPercentage
let dataEntry = [PieChartDataEntry(value: consistencyPercentage, data: String(consistencyPercentage)), PieChartDataEntry(value: remainingPercent, data: nil)]
let dataSet = PieChartDataSet(entries: dataEntry)
let chartData = PieChartData(dataSet: dataSet)
let color1 = randomColor()
let color2 = randomColor()
dataSet.colors = [color1, color2]
pieChartView.data = chartData
}
private func randomColor() -> UIColor {
let red = Double(arc4random_uniform(256))
let green = Double(arc4random_uniform(256))
let blue = Double(arc4random_uniform(256))
let color = UIColor(red: CGFloat(red/255), green: CGFloat(green/255), blue: CGFloat(blue/255), alpha: 1)
return color
}
}
Сначала я подумал, что это связано с тем, что размеры контроллера представления могут быть неоднозначными. Затем я жестко запрограммировал ширину и высоту каждого контроллера представления, но все равно не повезло.
Любая помощь очень ценится! Я в недоумении относительно того, как это вообще возможно.
Заранее благодарю вас!
Ответ №1:
Я наконец нашел решение!
Мне нужно было добавить:
child.view.heightAnchor.constraint(equalToConstant: child.view.frame.size.height).isActive = true
к StackViewController здесь:
func add(_ child: UIViewController) {
addChild(child)
child.view.heightAnchor.constraint(equalToConstant: child.view.frame.size.height).isActive = true
stackView.addArrangedSubview(child.view)
child.didMove(toParent: self)
}
Я не уверен, почему это так. Я уже жестко запрограммировал высоту представления, но StackView также хотел, чтобы я ограничил его перед добавлением представления.
Я надеюсь, что это поможет кому-то в будущем! Я целую вечность бился головой о стену…
Комментарии:
1. Чтобы объяснить, почему… Когда вы загружаете VC с
.instantiateViewController()
помощью, «корневое» представление этого контроллера имеет.translatesAutoresizingMaskIntoConstraints = true
. Однако, когда вы делаете это:stackView.addArrangedSubview(child.view)
, автоматически устанавливается представление стекаchild.view.translatesAutoresizingMaskIntoConstraints = false
. Если ваши дочерние представления квадратные (соотношение 1: 1), вы можете изменить эту строку на:child.view.heightAnchor.constraint(equalTo: child.view.widthAnchor).isActive = true
2. Большое вам спасибо за объяснение @DonMag! Всегда лучше знать, почему что-то работает, чтобы я мог использовать это в будущем.
3. @DonMag хорошее объяснение. Я всегда думал, что stackviews самостоятельно позаботился обо всех размерах. Я только что столкнулся с такой же проблемой, и ответ Яна помог. Но я не знал, что представления стека устанавливают для своих подвидов значение ‘translatesAutoresizingMaskIntoConstraints = false’. полезно знать на будущее. Спасибо
Ответ №2:
Вы забыли некоторые необходимые ограничения для вашего StackView внутри ScrollView. У вас есть только то trailing, top, and bottom
, чего недостаточно.
- Правильный:
stackView.leadingAnchor
stackView.trailingAnchor
stackView.topAnchor
stackView.bottomAnchor
stackView.widthAnchor
Комментарии:
1. Вы говорите о StackViewController? Если это так, у меня есть «StackView.leadingAnchor.constraint(equalTo: ScrollView.contentLayoutGuide. leadingAnchor), StackView.trailingAnchor.constraint(равный: ScrollView.contentLayoutGuide.trailingAnchor), StackView.topAnchor.constraint(равный: ScrollView.contentLayoutGuide.topAnchor), StackView.bottomAnchor.constraint(равный: ScrollView.contentLayoutGuide.bottomAnchor), StackView.bottomAnchor.constraint(равный: ScrollView.contentLayoutGuide.bottomAnchor), StackView. widthAnchor.constraint(равнозначно: ScrollView.frameLayoutGuide. widthAnchor) »
2. Есть ли другое место, где я должен изменить ограничения?
3. Но в коде, который вы показали, не существует этих ограничений? Вы добавляете только 3 ограничения для вашего StackView
4. Все ограничения, включая те, которые вы упомянули, находятся в функции setupConstraints() в классе StackViewController выше. Есть ли где-нибудь еще, где я должен настраивать ограничения StackView?