【问题标题】:Swift: How to fill a ScrollView from Interface Builder with UIViews programmaticallySwift:如何以编程方式使用 UIViews 从 Interface Builder 填充 ScrollView
【发布时间】:2021-05-21 21:13:05
【问题描述】:

我正在开展一个项目,我希望用户能够为同一个表单选择两种输入方法。我想出了一个包含两个自定义 UIView(以编程方式制作)的滚动视图。这是负责的视图控制器的代码:

import UIKit

class MainVC: UIViewController, UIScrollViewDelegate {

    @IBOutlet weak var scrollView: UIScrollView!
    
    @IBOutlet weak var pageControl: UIPageControl!
    
    var customView1: CustomView1 = CustomView1()
    
    var customView2: customView2 = CustomView2()
    
    var frame = CGRect.zero
    
    func setupScrollView() {

        pageControl.numberOfPages = 2
        
        frame.origin.x = 0
        frame.size = scrollView.frame.size
        
        customView1 = customView1(frame: frame)

        self.scrollView.addSubview(customView1)
        
        frame.origin.x = scrollView.frame.size.width
        frame.size = scrollView.frame.size
        
        customView2 = CustomView2(frame: frame)
        
        self.scrollView.addSubview(customView2)
        
        self.scrollView.contentSize = CGSize(width: scrollView.frame.size.width * 2, height: scrollView.frame.size.height)
        
        self.scrollView.delegate = self
        
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
        pageControl.currentPage = Int(pageNumber)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupScrollView()
        
        scrollView.delegate = self
    }

当它工作时,Xcode 给我一个自动布局的错误消息:

“ScrollView”的可滚动内容大小不明确

还有一个问题:第二个 UIView 上的内容没有居中,即使它应该是:

picture of the not centered content

import UIKit

class customView2: UIView {

lazy var datePicker: UIDatePicker = {
    let datePicker = UIDatePicker()
    datePicker.translatesAutoresizingMaskIntoConstraints = false
    return datePicker
  }()

//initWithFrame to init view from code
override init(frame: CGRect) {
    super.init(frame: frame)
    setupView()
}
  
//initWithCode to init view from xib or storyboard
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupView()
}

func setupView () {
    self.backgroundColor = .systemYellow
    
    datePicker.datePickerMode = .date
    
    datePicker.addTarget(self, action: #selector(self.datePickerValueChanged(_:)), for: .valueChanged)
    
    addSubview(datePicker)
    
    setupLayout()
}

func setupLayout() {
    
    let view = self
    
    NSLayoutConstraint.activate([
        datePicker.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        datePicker.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
        datePicker.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
        datePicker.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2)
    ])
}

@objc func datePickerValueChanged(_ sender: UIDatePicker) {
    let dateFormatter: DateFormatter = DateFormatter()
    dateFormatter.dateFormat = "dd.MM.yyyy"
    let selectedDate: String = dateFormatter.string(from: sender.date)
    print("Selected value \(selectedDate)")
}

关于如何解决这个问题的任何想法?非常感谢您提前。请放轻松,这是我关于stackoverflow的第一个问题。我对 swift 编程也很陌生。

【问题讨论】:

  • 1) Storyboard / Interface Builder 中的空滚动视图将始终显示“滚动视图”的可滚动内容大小不明确 - 因为您没有为其内容放置任何内容. 2)当你将customView1customView2视图添加到滚动视图时,使用自动布局约束...视图框架尚未在viewDidLoad()中设置。

标签: ios swift xcode interface-builder


【解决方案1】:

为了让自己更轻松,

  • 向滚动视图添加水平的UIStackView
  • 设置.distribution = .fillEqually
  • 将所有 4 个边约束到滚动视图的 .contentLayoutGuide
  • 将其高度限制为滚动视图的.frameLayoutGuide
  • 将您的自定义视图添加到堆栈视图中
  • 第一个自定义视图的宽度限制为滚动视图.frameLayoutGuide的宽度

这是你的代码,用这种方法修改过:

class MainVC: UIViewController, UIScrollViewDelegate {
    
    @IBOutlet weak var scrollView: UIScrollView!
    
    @IBOutlet weak var pageControl: UIPageControl!
    
    var customView1: CustomView1 = CustomView1()
    
