Reputation: 1014
I am trying to add three buttons above a collection view that pulls either the videos, photos, or all content from the photo library based on what button a user selects.
I managed to create the collection view and the buttons separately but when I try to combine them in a vertical stack view I get an error where I declare the UIStackView.
I am trying to combine a horizontal stack view (the buttons) into a vertical stack view (the button stack view on top and the collection view below).
I think the error is related to how I am declaring the buttonStack but everything I have tried has failed.
I assumed that two stacked views would be the best way to accomplish my goal but I am open to other/better suggestions. Regardless I would like to know why this is not working for me.
Code line and error message:
let stackView = UIStackView(arrangedSubviews: [buttonsStack, collectionView!])
Type of expression is ambiguous without more context
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),
])
}
}
UPDATE
Below is an image example of the general concept of the UI I am trying to create.
Upvotes: 0
Views: 1697
Reputation: 77672
Try taking things step-by-step...
Start with this code (assigned to a plain UIViewController
as the root controller of a 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),
])
}
}
If you run the app as-is, you'll see this:
The stack view is there, but we haven't added any subviews to it.
So, let's add two views (labels)... the top one at 50-pts in height:
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)
}
}
Run that code, and we get:
Now, let's replace v1
(the "top" label) with a horizontal stack view with 3 red, 50-pt tall buttons:
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)
}
}
Run that code, and we get:
Now we can replace v2
with our collection view:
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")
}
}
And we should have this (I just have the default photos, no videos, so I changed grabVideos()
to get photos):
Upvotes: 3
Reputation: 2164
Well, I will suggest to go ahead programatically. But, I didn't under stood your code a lot, but what I will suggest you that you can take a UIStackView
and place it below safe area and then there will be the UICollectionView
.
private let stackV: UIStackView = {
let stackV = UIStackView()
stackV.axis = .horizontal
stackV.distribution = .horizontal
stackV.distribution = .equalSpacing
return stackV
}()
Then, just create the buttons:
private let collectionB: [UIButtons] = {
let buttonA = UIButton()
let buttonB = UIButton()
let buttonC = UIButton()
return [buttonA, buttonB, buttonC]
}()
Them you can add it as an arranged subview to the stackV
:
collectionB.forEach { stackV.addArrangedSubview($0) }
This will do it, you have to give a fixed heigh to the stackV
and that is it then you can add the collectionV
at the bottom and place your constraints accordingly.
I have created views programatically for a long time now and what you are trying to achieve is totally achievable, if I got to look at the UI
then things would have been better, what I didn't understood is that why have you added a stackView
in your UICollectionViewCell
, may be it can be handled in a smoother way.
P.S.: I haven't typed this code on Xcode
it may throw some error.
Upvotes: 1
Reputation: 342
You might be better off using AutoLayout instead of a vertical UIStackView.
So you should put the Button stack view on the top of the ViewController, and you might want to give it a fixed height. Then, instead of setting the frame of collectionView, set up its constraints, so that its edges stick to the viewController's view, except for top constraint, which should stick to the button stack's bottom constraint.
Side note, interface builder is not necessarily better than creating views programmatically. Personally, I would suggest you stick with the programmatic way.
Upvotes: 1