以编程方式使用自动布局的最佳实践

Best Practices for programmatically using autolayout

我在我的整个应用程序中以编程方式使用 auto layout,但我真的很难让我的应用程序在所有设备上看起来都不错(尤其是在 iPhone SE 上)。这是我的 StartViewController(SE、8 和 11 Pro Max)的示例:

如您所见,iPhone 8 和 11 Pro Max 上的视图看起来相当不错。但是在 iPhone SE 上它很糟糕。我不太明白为什么,因为 space 可以像 iPhone 8 那样布置所有视图?出于某种原因,我认为 buttonslabels 更大(可能只是一种错觉)。

我的问题是如何解决这个问题?什么是最佳实践?缩小 fontSize?使 buttons 更小?获得适用于每个 iPhone 的动态布局的最佳方法是什么?显然我没有以最好的方式使用自动布局...

下面是我如何限制图片中的 views

//MARK: setupViews
func setUpViews(){

    view.addSubview(backgroundImage)
    view.addSubview(willkommenLabel)
    view.addSubview(textLabel)
    view.addSubview(emailButton)
    emailButton.addSubview(emailImage)
    view.addSubview(oderLabel)
    view.addSubview(lineLeft)
    view.addSubview(lineRight)
    view.addSubview(facebookButton)
    facebookButton.addSubview(facebookLogo)
    view.addSubview(googleButton)
    googleButton.addSubview(googleLogo)
    view.addSubview(appleButton)
    appleButton.addSubview(appleLogo)
    view.addSubview(documentsLabel)

    backgroundImage.topAnchor.constraint(equalTo: view.topAnchor, constant: -20).isActive = true
    backgroundImage.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 20).isActive = true
    backgroundImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: -20).isActive = true
    backgroundImage.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 20).isActive = true

    willkommenLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80).isActive = true
    willkommenLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
    willkommenLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true


    textLabel.topAnchor.constraint(equalTo: willkommenLabel.bottomAnchor, constant: 30).isActive = true
    textLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
    textLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true

    emailButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
    emailButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
    emailButton.topAnchor.constraint(equalTo: textLabel.topAnchor, constant: 100).isActive = true
    emailButton.heightAnchor.constraint(equalToConstant: 50).isActive = true

    emailImage.centerYAnchor.constraint(equalTo: emailButton.centerYAnchor).isActive = true
    emailImage.leadingAnchor.constraint(equalTo: emailButton.leadingAnchor, constant: 10).isActive = true
    emailImage.heightAnchor.constraint(equalToConstant: 25).isActive = true
    emailImage.widthAnchor.constraint(equalToConstant: 25).isActive = true

    oderLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    oderLabel.bottomAnchor.constraint(equalTo: emailButton.bottomAnchor, constant: 40).isActive = true
    oderLabel.widthAnchor.constraint(equalToConstant: 60).isActive = true

    lineLeft.centerYAnchor.constraint(equalTo: oderLabel.centerYAnchor).isActive = true
    lineLeft.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
    lineLeft.trailingAnchor.constraint(equalTo: oderLabel.leadingAnchor).isActive = true

    lineRight.centerYAnchor.constraint(equalTo: oderLabel.centerYAnchor).isActive = true
    lineRight.leadingAnchor.constraint(equalTo: oderLabel.trailingAnchor).isActive = true
    lineRight.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true

    facebookButton.leadingAnchor.constraint(equalTo: emailButton.leadingAnchor).isActive = true
    facebookButton.trailingAnchor.constraint(equalTo: emailButton.trailingAnchor).isActive = true
    facebookButton.bottomAnchor.constraint(equalTo: oderLabel.bottomAnchor, constant: 55 + 10).isActive = true
    facebookButton.heightAnchor.constraint(equalToConstant: 50).isActive = true

    facebookLogo.centerYAnchor.constraint(equalTo: facebookButton.centerYAnchor).isActive = true
    facebookLogo.leadingAnchor.constraint(equalTo: facebookButton.leadingAnchor, constant: 10).isActive = true
    facebookLogo.heightAnchor.constraint(equalToConstant: 25).isActive = true
    facebookLogo.widthAnchor.constraint(equalToConstant: 25).isActive = true

    googleButton.leadingAnchor.constraint(equalTo: emailButton.leadingAnchor).isActive = true
    googleButton.trailingAnchor.constraint(equalTo: emailButton.trailingAnchor).isActive = true
    googleButton.bottomAnchor.constraint(equalTo: facebookButton.bottomAnchor, constant: 55 + 10).isActive = true
    googleButton.heightAnchor.constraint(equalToConstant: 50).isActive = true

    googleLogo.centerYAnchor.constraint(equalTo: googleButton.centerYAnchor).isActive = true
    googleLogo.leadingAnchor.constraint(equalTo: googleButton.leadingAnchor, constant: 10).isActive = true
    googleLogo.heightAnchor.constraint(equalToConstant: 25).isActive = true
    googleLogo.widthAnchor.constraint(equalToConstant: 25).isActive = true

    appleButton.leadingAnchor.constraint(equalTo: emailButton.leadingAnchor).isActive = true
    appleButton.trailingAnchor.constraint(equalTo: emailButton.trailingAnchor).isActive = true
    appleButton.bottomAnchor.constraint(equalTo: googleButton.bottomAnchor, constant: 55 + 10).isActive = true
    appleButton.heightAnchor.constraint(equalToConstant: 50).isActive = true

    appleLogo.centerYAnchor.constraint(equalTo: appleButton.centerYAnchor).isActive = true
    appleLogo.leadingAnchor.constraint(equalTo: appleButton.leadingAnchor, constant: 10).isActive = true
    appleLogo.heightAnchor.constraint(equalToConstant: 25).isActive = true
    appleLogo.widthAnchor.constraint(equalToConstant: 25).isActive = true

    documentsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
    documentsLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
    documentsLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -5).isActive = true

}

