How can I change the constraints so that it was like on the second screenshot? - ios

Please can you help me to change constraints in code to take an elements like in the second screen.
enter image description here
enter image description here
class DetailsHomeViewController: UIViewController {
var images:[String] = ["label","label","label"]
let MainImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage(named: "label.png")
theImageView.translatesAutoresizingMaskIntoConstraints = false
return theImageView
}()
let someImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.backgroundColor = .white
theImageView.translatesAutoresizingMaskIntoConstraints = false
theImageView.isUserInteractionEnabled = true
return theImageView
}()
lazy var collectionView:UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.dataSource = self
cv.delegate = self
cv.register(ImageCell.self, forCellWithReuseIdentifier: ImageCell.identifier)
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(MainImageView)
view.addSubview(someImageView)
someImageView.addSubview(collectionView)
someImageViewConstraints()
MainImageViewConstraints()
view.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
setupViews()
mysetupViews()
}
private func setupViews() {
someImageViewConstraints()
}
func someImageViewConstraints() {
someImageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width/2)
someImageView.center = view.center
let itemHeight = 120
let padding = 25
let width = (itemHeight * 3) + padding
collectionView.frame = CGRect(x: Int(view.center.x) - (width / 2),
y: Int(someImageView.frame.height) - (itemHeight + padding),
width: width, height: itemHeight)
}
private func mysetupViews() {
createCustomNavigationBar()
let RightButton = createCustomButton(
imageName: "square.and.arrow.up",
selector: #selector(RightButtonTapped)
)
let customTitleView = createCustomTitleView(
detailsName: "Label"
)
navigationItem.rightBarButtonItems = [RightButton]
navigationItem.titleView = customTitleView
}
#objc private func RightButtonTapped() {
print("RightButtonTapped")
}
func MainImageViewConstraints() {
MainImageView.translatesAutoresizingMaskIntoConstraints = false
} }
extension DetailsHomeViewController:UICollectionViewDataSource , UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageCell.identifier, for: indexPath) as! ImageCell
cell.someImageView.image = UIImage(named: images[indexPath.row])
return cell
}
} extension DetailsHomeViewController:UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.height, height: collectionView.frame.height)
}
}
class ImageCell:UICollectionViewCell {
static let identifier = "ImageCell"
override var isSelected: Bool {
didSet {
self.someImageView.layer.borderColor = isSelected ? UIColor.green.cgColor : UIColor.clear.cgColor
self.someImageView.layer.borderWidth = 5
}
}
let someImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.clipsToBounds = true
return theImageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 8
self.clipsToBounds = true
addSubview(someImageView)
someImageView.frame = self.bounds
}
override func layoutSubviews() {
super.layoutSubviews()
//someImageView.frame = self.frame
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

You really need to go through a bunch of good auto-layout tutorials, but here is a quick general idea...
We'll constrain the "main image view" 12-points from the top, 40-points from each side, and give it a 5:3 ratio.
Then we'll add a UIStackView (with 12-point spacing) below it, constrained 12-points from the bottom of the main image view, 75% of the width, centered horizontally.
Each image view that we add to the stack view will be constrained to a 1:2 ratio (square).
Here's sample code to do that:
class DetailsHomeViewController: UIViewController {
var images:[String] = ["label", "label", "label"]
let mainImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.contentMode = .scaleAspectFill
theImageView.clipsToBounds = true
theImageView.layer.cornerRadius = 24.0
return theImageView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
setupViews()
mysetupViews()
}
private func setupViews() {
// a stack view to hold the "thumbnail" image views
let stackView = UIStackView()
stackView.spacing = 12
mainImageView.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainImageView)
view.addSubview(stackView)
// respect the safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// main image view - let's go with
// Top with 12-points "padding"
mainImageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 12.0),
// Leading and Trailing with 40-points "padding"
mainImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
mainImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
// height equal to width x 3/5ths (5:3 aspect ratio)
mainImageView.heightAnchor.constraint(equalTo: mainImageView.widthAnchor, multiplier: 3.0 / 5.0),
// stack view
// Top 12-points from main image view Bottom
stackView.topAnchor.constraint(equalTo: mainImageView.bottomAnchor, constant: 12.0),
// width equal to 75% of the main image view width
stackView.widthAnchor.constraint(equalTo: mainImageView.widthAnchor, multiplier: 0.75),
// centered horizontally
stackView.centerXAnchor.constraint(equalTo: mainImageView.centerXAnchor),
])
// make sure we can load the main image
if let img = UIImage(named: images[0]) {
mainImageView.image = img
}
// now we'll add 3 image views to the stack view
images.forEach { imgName in
let thumbView = UIImageView()
thumbView.contentMode = .scaleAspectFill
thumbView.clipsToBounds = true
thumbView.layer.cornerRadius = 16.0
// we want them to be square
thumbView.heightAnchor.constraint(equalTo: thumbView.widthAnchor).isActive = true
// make sure we can load the image
if let img = UIImage(named: imgName) {
thumbView.image = img
}
stackView.addArrangedSubview(thumbView)
}
}
private func mysetupViews() {
createCustomNavigationBar()
let RightButton = createCustomButton(
imageName: "square.and.arrow.up",
selector: #selector(RightButtonTapped)
)
let customTitleView = createCustomTitleView(
detailsName: "Label"
)
navigationItem.rightBarButtonItems = [RightButton]
navigationItem.titleView = customTitleView
}
#objc private func RightButtonTapped() {
print("RightButtonTapped")
}
}

