ios - 以编程方式在 UIScrollView 中使用 UIStackView

标签 ios swift uiscrollview uikit uistackview

我正在尝试创建这个用户界面:

enter image description here

使用此代码:

extension String {

  func style(_ attributes: [NSAttributedString.Key: Any]) -> NSAttributedString {

    return NSAttributedString(string: self, attributes: attributes)
  }
}

class ViewController: UIViewController {

  private weak var newContainer: UIView?
  private weak var newScrollContainer: UIScrollView?
  private weak var newInputContainer: UIStackView?

  private func addNewContainer() {

    let container = UIView()
    container.translatesAutoresizingMaskIntoConstraints = false
    newContainer = container

    view.addSubview(container)
    container.topAnchor.constraint(
      equalTo: view.safeAreaLayoutGuide.topAnchor,
      constant: 0).isActive = true
    container.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
    container.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
    container.heightAnchor.constraint(equalToConstant: 368).isActive = true
  }

  private func configureNewContainer() {

    newContainer?.backgroundColor = UIColor(red: 188/255, green: 188/255, blue: 188/255, alpha: 1)
    newContainer?.layer.borderColor = UIColor.black.cgColor
    newContainer?.layer.borderWidth = 1
    newContainer?.layer.cornerRadius = 6
    newContainer?.layer.maskedCorners = [
      .layerMaxXMaxYCorner,
      .layerMaxXMinYCorner,
      .layerMinXMaxYCorner,
      .layerMinXMinYCorner]
  }

  private func addNewScrollContainer() {

    guard let container = newContainer else { return }
    let scroll = UIScrollView()
    scroll.translatesAutoresizingMaskIntoConstraints = false
    self.newScrollContainer = scroll

    newContainer?.addSubview(scroll)
    scroll.topAnchor.constraint(equalTo: container.topAnchor, constant: 75).isActive = true
    scroll.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 28).isActive = true
    scroll.trailingAnchor.constraint(
      equalTo: container.trailingAnchor,
      constant: 18).isActive = true
    scroll.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 36).isActive = true
  }

  private func addStack() {

    let stack = UIStackView()
    stack.translatesAutoresizingMaskIntoConstraints = false
    stack.axis = .vertical
    stack.spacing = UIStackView.spacingUseSystem
    stack.distribution = .fillProportionally
    self.newInputContainer = stack

    newScrollContainer?.addSubview(stack)
    newScrollContainer?.topAnchor.constraint(equalTo: stack.topAnchor, constant: 0).isActive = true
    newScrollContainer?.leadingAnchor.constraint(equalTo: stack.leadingAnchor, constant: 0).isActive = true
    newScrollContainer?.trailingAnchor.constraint(equalTo: stack.trailingAnchor, constant:0).isActive = true
    newScrollContainer?.bottomAnchor.constraint(equalTo: stack.bottomAnchor, constant: 0).isActive = true
  }

  private func addStackControls() {

    guard let container = newInputContainer else { return }

    let red: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.red]

    let dest = UITextField()
    dest.attributedPlaceholder = "destination".style(red)

    let cost = UITextField()
    cost.attributedPlaceholder = "cost".style(red)

    let pets = UITextField()
    pets.attributedPlaceholder = "pets".style(red)

    let people = UITextField()
    people.attributedPlaceholder = "people".style(red)

    let horizDivider = UIView()
    horizDivider.backgroundColor = UIColor.black
    horizDivider.heightAnchor.constraint(equalToConstant: 1).isActive = true
    let width = container.frame.size.width*0.85
    horizDivider.widthAnchor.constraint(equalToConstant: width).isActive = true

    let origin = UITextField()
    origin.attributedPlaceholder = "origin".style(red)

    let departure = UITextField()
    departure.attributedPlaceholder = "departure".style(red)

    let note = UITextField()
    note.attributedPlaceholder = "note".style(red)

    let help = UIButton()
    help.heightAnchor.constraint(equalToConstant: 26).isActive = true
    help.widthAnchor.constraint(equalToConstant: 26).isActive = true
    help.layer.cornerRadius = 13
    help.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
    help.backgroundColor = UIColor.gray
    help.layer.borderColor = UIColor.darkGray.cgColor
    help.layer.borderWidth = 1

    help.setAttributedTitle("?".style([.foregroundColor: UIColor.white]), for: .normal)

    let vertDivider = UIView()
    vertDivider.backgroundColor = UIColor.gray
    vertDivider.widthAnchor.constraint(equalToConstant: 1).isActive = true
    vertDivider.heightAnchor.constraint(equalToConstant: 25).isActive = true

    let save = UIButton()
    save.heightAnchor.constraint(equalToConstant: 35).isActive = true
    save.widthAnchor.constraint(equalToConstant: 91).isActive = true
    save.layer.cornerRadius = 6
    save.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
    save.layer.borderWidth = 1
    save.layer.borderColor = UIColor.white.cgColor
    save.backgroundColor = .clear

    let costPetsPeople = UIStackView(arrangedSubviews: [cost, pets, people])
    costPetsPeople.spacing = UIStackView.spacingUseSystem
    costPetsPeople.distribution = .fillEqually
    costPetsPeople.axis = .horizontal

    let originDeparture = UIStackView(arrangedSubviews: [origin, departure])
    originDeparture.spacing = UIStackView.spacingUseSystem
    originDeparture.distribution = .fillEqually
    costPetsPeople.axis = .horizontal

    let helpDividerSave = UIStackView(arrangedSubviews: [help, vertDivider, save])
    helpDividerSave.distribution = .fillProportionally
    helpDividerSave.axis = .horizontal

    [dest, costPetsPeople, horizDivider, originDeparture, note, helpDividerSave].forEach {

      newInputContainer?.addArrangedSubview($0)
    }
  }

  override func viewDidLoad() {

    super.viewDidLoad()
    addNewContainer()
    configureNewContainer()
    addNewScrollContainer()
    addStack()
    addStackControls()

    view.backgroundColor = UIColor(red: 206/255, green: 196/255, blue: 163/255, alpha: 1)
  }
}

