如何在 Swift 中的集合视图上方添加水平按钮堆栈?
Posted
技术标签:
【中文标题】如何在 Swift 中的集合视图上方添加水平按钮堆栈?【英文标题】:How can I add a horizontal stack of buttons above a collection view in Swift? 【发布时间】:2020-10-02 15:54:13 【问题描述】:我正在尝试在集合视图上方添加三个按钮,根据用户选择的按钮从照片库中提取视频、照片或所有内容。
我设法分别创建了集合视图和按钮,但是当我尝试将它们组合到垂直堆栈视图中时,我在声明 UIStackView 时遇到错误。
我正在尝试将水平堆栈视图(按钮)组合成垂直堆栈视图(顶部的按钮堆栈视图和下方的集合视图)。
我认为该错误与我声明 buttonStack 的方式有关,但我尝试的一切都失败了。
我认为两个堆叠的视图将是实现我的目标的最佳方式,但我愿意接受其他/更好的建议。无论如何,我想知道为什么这对我不起作用。
代码行和错误信息:
让 stackView = UIStackView(arrangedSubviews: [buttonsStack, collectionView!])
没有更多上下文的表达类型是模棱两可的
class TestVideoItemCell: UICollectionViewCell
var stackView = UIStackView()
var vid = UIImageView()
override init(frame: CGRect)
super.init(frame: frame)
vid.contentMode = .scaleAspectFill
vid.clipsToBounds = true
self.addSubview(vid)
override func layoutSubviews()
super.layoutSubviews()
vid.frame = self.bounds
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
class TestVideoViewVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UINavigationControllerDelegate
var myCollectionView: UICollectionView!
var videoArray = [UIImage]()
override func viewDidLoad()
super.viewDidLoad()
grabVideos()
//MARK: CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return videoArray.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath) as! VideoItemCell
cell.vid.image = videoArray[indexPath.item]
return cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
let width = collectionView.frame.width
return CGSize(width: width/4 - 1, height: width/4 - 1)
override func viewWillLayoutSubviews()
super.viewWillLayoutSubviews()
myCollectionView.collectionViewLayout.invalidateLayout()
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
return 1.0
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
return 1.0
//MARK: grab videos
func grabVideos()
videoArray = []
DispatchQueue.global(qos: .background).async
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)]
let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions)
print(fetchResult)
print(fetchResult.count)
if fetchResult.count > 0
for i in 0..<fetchResult.count
imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset, targetSize: CGSize(width:500, height: 500),contentMode: .aspectFill, options: requestOptions, resultHandler: (image, error) in
self.videoArray.append(image!)
)
else
print("No videos found.")
DispatchQueue.main.async
self.myCollectionView.reloadData()
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// MARK: SetUp Methods
func buttonsStack()
let buttonChoices = ButtonStackView()
buttonChoices.setupButtonsStackView()
func setupCollection()
let layout = UICollectionViewFlowLayout()
myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
myCollectionView.delegate = self
myCollectionView.dataSource = self
myCollectionView.register(VideoItemCell.self, forCellWithReuseIdentifier: "videoCell")
myCollectionView.backgroundColor = UIColor.white
self.view.addSubview(myCollectionView)
myCollectionView.autoresizingMask = UIView.AutoresizingMask(rawValue: UIView.AutoresizingMask.RawValue(UInt8(UIView.AutoresizingMask.flexibleWidth.rawValue) | UInt8(UIView.AutoresizingMask.flexibleHeight.rawValue)))
func setupStack()
let stackView = UIStackView(arrangedSubviews: [buttonsStack, collectionView!])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .fillEqually
stackView.axis = .horizontal
stackView.spacing = 8
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
])
更新 下面是我正在尝试创建的 UI 的一般概念的图像示例。
【问题讨论】:
不使用情节提要??? 不,我正在尝试尽可能以编程方式进行。我知道使用界面构建器更容易,但我认为长期学习如何以编程方式完成它会更好,但我对 Swift 太陌生了,我不确定实现这一点的最佳方法。界面构建器会更好吗? 这当然不是“更容易!”用故事板来做!这是一种真正的艺术形式。如果您的目标最终是在商业上工作,那么大笔资金就是掌握故事板。毫无疑问,您必须在这两个方面都绝对是专家:/ 在这个时代,你真的不能“设置框架”,比如你的代码在 .. vid.frame = self.bounds。您必须在任何地方使用约束(即自动布局)。老实说不幸的是,您可能必须从头开始,到处都有限制。 (只有在低级别工作时才“设置框架”,使用 CALayers 制作完全自定义的视图,示例***.com/a/41962342/294884(查看底部的链接)) 【参考方案1】:试着一步一步来……
从此代码开始(分配给普通的UIViewController
作为UINavigationController
的根控制器):
class TestVideoViewVC: UIViewController
var myCollectionView: UICollectionView!
var videoArray = [UIImage]()
override func viewDidLoad()
super.viewDidLoad()
// set main view background color to a nice medium blue
view.backgroundColor = UIColor(red: 0.25, green: 0.5, blue: 1.0, alpha: 1.0)
// vertical stack view for the full screen (safe area)
let mainStack = UIStackView()
mainStack.axis = .vertical
mainStack.spacing = 8
mainStack.translatesAutoresizingMaskIntoConstraints = false
// add it to the view
view.addSubview(mainStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: g.topAnchor),
mainStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
mainStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
mainStack.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
如果您按原样运行应用程序,您会看到:
堆栈视图在那里,但我们还没有添加任何子视图。
所以,让我们添加两个视图(标签)...顶部的高度为 50 点:
class TestVideoViewVC: UIViewController
var myCollectionView: UICollectionView!
var videoArray = [UIImage]()
override func viewDidLoad()
super.viewDidLoad()
// set main view background color to a nice medium blue
view.backgroundColor = UIColor(red: 0.25, green: 0.5, blue: 1.0, alpha: 1.0)
// vertical stack view for the full screen (safe area)
let mainStack = UIStackView()
mainStack.axis = .vertical
mainStack.spacing = 8
mainStack.translatesAutoresizingMaskIntoConstraints = false
// add it to the view
view.addSubview(mainStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: g.topAnchor),
mainStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
mainStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
mainStack.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
// add two arranged subviews, so we can see the layout
let v1 = UILabel()
v1.textAlignment = .center
v1.text = "Buttons will go here..."
v1.backgroundColor = .green
let v2 = UILabel()
v2.textAlignment = .center
v2.text = "Collection view will go here..."
v2.backgroundColor = .yellow
// let's give the top view a height of 50-pts
v1.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
mainStack.addArrangedSubview(v1)
mainStack.addArrangedSubview(v2)
运行该代码,我们得到:
现在,让我们将v1
(“顶部”标签)替换为带有 3 个红色、50 磅高的按钮的水平堆栈视图:
class TestVideoViewVC: UIViewController
var myCollectionView: UICollectionView!
var videoArray = [UIImage]()
override func viewDidLoad()
super.viewDidLoad()
// set main view background color to a nice medium blue
view.backgroundColor = UIColor(red: 0.25, green: 0.5, blue: 1.0, alpha: 1.0)
// vertical stack view for the full screen (safe area)
let mainStack = UIStackView()
mainStack.axis = .vertical
mainStack.spacing = 8
mainStack.translatesAutoresizingMaskIntoConstraints = false
// add it to the view
view.addSubview(mainStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: g.topAnchor),
mainStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
mainStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
mainStack.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
// create a horizontal stack view
let buttonsStack = UIStackView()
buttonsStack.axis = .horizontal
buttonsStack.spacing = 8
buttonsStack.distribution = .fillEqually
// create and add 3 50-pt height buttons to the stack view
["Videos", "Photos", "All"].forEach str in
let b = UIButton()
b.setTitle(str, for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.gray, for: .highlighted)
b.backgroundColor = .red
buttonsStack.addArrangedSubview(b)
b.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// add the buttons stack view to the main stack view
mainStack.addArrangedSubview(buttonsStack)
// create a label (this will be our collection view)
let v2 = UILabel()
v2.textAlignment = .center
v2.text = "Collection view will go here..."
v2.backgroundColor = .yellow
// add the label to the main stack view
mainStack.addArrangedSubview(v2)
运行该代码,我们得到:
现在我们可以用我们的集合视图替换v2
:
class TestVideoViewVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UINavigationControllerDelegate
var myCollectionView: UICollectionView!
var videoArray = [UIImage]()
override func viewDidLoad()
super.viewDidLoad()
// set main view background color to a nice medium blue
view.backgroundColor = UIColor(red: 0.25, green: 0.5, blue: 1.0, alpha: 1.0)
// vertical stack view for the full screen (safe area)
let mainStack = UIStackView()
mainStack.axis = .vertical
mainStack.spacing = 8
mainStack.translatesAutoresizingMaskIntoConstraints = false
// add it to the view
view.addSubview(mainStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: g.topAnchor),
mainStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
mainStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
mainStack.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
// create a horizontal stack view
let buttonsStack = UIStackView()
buttonsStack.axis = .horizontal
buttonsStack.spacing = 8
buttonsStack.distribution = .fillEqually
// create and add 3 50-pt height buttons to the stack view
["Videos", "Photos", "All"].forEach str in
let b = UIButton()
b.setTitle(str, for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.gray, for: .highlighted)
b.backgroundColor = .red
buttonsStack.addArrangedSubview(b)
b.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// add the buttons stack view to the main stack view
mainStack.addArrangedSubview(buttonsStack)
// setup the collection view
setupCollection()
// add it to the main stack view
mainStack.addArrangedSubview(myCollectionView)
// start the async call to get the assets
grabVideos()
func setupCollection()
let layout = UICollectionViewFlowLayout()
myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
myCollectionView.delegate = self
myCollectionView.dataSource = self
myCollectionView.register(VideoItemCell.self, forCellWithReuseIdentifier: "videoCell")
myCollectionView.backgroundColor = UIColor.white
//MARK: CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return videoArray.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath) as! VideoItemCell
cell.vid.image = videoArray[indexPath.item]
return cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
let width = collectionView.frame.width
return CGSize(width: width/4 - 1, height: width/4 - 1)
override func viewWillLayoutSubviews()
super.viewWillLayoutSubviews()
myCollectionView.collectionViewLayout.invalidateLayout()
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
return 1.0
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
return 1.0
//MARK: grab videos
func grabVideos()
videoArray = []
DispatchQueue.global(qos: .background).async
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)]
//let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions)
let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
print(fetchResult)
print(fetchResult.count)
if fetchResult.count > 0
for i in 0..<fetchResult.count
imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset, targetSize: CGSize(width:500, height: 500),contentMode: .aspectFill, options: requestOptions, resultHandler: (image, error) in
self.videoArray.append(image!)
)
else
print("No videos found.")
DispatchQueue.main.async
self.myCollectionView.reloadData()
class VideoItemCell: UICollectionViewCell
var stackView = UIStackView()
var vid = UIImageView()
override init(frame: CGRect)
super.init(frame: frame)
vid.contentMode = .scaleAspectFill
vid.clipsToBounds = true
self.addSubview(vid)
override func layoutSubviews()
super.layoutSubviews()
vid.frame = self.bounds
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
我们应该有这个(我只有默认照片,没有视频,所以我更改了grabVideos()
来获取照片):
【讨论】:
我对 Swift 很陌生(您可能已经注意到了),但是您已经针对我提出或研究过的问题发布了几个答案。您的回答和解释非常有帮助!非常感谢你!最初我对你的答案有类似的方法,但我无法让它发挥作用。那是我开始尝试不同的事情并以 OP 中发布的意大利面条代码结束的时候。我现在看到我的原始问题与我使用 .addArrangedSubview 的方式有关。再次感谢!!【参考方案2】:使用 AutoLayout 而不是垂直 UIStackView 可能会更好。
因此,您应该将 Button 堆栈视图放在 ViewController 的顶部,并且您可能希望给它一个固定的高度。然后,不是设置collectionView的框架,而是设置它的约束,使其边缘粘在viewController的视图上,除了顶部约束,它应该粘在按钮堆栈的底部约束。
旁注,界面生成器不一定比以编程方式创建视图更好。就个人而言,我建议您坚持编程方式。
【讨论】:
您必须使用情节提要完全掌握和理解各处的约束(自动布局)。然后(如果出于某种原因你想)你可以在代码中完成这一切。 理解约束实际上并不难,除非你是一个完全的初学者。每个初级开发人员都应该能够做到这一点。【参考方案3】:好吧,我建议以编程方式进行。但是,我不太了解您的代码,但我建议您可以将UIStackView
放在安全区域下方,然后会有UICollectionView
。
private let stackV: UIStackView =
let stackV = UIStackView()
stackV.axis = .horizontal
stackV.distribution = .horizontal
stackV.distribution = .equalSpacing
return stackV
()
然后,只需创建按钮:
private let collectionB: [UIButtons] =
let buttonA = UIButton()
let buttonB = UIButton()
let buttonC = UIButton()
return [buttonA, buttonB, buttonC]
()
您可以将其作为排列好的子视图添加到stackV
:
collectionB.forEach stackV.addArrangedSubview($0)
这样就可以了,你必须给stackV
一个固定的高度,然后你可以在底部添加collectionV
并相应地放置你的约束。
我已经以编程方式创建视图很长一段时间了,而您想要实现的目标是完全可以实现的,如果我看到 UI
那么事情会更好,我不明白的是为什么你有没有在你的UICollectionViewCell
中加了stackView
,可能处理起来更流畅。
P.S.:我没有在 Xcode
上输入此代码,它可能会引发一些错误。
【讨论】:
虽然这段代码很好,但需要注意的一点是,在现实生活中的应用程序中,您可以只使用“:固定高度”是不寻常的。 (请注意,我什至无法遵循 OP 正在尝试做的事情 :)) @Rob 感谢您的解释。我现在正在尝试一些解决方案。我知道我的代码和解释/问题可能令人困惑(我显然是新手)。感谢您的回复。它使我的问题变得清晰。我刚刚用我想要实现的 UI 模型更新了我的问题。以上是关于如何在 Swift 中的集合视图上方添加水平按钮堆栈?的主要内容,如果未能解决你的问题,请参考以下文章