Related

UIImageView not resizing as circle and UILabel not resizing within StackView and Custom Collection Cell

I am trying to resize my UIImageView as a circle, however; every time I try to resize the UIImageView, which is inside a StackView along with the UILabel, I keep on ending up with a more rectangular shape. Can someone show me where I am going wrong I have been stuck on this for days? Below is my code, and what it's trying to do is add the image with the label at the bottom, and the image is supposed to be round, and this is supposed to be for my collection view controller.
custom collection view cell
import UIKit
class CarerCollectionViewCell: UICollectionViewCell {
static let identifier = "CarerCollectionViewCell"
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0, width: 20, height: 20);
//imageView.center = imageView.superview!.center;
imageView.contentMode = .scaleAspectFill
imageView.layer.borderWidth = 4
imageView.layer.masksToBounds = false
imageView.layer.borderColor = UIColor.orange.cgColor
imageView.layer.cornerRadius = imageView.frame.height / 2
return imageView
}()
private let carerNamelabel: UILabel = {
let carerNamelabel = UILabel()
carerNamelabel.layer.masksToBounds = false
carerNamelabel.font = .systemFont(ofSize: 12)
carerNamelabel.textAlignment = .center
carerNamelabel.layer.frame = CGRect(x: 0, y: 0, width: 50, height: 50);
return carerNamelabel
}()
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.layer.masksToBounds = false
stackView.axis = .vertical
stackView.alignment = .center
stackView.backgroundColor = .systemOrange
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureContentView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func configureContentView() {
imageView.clipsToBounds = true
stackView.clipsToBounds = true
carerNamelabel.clipsToBounds = true
contentView.addSubview(stackView)
configureStackView()
}
private func configureStackView() {
allContraints()
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(carerNamelabel)
}
private func allContraints() {
setStackViewConstraint()
}
private func setStackViewConstraint() {
stackView.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: contentView.heightAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
public func configureImage(with imageName: String, andImageName labelName: String) {
imageView.image = UIImage(named: imageName)
carerNamelabel.text = labelName
}
override func layoutSubviews() {
super.layoutSubviews()
stackView.frame = contentView.bounds
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
carerNamelabel.text = nil
}
}
Below here is my code for the CollectionViewControler
custom collection view
import UIKit
class CarerViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 120, height: 120)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CarerCollectionViewCell.self, forCellWithReuseIdentifier: CarerCollectionViewCell.identifier)
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = .clear
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `collectionView`
NSLayoutConstraint.activate([
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor),
collectionView.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor, constant: 600),
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CarerCollectionViewCell.identifier, for: indexPath) as! CarerCollectionViewCell
cell.configureImage(with: "m7opt04g_ms-dhoni-afp_625x300_06_July_20", andImageName: "IMAGE NO. 1")
return cell
}
}
Can somebody show or point to me what I am doing wrong, thank you
This is what I am trying to achieve
UIStackView could be tricky sometimes, you can embed your UIImageView in a UIView, and move your layout code to viewWillLayoutSubviews(), this also implys for the UILabel embed that also inside a UIView, so the containers UIViews will have a static frame for the UIStackViewto be layout correctly and whats inside them will only affect itself.

How can I add a horizontal stack of buttons above a collection view in Swift?

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.
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):
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.
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.

UIScrollView not showing up in the view

