Swift: UIStackView перекрывает все представления вместо того, чтобы укладывать их вертикально

#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?