实际上这取决于您希望布局在每个设备中的显示方式,因此如果您希望所有元素的高度固定,那么您应该将所有元素包装在一个滚动视图中,该滚动视图将为小型设备滚动并在不存在时执行大型设备,或者如果您需要使元素适合所有设备的屏幕,那么您应该使高度限制与屏幕高度成比例

试试这个。

它使用了几个百分比的高度(基于您在 iPhone 8 屏幕上的原始布局)。

我没有更改您现有的任何代码。只需添加以下 func 并将您的呼叫从:

setupViews()

setupViewsDon()

应该从评论中清楚地知道您可能想要进行任何调整...但希望这会让您接近您的目标 - 也许您会发现一些提示以供将来使用:

func setupViewsDon(){

    // setting these properties here, so I don't have to change your original initialization
    willkommenLabel.numberOfLines = 1
    willkommenLabel.adjustsFontSizeToFitWidth = true
    willkommenLabel.minimumScaleFactor = 0.5

    textLabel.numberOfLines = 2
    textLabel.adjustsFontSizeToFitWidth = true
    textLabel.minimumScaleFactor = 0.5

    // prevent willkommenLabel from being compressed or streched
    willkommenLabel.setContentHuggingPriority(.required, for: .vertical)
    willkommenLabel.setContentCompressionResistancePriority(.required, for: .vertical)

    // prevent oderLabel from being compressed or streched
    oderLabel.setContentHuggingPriority(.required, for: .vertical)
    oderLabel.setContentCompressionResistancePriority(.required, for: .vertical)

    // prevent documentsLabel from being compressed or streched
    documentsLabel.setContentHuggingPriority(.required, for: .vertical)
    documentsLabel.setContentCompressionResistancePriority(.required, for: .vertical)

    view.addSubview(backgroundImage)
    view.addSubview(willkommenLabel)
    view.addSubview(textLabel)
    view.addSubview(emailButton)
    emailButton.addSubview(emailImage)
    view.addSubview(oderLabel)
    view.addSubview(lineLeft)
    view.addSubview(lineRight)
    view.addSubview(facebookButton)
    facebookButton.addSubview(facebookLogo)
    view.addSubview(googleButton)
    googleButton.addSubview(googleLogo)
    view.addSubview(appleButton)
    appleButton.addSubview(appleLogo)
    view.addSubview(documentsLabel)

    backgroundImage.topAnchor.constraint(equalTo: view.topAnchor, constant: -20).isActive = true
    backgroundImage.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 20).isActive = true
    backgroundImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: -20).isActive = true
    backgroundImage.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 20).isActive = true

    // add a layout guide for percentage top spacing
    let topSpaceGuide = UILayoutGuide()
    view.addLayoutGuide(topSpaceGuide)

    // based on iPhone 8 ... 80-pts from top
    // will be shorter on smaller devices, taller on larger devices
    topSpaceGuide.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    topSpaceGuide.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 80.0 / 667.0).isActive = true

    willkommenLabel.topAnchor.constraint(equalTo: topSpaceGuide.bottomAnchor).isActive = true
    willkommenLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
    willkommenLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true

    // textLabel top constrained to willkommenLabel bottom
    textLabel.topAnchor.constraint(equalTo: willkommenLabel.bottomAnchor, constant: 0).isActive = true
    textLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
    textLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true

    // textLabel height = a percentage of view height using 100-pts based on an iPhone 8
    //  priority = .defaultHigh so it can be compressed if needed (on smaller devices)
    let c = textLabel.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 100.0 / 667.0)
    c.priority = .defaultHigh
    c.isActive = true

    // set email button height
    emailButton.heightAnchor.constraint(equalToConstant: 50).isActive = true

    // set other button heights equal to emailButton
    facebookButton.heightAnchor.constraint(equalTo: emailButton.heightAnchor).isActive = true
    googleButton.heightAnchor.constraint(equalTo: emailButton.heightAnchor).isActive = true
    appleButton.heightAnchor.constraint(equalTo: emailButton.heightAnchor).isActive = true

    // add the logo images to the buttons, and make their heights relative to button heights
    //      in case you want to change the button heights

    for (btn, img) in [(emailButton, emailImage), (facebookButton, facebookLogo), (googleButton, googleLogo), (appleButton, appleLogo)] {
        btn.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
        btn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
        btn.addSubview(img)
        img.centerYAnchor.constraint(equalTo: btn.centerYAnchor).isActive = true
        img.leadingAnchor.constraint(equalTo: btn.leadingAnchor, constant: 10).isActive = true
        img.heightAnchor.constraint(equalTo: btn.heightAnchor, multiplier: 0.5).isActive = true
        img.widthAnchor.constraint(equalTo: img.heightAnchor).isActive = true
    }

    emailButton.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 20).isActive = true
    oderLabel.topAnchor.constraint(equalTo: emailButton.bottomAnchor, constant: 15).isActive = true
    facebookButton.topAnchor.constraint(equalTo: oderLabel.bottomAnchor, constant: 15).isActive = true
    googleButton.topAnchor.constraint(equalTo: facebookButton.bottomAnchor, constant: 10).isActive = true
    appleButton.topAnchor.constraint(equalTo: googleButton.bottomAnchor, constant: 10).isActive = true

    // make sure appleButton stays above documentsLabel
    appleButton.bottomAnchor.constraint(lessThanOrEqualTo: documentsLabel.topAnchor, constant: -20.0).isActive = true

    // horizontal arrangement of oderLabel and left/right lines
    oderLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    oderLabel.widthAnchor.constraint(equalToConstant: 60).isActive = true

    lineLeft.centerYAnchor.constraint(equalTo: oderLabel.centerYAnchor).isActive = true
    lineLeft.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
    lineLeft.trailingAnchor.constraint(equalTo: oderLabel.leadingAnchor).isActive = true

    lineRight.centerYAnchor.constraint(equalTo: oderLabel.centerYAnchor).isActive = true
    lineRight.leadingAnchor.constraint(equalTo: oderLabel.trailingAnchor).isActive = true
    lineRight.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true

    // documentsLabel stay at bottom
    documentsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
    documentsLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
    documentsLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -5).isActive = true

}

如果您希望您的设计在所有设备上都能完美运行,那么您必须尽可能避免设置常量值,除非有必要,这里您设置高度和填充固定数字,尝试将它们设置为与屏幕相关大小,例如,您可以在此处设置视图中的所有按钮并将其高度设置为屏幕的一半:

  Let height = view.frame.size.height / 2
  buttonView.heightAnchor.constrains(equalTo: height).isActive = true

并且还将按钮插入到 stackView 中,覆盖按钮视图并将其设置为均匀填充按钮。因此,您将平等地拥有所有按钮而不是硬编码,以及与运行该应用程序的任何设备的屏幕尺寸相关的按钮动态视图