I am implementing a UIScrollView in a CollectionViewCell. I have a custom view which the scroll view should display, hence I am performing the following program in the CollectionViewCell. I have created everything programmatically and below is my code :
struct ShotsCollections {
let title: String?
}
class ShotsMainView: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
containerScrollView.contentSize.width = frame.width * CGFloat(shotsData.count)
shotsData = [ShotsCollections.init(title: "squad"), ShotsCollections.init(title: "genral")]
var i = 0
for data in shotsData {
let customview = ShotsMediaView(frame: CGRect(x: containerScrollView.frame.width * CGFloat(i), y: 0, width: containerScrollView.frame.width, height: containerScrollView.frame.height))
containerScrollView.addSubview(customview)
i += 1
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var shotsData = [ShotsCollections]()
var containerScrollView: UIScrollView = {
let instance = UIScrollView()
instance.isScrollEnabled = true
instance.bounces = true
instance.backgroundColor = blueColor
return instance
}()
private func setupViews() { //These are constraints by using TinyConstraints
addSubview(containerScrollView)
containerScrollView.topToSuperview()
containerScrollView.bottomToSuperview()
containerScrollView.rightToSuperview()
containerScrollView.leftToSuperview()
}
}
Now the issue is, while the scrollview is displayed, the content in it is not. I on printing the contentSize and frame of the scrollview, it displays 0. But if I check the Debug View Hierarchy, scrollview containes 2 views with specific frames.
I am not sure whats going wrongs. Any help is appreciated.
When you are adding customView in your containerScrollView, you are not setting up the constraints between customView and containerScrollView.
Add those constraints and you will be able to see your customViews given that your customView has some height. Also, when you add more view, you would need to remove the bottom constraint of the last added view and create a bottom constraint to the containerScrollView with the latest added view.
I created a sample app for your use case. I am pasting the code and the resultant screen shot below. Hope this is the functionality you are looking for. I suggest you paste this in a new project and tweak the code until you are satisfied. I have added comments to make it clear.
ViewController
import UIKit
class ViewController: UIViewController {
// Initialize dummy data array with numbers 0 to 9
var data: [Int] = Array(0..<10)
override func loadView() {
super.loadView()
// Add collection view programmatically
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(ShotsMainView.self, forCellWithReuseIdentifier: ShotsMainView.identifier)
self.view.addSubview(collectionView)
NSLayoutConstraint.activate([
self.view.topAnchor.constraint(equalTo: collectionView.topAnchor),
self.view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
self.view.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
])
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor.white
self.view.addSubview(collectionView)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = UIColor.white
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ShotsMainView.identifier, for: indexPath) as! ShotsMainView
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// The cell dimensions are set from here
return CGSize(width: collectionView.frame.size.width, height: 100.0)
}
}
ShotsMainView
This is the collection view cell
import UIKit
class ShotsMainView: UICollectionViewCell {
static var identifier = "Cell"
weak var textLabel: UILabel!
override init(frame: CGRect) {
// Initialize with zero frame
super.init(frame: frame)
// Add the scrollview and the corresponding constraints
let containerScrollView = UIScrollView(frame: .zero)
containerScrollView.isScrollEnabled = true
containerScrollView.bounces = true
containerScrollView.backgroundColor = UIColor.blue
containerScrollView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(containerScrollView)
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: containerScrollView.topAnchor),
self.bottomAnchor.constraint(equalTo: containerScrollView.bottomAnchor),
self.leadingAnchor.constraint(equalTo: containerScrollView.leadingAnchor),
self.trailingAnchor.constraint(equalTo: containerScrollView.trailingAnchor)
])
// Add the stack view that will hold the individual items that
// in each row that need to be scrolled horrizontally
let stackView = UIStackView(frame: .zero)
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
containerScrollView.addSubview(stackView)
stackView.backgroundColor = UIColor.magenta
NSLayoutConstraint.activate([
containerScrollView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
containerScrollView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
containerScrollView.topAnchor.constraint(equalTo: stackView.topAnchor),
containerScrollView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
])
// Add individual items (Labels in this case).
for i in 0..<10 {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
label.text = "\(i)"
label.font = UIFont(name: "System", size: 20.0)
label.textColor = UIColor.white
label.backgroundColor = UIColor.purple
label.layer.masksToBounds = false
label.layer.borderColor = UIColor.white.cgColor
label.layer.borderWidth = 1.0
label.textAlignment = .center
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1.0, constant: 0.0),
label.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.2, constant: 0.0)
])
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Screenshot

