使用带有 StackView 的 ScrollView 作为子视图和 UIViews 作为 StackView 的 children

Using ScrollView with StackView as subview and UIViews as children of StackView

我正在努力让我的滚动视图以编程方式工作。

我有一个视图控制器,它实例化了一个具有以下约束的 UIScrollView

class HomeTabBarController: ViewController {
  let homePageView = HomePageView()

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(true)
    homePageView.setupHomePage()
    view.addSubview(homePageView)
    
    ///constraints
    homePageView.stackView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
    homePageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    homePageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true;
    homePageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true;
    homePageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -83).isActive = true;
  }
}

HomePageView (UIScrollView) 有一个 UIStackView 并实例化了另外 3 个 UIView,它们是 UIStackView 的 children。代码及约束如下

class HomePageView: UIScrollView {
var homePageCarrousel: HomePageCarrousel?
var homePageSocial: HomePageSocialUp?
var homePageAboutUs: HomePageAboutUs?
var stackView = UIStackView()

func setupHomePage() {
    translatesAutoresizingMaskIntoConstraints = false
    homePageCarrousel = HomePageCarrousel()
    homePageSocial = HomePageSocialUp()
    homePageAboutUs = HomePageAboutUs()

    guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }

    homePageSocial.setupSocialHeader()
    stackView.addArrangedSubview(homePageSocial)

    homePageCarrousel.setupCarrousel()
    stackView.addArrangedSubview(homePageCarrousel)

    homePageAboutUs.setup()
    stackView.addArrangedSubview(homePageAboutUs)

    addSubview(stackView)
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .vertical
    stackView.spacing = 10

    setLayout()
}

func setLayout(){
    guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }

    ///header
    homePageSocial.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
    homePageSocial.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
    homePageSocial.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true

    ///carrousel
    homePageCarrousel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 20).isActive = true
    homePageCarrousel.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 20).isActive = true
    homePageCarrousel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -20).isActive = true

    ///about us
    homePageAboutUs.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
    homePageAboutUs.topAnchor.constraint(equalTo: homePageCarrousel.bottomAnchor, constant: 10).isActive = true
    homePageAboutUs.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
    homePageAboutUs.bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true

    ///stackview
    self.stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true;
    self.stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true;
    self.stackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true;
    self.stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true;
}

func dispose() {
    homePageSocial = nil
    homePageCarrousel = nil
    homePageAboutUs = nil
    subviews.forEach{[=12=].removeFromSuperview()}
}
}

每个 children UIView(homePageSocial、homePageCarrousel 和 homePageAboutUs)都有约束也有约束:

HomePageCarrousel

加载 UIImageView 并在添加为子视图后将约束设置为

heightAnchor.constraint(equalToConstant: imageView.frame.height).isActive = true

主页关于我们 有 3 个 UITextviews (headerText, bodyTextLeft, bodyTextRight) Header 在顶部,其下方的 bodytextLeft 为屏幕宽度的 50% x = 0 和 bodyTextRight x = bodyTextLeft 的宽度。

约束条件如下

heightAnchor.constraint(equalToConstant: headerText.frame.height + bodyTextLeft.frame.height).isActive = true
    
    bodyTextLeft.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 15).isActive = true
    bodyTextLeft.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width / 2).isActive = true
    bodyTextRight.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 15).isActive = true
    bodyTextRight.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width / 2).isActive = true
    bodyTextRight.leadingAnchor.constraint(equalTo: bodyTextLeft.trailingAnchor).isActive = true

HomePageSocialUp 带有图标和约束条件

的基本 header
heightAnchor.constraint(equalToConstant: 325).isActive = true

完成所有这些设置后,我仍然无法让我的 ui 滚动视图在 y 轴上滚动,将我的 homePageAboutUs 文本留在 tabBar 下方并离开屏幕。

我做错了什么?

提前致谢

首先 - UIStackView 的主要目的是安排其子视图,因此向这些子视图添加位置约束是错误的。

接下来,将子视图添加到控制器的“根”视图(例如您的滚动视图)时,请确保将它们限制在安全区域布局指南中。

第三,将滚动视图的内容限制在其内容布局指南中。

我有点接受你的描述,希望我接近你在这里的目的:

向下滚动后:

这是您的代码,经过修改以产生该结果:

class HomeTabBarController: UIViewController {
    
    let homePageView = HomePageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        homePageView.setupHomePage()
        view.addSubview(homePageView)

        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        ///constraints
        NSLayoutConstraint.activate([
            homePageView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            homePageView.topAnchor.constraint(equalTo: g.topAnchor),
            homePageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            homePageView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ])
    }

}