但我明白了:

enter image description here

这是 View 层次结构: enter image description here

感谢您的阅读,希望对您有所帮助:)。

最佳答案

情侣笔记...

  1. 使用 guard 展开对象以避免出现以下情况:newContainer?.layer.borderWidth = 1

  2. 一次从一个元素开始...如果您看不到“目标”文本字段,您可能也看不到其他任何内容。当您添加每个附加元素时,很容易看到某些内容是否被丢弃。

  3. 使用“批量”约束添加,并对它们进行逻辑分组。只是让阅读和跟踪正在发生的事情变得更容易。

  4. 提供对比背景颜色的 View ,以便更轻松地查看框架的走向。

这是你修改后的代码...你仍然需要调整一些东西才能得到你想要的东西,但这应该会让你上路(很多评论可以帮助你了解我做了什么):

extension String {

    func style(_ attributes: [NSAttributedString.Key: Any]) -> NSAttributedString {

        return NSAttributedString(string: self, attributes: attributes)
    }
}

class PanelViewController: UIViewController {

    private weak var newContainerView: UIView?
    private weak var newScrollView: UIScrollView?
    private weak var newInputStackView: UIStackView?

    private func addNewContainer() {

        let container = UIView()
        container.translatesAutoresizingMaskIntoConstraints = false
        newContainerView = container

        view.addSubview(container)

        NSLayoutConstraint.activate([
            container.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
            container.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
            container.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
            container.heightAnchor.constraint(equalToConstant: 368),
            ])

    }

    private func configureNewContainer() {

        guard let container = newContainerView else { return }

        container.backgroundColor = UIColor(red: 188/255, green: 188/255, blue: 188/255, alpha: 1)
        container.layer.borderColor = UIColor.black.cgColor
        container.layer.borderWidth = 1
        container.layer.cornerRadius = 6
        container.layer.maskedCorners = [
            .layerMaxXMaxYCorner,
            .layerMaxXMinYCorner,
            .layerMinXMaxYCorner,
            .layerMinXMinYCorner]
    }