iOS UICollectionView background around cell

I have screen which look like:
As you see there is UICollectionView with image background. Background should be whiter than original image. But cells of collection view have to be transparent and display original part of image. Does somebody have idea how to it?
Update
Sample of background image
https://i.stack.imgur.com/h9Qp4.jpg
The basic idea will be:
add a background imageView behind the collectionView
configure your cells to have a translucent background with a transparent rectangular "hole"
configure the collectionView to exactly fit two columns with no spacing between the cells
handle variable "padding" on the cells so the outer portion matches the cell / row size
Here is an example:
class SeeThruCollectionViewCell: UICollectionViewCell {
static let identifier = "seeThruCellIdentifier"
var theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .clear
v.textColor = .white
v.shadowColor = .black
v.shadowOffset = CGSize(width: 1, height: 1)
v.font = UIFont.boldSystemFont(ofSize: 17)
return v
}()
var overlayPath: UIBezierPath!
var transparentPath: UIBezierPath!
var fillLayer: CAShapeLayer!
var padding: CGRect!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
padding = CGRect.zero
// init and add the sublayer we'll use as a mask
fillLayer = CAShapeLayer()
layer.addSublayer(fillLayer)
// add a label
addSubview(theLabel)
// center the label
NSLayoutConstraint.activate([
theLabel.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0.0),
theLabel.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0.0),
])
}
override func layoutSubviews() {
super.layoutSubviews()
// overlayPath and transparentPath will combine to
// create a translucent mask with a transparent rect in the middle
overlayPath = UIBezierPath(rect: bounds)
var r = bounds
r.origin.x += padding.origin.x
r.origin.y += padding.origin.y
r.size.width -= r.origin.x + padding.size.width
r.size.height -= r.origin.y + padding.size.height
transparentPath = UIBezierPath(rect: r)
overlayPath.append(transparentPath)
overlayPath.usesEvenOddFillRule = true
fillLayer.path = overlayPath.cgPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor(white: 1.0, alpha: 0.75).cgColor
}
}
class SeeThruViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var theCollectionView: UICollectionView = {
let v = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .clear
return v
}()
var theBackgroundImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// translucent padding for cells
let pad = CGFloat(12)
let numItems = 10
let numSections = 1
override func viewDidLoad() {
super.viewDidLoad()
// add background image view
view.addSubview(theBackgroundImageView)
// add collection view view
view.addSubview(theCollectionView)
if let img = UIImage(named: "cvBKG") {
theBackgroundImageView.image = img
}
let guide = view.safeAreaLayoutGuide
// constrain background image view and collection view to same frames
NSLayoutConstraint.activate([
theBackgroundImageView.topAnchor.constraint(equalTo: guide.topAnchor, constant: 0.0),
theBackgroundImageView.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: 0.0),
theBackgroundImageView.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 0.0),
theBackgroundImageView.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: 0.0),
theCollectionView.topAnchor.constraint(equalTo: theBackgroundImageView.topAnchor, constant: 0.0),
theCollectionView.bottomAnchor.constraint(equalTo: theBackgroundImageView.bottomAnchor, constant: 0.0),
theCollectionView.leadingAnchor.constraint(equalTo: theBackgroundImageView.leadingAnchor, constant: 0.0),
theCollectionView.trailingAnchor.constraint(equalTo: theBackgroundImageView.trailingAnchor, constant: 0.0),
])
// normal collection view tasks
theCollectionView.register(SeeThruCollectionViewCell.self, forCellWithReuseIdentifier: SeeThruCollectionViewCell.identifier)
theCollectionView.dataSource = self
theCollectionView.delegate = self
// we want ZERO spacing between the cells
if let flow = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flow.minimumLineSpacing = 0
flow.minimumInteritemSpacing = 0
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// set cell size to fill exactly two "columns"
if let flow = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flow.itemSize = CGSize(width: theCollectionView.frame.size.width / 2.0, height: theCollectionView.frame.size.width / 2.0)
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return numSections
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numItems
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SeeThruCollectionViewCell.identifier, for: indexPath) as! SeeThruCollectionViewCell
cell.theLabel.text = "Cell: \(indexPath.item)"
var padRect = CGRect(x: pad, y: pad, width: pad, height: pad)
if indexPath.item <= 1 {
// if it's the first row
// add paddingn on the top
padRect.origin.y = pad * 2
}
if indexPath.item % 2 == 0 {
// if it's the left "column"
// add padding on the left
padRect.origin.x = pad * 2
} else {
// if it's the right "column"
// add padding on the right
padRect.size.width = pad * 2
}
if indexPath.item / 2 >= (numItems / 2) - 1 {
// if it's the last row
// add paddingn on the bottom
padRect.size.height = pad * 2
}
cell.padding = padRect
return cell
}
}
Name your background image cvNKG.png and add it to your project's Assets, then create a new UIViewController and assign its class to SeeThruViewController. You should be able to run this as-is.
Result:
Note: this is a starting point. You'll (obviously) need to add labels for your needs, and you'll want to handle cases such as an odd number of items or if the number of rows don't fill the screen.

