Swift:如何以编程方式使用 UIView 从 Interface Builder 填充 ScrollView
Swift: How to fill a ScrollView from Interface Builder with UIViews programmatically
我正在开展一个项目,我希望用户能够 select 为同一表单使用两种输入方法。我想出了一个包含两个自定义 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 给我一条自动布局的错误消息:
Scrollable content size is ambiguous for "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)")
}
关于如何解决这个问题有什么想法吗?非常感谢你提前。请放轻松,这是我关于 Whosebug 的第一个问题。我对 swift.
中的编程也很陌生
为了让自己更轻松,
- 向滚动视图添加水平
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
}
}
编辑
一些附加说明...
UIScrollView
布局不明确。
正如我在最初的评论中所说,如果我们在 Storyboard / Iinterface Builder 中添加一个 UIScrollView
,但是 NOT 给它任何受限的内容,IB 会抱怨它有 可滚动内容大小歧义 - - 因为它确实。我们还没有告诉 IB 内容是什么。
我们可以忽略它,或者 select 滚动视图,然后在“大小检查器”窗格的底部,将 Ambiguity
更改为 Never Verify
。
作为一般规则,您应该更正所有自动布局警告/错误,但在特定情况下,例如这种情况 - 我们 知道 它是我们想要的设置,我们将在 运行-time 满足约束 - 不理会它并没有什么坏处。
UIDatePicker
没有水平居中。
它实际上是居中的。如果添加此行:
datePicker.backgroundColor = .green
您会看到对象框本身居中,但框内的 UI 元素左对齐:
根据快速研究,似乎无法更改。
现在,从 Apple 的 docs,我们看到:
You should integrate date pickers in your layout using Auto Layout. Although date pickers can be resized, they should be used at their intrinsic content size.
奇怪的是,如果我们在 Storyboard 中添加一个 UIDatePicker
,将其 Preferred Style
更改为 Compact
,并为其设置 centerX 和 centerY 约束... Storyboard 不相信它有固有内容大小。
如果我们通过代码添加它,只给它 X/Y 个位置限制,它将以 内部内容大小 显示在我们想要的位置。但是...如果我们跳入 Debug View Hierarchy
,Xcode 告诉我们它的 位置和大小不明确。
现在,还有什么更好玩的...
点击该控件并观察调试控制台充满 535 行自动布局错误/警告!!!
一些快速调查 -- 这些都是 内部 自动布局问题,与我们的代码或布局无关。
我们在 iOS 内置键盘开始显示自动完成选项时发现类似问题。
忽略这些是安全的。
我正在开展一个项目,我希望用户能够 select 为同一表单使用两种输入方法。我想出了一个包含两个自定义 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 给我一条自动布局的错误消息:
Scrollable content size is ambiguous for "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)")
}
关于如何解决这个问题有什么想法吗?非常感谢你提前。请放轻松,这是我关于 Whosebug 的第一个问题。我对 swift.
中的编程也很陌生为了让自己更轻松,
- 向滚动视图添加水平
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
}
}
编辑
一些附加说明...
UIScrollView
布局不明确。
正如我在最初的评论中所说,如果我们在 Storyboard / Iinterface Builder 中添加一个 UIScrollView
,但是 NOT 给它任何受限的内容,IB 会抱怨它有 可滚动内容大小歧义 - - 因为它确实。我们还没有告诉 IB 内容是什么。
我们可以忽略它,或者 select 滚动视图,然后在“大小检查器”窗格的底部,将 Ambiguity
更改为 Never Verify
。
作为一般规则,您应该更正所有自动布局警告/错误,但在特定情况下,例如这种情况 - 我们 知道 它是我们想要的设置,我们将在 运行-time 满足约束 - 不理会它并没有什么坏处。
UIDatePicker
没有水平居中。
它实际上是居中的。如果添加此行:
datePicker.backgroundColor = .green
您会看到对象框本身居中,但框内的 UI 元素左对齐:
根据快速研究,似乎无法更改。
现在,从 Apple 的 docs,我们看到:
You should integrate date pickers in your layout using Auto Layout. Although date pickers can be resized, they should be used at their intrinsic content size.
奇怪的是,如果我们在 Storyboard 中添加一个 UIDatePicker
,将其 Preferred Style
更改为 Compact
,并为其设置 centerX 和 centerY 约束... Storyboard 不相信它有固有内容大小。
如果我们通过代码添加它,只给它 X/Y 个位置限制,它将以 内部内容大小 显示在我们想要的位置。但是...如果我们跳入 Debug View Hierarchy
,Xcode 告诉我们它的 位置和大小不明确。
现在,还有什么更好玩的...
点击该控件并观察调试控制台充满 535 行自动布局错误/警告!!!
一些快速调查 -- 这些都是 内部 自动布局问题,与我们的代码或布局无关。
我们在 iOS 内置键盘开始显示自动完成选项时发现类似问题。
忽略这些是安全的。