    private func addNewScrollContainer() {

        guard let container = newContainerView else { return }

        let scroll = UIScrollView()
        scroll.translatesAutoresizingMaskIntoConstraints = false
        self.newScrollView = scroll

        container.addSubview(scroll)

        NSLayoutConstraint.activate([
            scroll.topAnchor.constraint(equalTo: container.topAnchor, constant: 75),
            scroll.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 28),
            scroll.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -18),
            scroll.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -36),
            ])

    }

    private func addStack() {

        let stack = UIStackView()
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.axis = .vertical
        stack.spacing = UIStackView.spacingUseSystem
        stack.distribution = .fill
        self.newInputStackView = stack

        guard let nsc = newScrollView else { return }
        nsc.addSubview(stack)

        NSLayoutConstraint.activate([
            stack.topAnchor.constraint(equalTo: nsc.topAnchor, constant: 0.0),
            stack.leadingAnchor.constraint(equalTo: nsc.leadingAnchor, constant: 0.0),
            stack.trailingAnchor.constraint(equalTo: nsc.trailingAnchor, constant: 0.0),
            stack.bottomAnchor.constraint(equalTo: nsc.bottomAnchor, constant: 0.0),
            ])

    }

    private func addStackControls() {

        // unwrap here to avoid the need for "?"
        guard let inputStackView = newInputStackView,
            let theScrollView = newScrollView
                else { return }

        let red: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.red]

        let dest = UITextField()
        dest.attributedPlaceholder = "destination".style(red)

        let cost = UITextField()
        cost.attributedPlaceholder = "cost".style(red)

        let pets = UITextField()
        pets.attributedPlaceholder = "pets".style(red)

        let people = UITextField()
        people.attributedPlaceholder = "people".style(red)

        let horizDivider = UIView()
        horizDivider.backgroundColor = UIColor.black

        // to get the horizDivider centered we need to embed it
        // in a "containing" view
        let horizDividerContainingView = UIView()
        horizDividerContainingView.addSubview(horizDivider)

        let origin = UITextField()
        origin.attributedPlaceholder = "origin".style(red)

        let departure = UITextField()
        departure.attributedPlaceholder = "departure".style(red)

        let note = UITextField()
        note.attributedPlaceholder = "note".style(red)

        let help = UIButton()
        help.layer.cornerRadius = 13
        help.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
        help.backgroundColor = UIColor.gray
        help.layer.borderColor = UIColor.darkGray.cgColor
        help.layer.borderWidth = 1

        help.setAttributedTitle("?".style([.foregroundColor: UIColor.white]), for: .normal)

        let vertDivider = UIView()
        vertDivider.backgroundColor = UIColor.gray

        let save = UIButton()
        save.layer.cornerRadius = 6
        save.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
        save.layer.borderWidth = 1
        save.layer.borderColor = UIColor.white.cgColor
        save.backgroundColor = .clear

        save.setTitle("save", for: .normal)

        // horizontal stack view for cost / pets / people fields
        let costPetsPeople = UIStackView(arrangedSubviews: [cost, pets, people])
        costPetsPeople.spacing = UIStackView.spacingUseSystem
        costPetsPeople.distribution = .fillEqually
        costPetsPeople.axis = .horizontal

        // horizontal stack view for origin / departure fields
        let originDeparture = UIStackView(arrangedSubviews: [origin, departure])
        originDeparture.spacing = UIStackView.spacingUseSystem
        originDeparture.distribution = .fillEqually
        costPetsPeople.axis = .horizontal

        // horizontal stack view for help / save buttons fields
        let helpDividerSave = UIStackView(arrangedSubviews: [help, vertDivider, save])
        helpDividerSave.spacing = UIStackView.spacingUseSystem
        helpDividerSave.distribution = .fill
        helpDividerSave.axis = .horizontal

        // to get the buttons stack view "right-aligned" we need to embed it
        // in a "containing" view
        let helpDividerSaveContainingView = UIView()
        helpDividerSaveContainingView.addSubview(helpDividerSave)

        // add elements to the vertical "input" stack view
        [dest, costPetsPeople, horizDividerContainingView, originDeparture, note, helpDividerSaveContainingView].forEach {
            inputStackView.addArrangedSubview($0)
        }

        // make sure everything has translatesAutoresizingMaskIntoConstraints = false
        [dest, cost, pets, people, horizDivider, horizDividerContainingView,
         origin, departure, note, help, vertDivider, save,
         costPetsPeople, originDeparture, helpDividerSave, helpDividerSaveContainingView,
         ].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        // set all the textField backgrounds to white
        [dest, cost, pets, people, origin, departure, note].forEach {
            $0.backgroundColor = .white
        }

        // setup all the elements' constraints here
        NSLayoutConstraint.activate([

            // we want the vertical "input" stack view to take up the full width
            // of the scroll view
            inputStackView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor, constant: 0.0),

            // horizontal divider 1-pt height, 85% width, centered in its containing view
            horizDivider.heightAnchor.constraint(equalToConstant: 1),
            horizDivider.widthAnchor.constraint(equalTo: horizDividerContainingView.widthAnchor, multiplier: 0.85),
            horizDivider.centerXAnchor.constraint(equalTo: horizDividerContainingView.centerXAnchor, constant: 0.0),

            // help button 26 x 26 pts
            help.heightAnchor.constraint(equalToConstant: 26),
            help.widthAnchor.constraint(equalToConstant: 26),

            // save button 91-pts wide
            save.widthAnchor.constraint(equalToConstant: 91),

            // vertical divider 1-pt width
            vertDivider.widthAnchor.constraint(equalToConstant: 1),

            // buttons stack view constrained "right-aligned" to its containing view
            helpDividerSave.topAnchor.constraint(equalTo: helpDividerSaveContainingView.topAnchor, constant: 0.0),
            helpDividerSave.trailingAnchor.constraint(equalTo: helpDividerSaveContainingView.trailingAnchor, constant: 0.0),
            helpDividerSave.bottomAnchor.constraint(equalTo: helpDividerSaveContainingView.bottomAnchor, constant: 0.0),

            ])

    }

    override func viewDidLoad() {

        super.viewDidLoad()
        addNewContainer()
        configureNewContainer()
        addNewScrollContainer()
        addStack()
        addStackControls()

        view.backgroundColor = UIColor(red: 206/255, green: 196/255, blue: 163/255, alpha: 1)

    }
}

结果:

enter image description here

关于ios - 以编程方式在 UIScrollView 中使用 UIStackView,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56622671/

相关文章:

ios - 我可以使用 scenekit 和 arkit 制作可以透过透明物体看到的阴影吗?

ios - 将数组保存到核心数据

ios - StoreKit 的 finishTransaction 崩溃

database - 向 Kinvey 发送数据时出现 SIGABRT 错误

ios - 如何禁用 ScrollView subview 的点击手势?

ios - 将游戏屏幕分成左右触摸?

ios - 为什么 UICollectionView 在 Xcode 11 (ios 13) 中省略 Line/Intrim 间距?

ios - Swift: `["one": nil] as [String: Any]` 有效,为什么?

ios - UICollectionView 是否滚动到索引?

swift - 将 UIScrollView 控制在其目标范围之外