class HomePageSocialUp: UIView {
    func setupSocialHeader() -> Void {
        translatesAutoresizingMaskIntoConstraints = false
        backgroundColor = .red
        let imgView = UIImageView()
        imgView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(imgView)
        NSLayoutConstraint.activate([
            // constrain image view 20-pts on each side
            imgView.topAnchor.constraint(equalTo: topAnchor, constant: 20.0),
            imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
            imgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
            imgView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20.0),
            // Height = 325
            imgView.heightAnchor.constraint(equalToConstant: 325.0),
        ])
        if let img = UIImage(named: "myHeaderImage") {
            imgView.image = img
        }
    }
}
class HomePageCarrousel: UIView {
    func setupCarrousel() -> Void {
        translatesAutoresizingMaskIntoConstraints = false
        backgroundColor = .green
        let imgView = UIImageView()
        imgView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(imgView)
        NSLayoutConstraint.activate([
            // constrain image view 20-pts on each side
            imgView.topAnchor.constraint(equalTo: topAnchor, constant: 20.0),
            imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
            imgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
            imgView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20.0),
            // let's make it 3:2 ratio
            imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: 2.0 / 3.0)
        ])
        if let img = UIImage(named: "myCarouselImage") {
            imgView.image = img
        }
    }
}
class HomePageAboutUs: UIView {
    let headerText = UILabel()
    let bodyTextLeft = UILabel()
    let bodyTextRight = UILabel()
    
    func setup() -> Void {
        translatesAutoresizingMaskIntoConstraints = false
        backgroundColor = .blue
        
        [headerText, bodyTextLeft, bodyTextRight].forEach {
            // keep label height to text content
            [=10=].setContentHuggingPriority(.required, for: .vertical)
            [=10=].setContentCompressionResistancePriority(.required, for: .vertical)
            // allow word-wrap
            [=10=].numberOfLines = 0
            // yellow background
            [=10=].backgroundColor = .yellow
            [=10=].translatesAutoresizingMaskIntoConstraints = false
            addSubview([=10=])
        }
        
        NSLayoutConstraint.activate([
            // header text 8-pts from Top / Leading / Trailing
            headerText.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
            headerText.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
            headerText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
            
            // left text 8-pts from Bottom of header text, 8-pts Leading
            bodyTextLeft.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 8.0),
            bodyTextLeft.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
            
            // right text 8-pts from Bottom of header text, 8-pts Trailing
            bodyTextRight.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 8.0),
            bodyTextRight.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
            
            // 8-pts between left and right text
            bodyTextRight.leadingAnchor.constraint(equalTo: bodyTextLeft.trailingAnchor, constant: 8.0),
            
            // left and right text equal width
            bodyTextLeft.widthAnchor.constraint(equalTo: bodyTextRight.widthAnchor),
            
            // constrain Bottom of both to <= 8 (at least 8-pts
            bodyTextLeft.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
            bodyTextRight.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
        ])

        headerText.font = .systemFont(ofSize: 20, weight: .regular)
        bodyTextLeft.font = .systemFont(ofSize: 16, weight: .regular)
        bodyTextRight.font = .systemFont(ofSize: 16, weight: .regular)

        // texxt alignment
        headerText.textAlignment = .center
        bodyTextLeft.textAlignment = .left
        bodyTextRight.textAlignment = .right
        
        // some sample text
        headerText.text = "This is the text for the About Us Header label. It will, of course, wrap onto multiple lines when needed, and auto-size it's height to fit the text."
        bodyTextLeft.text = "Left label with\nembedded newlines\nso we can see it grow\nto fit the text.\nLine 5\nLine 6\nLine 7"
        bodyTextRight.text = "Right label will wrap if needed. The one with the most lines will determine the bottom."
    }
}

class HomePageView: UIScrollView {
    var homePageCarrousel: HomePageCarrousel?
    var homePageSocial: HomePageSocialUp?
    var homePageAboutUs: HomePageAboutUs?
    var stackView = UIStackView()
    
    func setupHomePage() {
        translatesAutoresizingMaskIntoConstraints = false
        homePageCarrousel = HomePageCarrousel()
        homePageSocial = HomePageSocialUp()
        homePageAboutUs = HomePageAboutUs()
        
        guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }
        
        homePageSocial.setupSocialHeader()
        stackView.addArrangedSubview(homePageSocial)
        
        homePageCarrousel.setupCarrousel()
        stackView.addArrangedSubview(homePageCarrousel)
        
        homePageAboutUs.setup()
        stackView.addArrangedSubview(homePageAboutUs)
        
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.spacing = 10
        
        setLayout()
    }
    
    func setLayout(){
        
        // constrain stackView to scroll view's Content Layout Guide
        let g = self.contentLayoutGuide
        
        ///stackview
        NSLayoutConstraint.activate([
            self.stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            self.stackView.topAnchor.constraint(equalTo: g.topAnchor),
            self.stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            self.stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            
            // stack view width is scroll view's frame layout guide width
            stackView.widthAnchor.constraint(equalTo: self.frameLayoutGuide.widthAnchor),
        ])
        
    }
    
}