How can I change my UICollectionView's Flow Layout to a vertical List with Horizontal Scrolling

Basically what I am trying to create is a table with three cells stacked on top of one another. But, if there are more than three cells, I want to be able to swipe left on the Collection View to show more cells. Here is a picture to illustrate.
Right now I have the cells arranged in a list but I cannot seem to change the scroll direction for some reason. - They still scroll vertically
Here is my current code for the Flow Layout:
Note: I'm not going to include the Collection View code that is in my view controller as I do not think it is relevant.
import Foundation
import UIKit
class HorizontalListCollectionViewFlowLayout: UICollectionViewFlowLayout {
let itemHeight: CGFloat = 35
func itemWidth() -> CGFloat {
return collectionView!.frame.width
}
override var itemSize: CGSize {
set {
self.itemSize = CGSize(width: itemWidth(), height: itemHeight)
}
get {
return CGSize(width: itemWidth(), height: itemHeight)
}
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
return collectionView!.contentOffset
}
override var scrollDirection: UICollectionViewScrollDirection {
set {
self.scrollDirection = .horizontal
} get {
return self.scrollDirection
}
}
}
If you have your cells sized correctly, Horizontal Flow Layout will do exactly what you want... fill down and across.
Here is a simple example (just set a view controller to this class - no IBOutlets needed):
//
// ThreeRowCViewViewController.swift
//
// Created by Don Mag on 6/20/17.
//
import UIKit
private let reuseIdentifier = "LabelItemCell"
class LabelItemCell: UICollectionViewCell {
// simple CollectionViewCell with a label
#IBOutlet weak var theLabel: UILabel!
let testLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = UIColor.black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
}
func addViews(){
addSubview(testLabel)
testLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8.0).isActive = true
testLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ThreeRowCViewViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// 3 gray colors for the rows
let cellColors = [
UIColor.init(white: 0.9, alpha: 1.0),
UIColor.init(white: 0.8, alpha: 1.0),
UIColor.init(white: 0.7, alpha: 1.0)
]
var theCodeCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// height we'll use for the rows
let rowHeight = 30
// just picked a random width of 240
let rowWidth = 240
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
// horizontal collection view direction
layout.scrollDirection = .horizontal
// each cell will be the width of the collection view and our pre-defined height
layout.itemSize = CGSize(width: rowWidth - 1, height: rowHeight)
// no item spacing
layout.minimumInteritemSpacing = 0.0
// 1-pt line spacing so we have a visual "edge" (with horizontal layout, the "lines" are vertical blocks of cells
layout.minimumLineSpacing = 1.0
theCodeCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
theCodeCollectionView.dataSource = self
theCodeCollectionView.delegate = self
theCodeCollectionView.register(LabelItemCell.self, forCellWithReuseIdentifier: reuseIdentifier)
theCodeCollectionView.showsVerticalScrollIndicator = false
// set background to orange, just to make it obvious
theCodeCollectionView.backgroundColor = .orange
theCodeCollectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(theCodeCollectionView)
// set collection view width x height to rowWidth x (rowHeight * 3)
theCodeCollectionView.widthAnchor.constraint(equalToConstant: CGFloat(rowWidth)).isActive = true
theCodeCollectionView.heightAnchor.constraint(equalToConstant: CGFloat(rowHeight * 3)).isActive = true
// center the collection view
theCodeCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0).isActive = true
theCodeCollectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0).isActive = true
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! LabelItemCell
cell.backgroundColor = cellColors[indexPath.row % 3]
cell.testLabel.text = "\(indexPath)"
return cell
}
}
I'll leave the "enable paging" part up to you :)

Resources