    var customView2: CustomView2 = CustomView2()
    
    func setupScrollView() {
        
        pageControl.numberOfPages = 2
        
        // let's put the two custom views in a horizontal stack view
        let stack = UIStackView()
        stack.axis = .horizontal
        stack.distribution = .fillEqually
        stack.translatesAutoresizingMaskIntoConstraints = false
        
        stack.addArrangedSubview(customView1)
        stack.addArrangedSubview(customView2)
        
        // add the stack view to the scroll view
        scrollView.addSubview(stack)
        
        let contentG = scrollView.contentLayoutGuide
        let frameG = scrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain stack view to all 4 sides of content layout guide
            stack.topAnchor.constraint(equalTo: contentG.topAnchor),
            stack.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
            stack.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
            stack.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),

            // stack view Height equal to scroll view frame layout guide height
            stack.heightAnchor.constraint(equalTo: frameG.heightAnchor),
            
            // stack is set to fillEqually, so we only need to set
            //  width of first custom view equal to scroll view frame layout guide width
            customView1.widthAnchor.constraint(equalTo: frameG.widthAnchor),
            
        ])
        
        self.scrollView.delegate = self
        
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
        pageControl.currentPage = Int(pageNumber)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupScrollView()
        
        scrollView.delegate = self
    }
}

编辑

几个附加说明...

  1. UIScrollView 布局模糊。

正如我在最初的评论中所说,如果我们在 Storyboard / I界面Buider中添加UIScrollView,但NOT 给它任何受限制的内容,IB 会抱怨它有可滚动内容大小歧义——因为它确实。我们还没有告诉 IB 内容是什么。

我们可以忽略它,或者选择滚动视图,然后在 Size Inspector 窗格的底部,将 Ambiguity 更改为 Never Verify

作为一般规则,您应该更正所有自动布局警告/错误,但在诸如此类的特定情况下 - 我们知道这是我们想要的设置,并且我们将在运行时满足约束 - 不理会它并没有什么坏处。

  1. UIDatePicker 没有水平居中。

它实际上是居中的。如果添加这一行:

datePicker.backgroundColor = .green

您会看到对象框架本身是居中的,但框架内的 UI 元素是左对齐的:

根据快速研究,似乎无法更改。

现在,从 Apple 的 docs,我们看到:

您应该使用自动布局将日期选择器集成到您的布局中。尽管可以调整日期选择器的大小,但它们应该以其固有的内容大小使用。

奇怪的是,如果我们在 Storyboard 中添加 UIDatePicker,将其 Preferred Style 更改为 Compact,并为其设置 centerX 和 centerY 约束... Storyboard 不相信它具有固有的内容大小。

如果我们通过代码添加它,只给它 X/Y 位置约束,它将以内在内容大小显示在我们想要的位置。但是...如果我们跳转到Debug View Hierarchy,Xcode 会告诉我们它的位置和大小是不明确的。

现在,更有趣的是……

点击该控件并观察调试控制台填充 535 行自动布局错误/警告!!!

一些快速调查——这些都是内部自动布局问题,与我们的代码或布局无关。

当 iOS 内置键盘开始显示自动完成选项时,我们看到了类似的问题。

这些是可以安全忽略的。

【讨论】:

  • 很好的建议,我会试试这个,让你知道结果如何!非常感谢您的帮助!
  • 工作得同样好,代码也更干净。虽然 DatePicker 仍然没有居中,但这可能是一个更深层次的问题,因为我得到了有关 DatePicker 约束的控制台错误的完整列表。我想我明天会看看它。
  • 关于模糊滚动视图大小的错误消息仍然存在,但我认为忽略它是安全的?
  • @RickDeckard - 抱歉,我错过了导致该问题的原因。请参阅我的答案的编辑
  • 哇,干得好!非常感谢你的帮助!在控制台中看到 DatePicker 出现的许多错误行时,我也感到有些惊讶,但是您的调查确实很有帮助。你可能为我节省了几个小时的工作时间。
猜你喜欢
  • 2017-02-04
  • 2015-12-13
  • 1970-01-01
  • 2017-05-23
  • 1970-01-01
  • 2017-12-09
  • 2019-07-02
  • 2018-01-22
  • 1970-01-01
相关资源
最近更新 更多