I am developing a UIImageView on top of UICollectionView using autolayout constraints:
I have anchored the UIImageView to take the top part of screen with a fixed H:W ratio of 4:3, then let UICollectionView to take whatever space is left at the bottom.
Strangely, when I ran it, the view in xcode simulator is quite different from the view-hierarchy debugger:
View Hierarchy Debugger:
iPhone 8 Plus simulator:
In the UICollectionView, each cell will show a face photo, I have configured the itemSize of UICollectionView to be a square. It should show then entire face (so the debugger view is correct, the simulator is not).
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController : UICollectionViewDelegateFlowLayout {
// set item size
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
// gw: to force one row, height need to be smaller than flow height
return CGSize(width: collectionView.bounds.height, height: collectionView.bounds.height)
}
}
What could be the cause of this difference?
I am using xcode 10.1, ios 12.1
Full code is here (not very long):
import UIKit
class ViewController: UICollectionViewController{
let collectionViewCellIdentifier = "MyCollectionViewCellIdentifier"
let canvas:Canvas = {
let canvas = Canvas()
canvas.backgroundColor = UIColor.black
canvas.translatesAutoresizingMaskIntoConstraints=false
canvas.alpha = 0.2
return canvas
} ()
let photoView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(imageLiteralResourceName: "hongjinbao")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
return imageView
} ()
private let myArray: NSArray = ["First","Second","Third"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// stack views
view.addSubview(photoView)
view.addSubview(canvas)
collectionView?.backgroundColor = UIColor.white
collectionView?.translatesAutoresizingMaskIntoConstraints = false
collectionView?.register(PersonCollectionViewCell.self, forCellWithReuseIdentifier: collectionViewCellIdentifier)
setupLayout()
}
private func setupLayout() {
photoView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
photoView.heightAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.333).isActive = true
photoView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
photoView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
canvas.topAnchor.constraint(equalTo: photoView.topAnchor).isActive = true
canvas.bottomAnchor.constraint(equalTo: photoView.bottomAnchor).isActive = true
canvas.leadingAnchor.constraint(equalTo: photoView.leadingAnchor).isActive = true
canvas.trailingAnchor.constraint(equalTo: photoView.trailingAnchor).isActive = true
collectionView?.topAnchor.constraint(equalTo: photoView.bottomAnchor).isActive = true
collectionView?.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
collectionView?.leadingAnchor.constraint(equalTo: photoView.leadingAnchor).isActive = true
collectionView?.trailingAnchor.constraint(equalTo: photoView.trailingAnchor).isActive = true
}
}
// MARK: - UICollectionViewDataSource
extension ViewController {
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.collectionViewCellIdentifier, for: indexPath)
return cell
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 13
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController : UICollectionViewDelegateFlowLayout {
// set item size
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
// gw: to force one row, height need to be smaller than flow height
return CGSize(width: collectionView.bounds.height, height: collectionView.bounds.height)
}
}
import UIKit
class PersonCollectionViewCell: UICollectionViewCell {
let face: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(imageLiteralResourceName: "mary")
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
} ()
let name: UILabel = {
let textLabel = UILabel()
textLabel.translatesAutoresizingMaskIntoConstraints = false
return textLabel
} ()
let desc: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
} ()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
// gw: needed by compiler
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
addSubview(face)
addSubview(name)
addSubview(desc)
backgroundColor = UIColor.red
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[v0]|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: ["v0":face]))
}
}
I found the cause, it is due to the stacking order of subviews in my ViewController. The photoView was add on top of the collectionView, and it blocked part of collectionView.
Solution: in viewDidLoad add below statement to bring the later one to front:
// stack views
view.addSubview(photoView)
// little trick to bring inherent collectionView to front
view.bringSubviewToFront(self.collectionView)
Related
I have a collection view which takes up the whole screen and should scroll horizontally.
Problem is I can't adjust the size for item while retaining the carousel effect. If I lower the size for item, the cell stacks vertically just like here:
The pink background is the collectionView. The collectionView fills the viewController's view that is why the cells doesn't align horizontally.
The alignment should be like this which scrolls horizontally much like a carousel but the problem is it's too big and it took up the whole view.
What's expected is for the cell and collection view to be around 100 height or 200 height only and can still scroll horizontally.
This is my existing code:
I am using SnapKit for constraints.
ViewController
import UIKit
import SnapKit
class ViewController: UIViewController {
let viewModel: [SeatViewModel] = [.init(text: "single text"), .init(text: "slightly longer text."), .init(text: "very very very very long long text")]
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.backgroundColor = .white
cv.register(SeatCardCell.self, forCellWithReuseIdentifier: "SeatCardCell")
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(200)
make.height.equalTo(100)
}
collectionView.dataSource = self
collectionView.backgroundColor = .systemPink
}
func lines(label: UILabel) -> Int {
self.collectionView.layoutIfNeeded()
let textSize = CGSize(width: label.frame.size.width, height: CGFloat(Float.infinity))
let rHeight = lroundf(Float(label.sizeThatFits(textSize).height))
let charSize = lroundf(Float(label.font.lineHeight))
let lineCount = rHeight/charSize
return lineCount
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectioCell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeatCardCell", for: indexPath) as? SeatCardCell else { fatalError() }
collectioCell.backgroundColor = [UIColor.green, .red, .black, .brown, .yellow].randomElement()
collectioCell.viewModel = viewModel[indexPath.row]
return collectioCell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 200, height: self.collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
}
View Model and Cell
import UIKit
import SnapKit
struct SeatViewModel {
let text: String
}
class SeatCardCell: UICollectionViewCell {
var viewModel: SeatViewModel? {
didSet {
configure()
}
}
// MARK: - Properties
let seatImageView = UIImageView(image: nil)
let seatLabel: CustomLabel = {
let label = CustomLabel()
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
guard let viewModel = viewModel else { return }
seatLabel.text = viewModel.text
seatLabel.backgroundColor = .cyan
seatLabel.sizeToFit()
seatImageView.image = .strokedCheckmark
let stackView = UIStackView(arrangedSubviews: [seatImageView, seatLabel])
stackView.spacing = 0
stackView.alignment = .center
addSubview(stackView)
seatImageView.snp.makeConstraints { make in
make.size.equalTo(40)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
// MARK: - CUSTOM TITLE LABEL
class CustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.textColor = .darkGray
self.textAlignment = .left
self.numberOfLines = 4
}
}
The problem of your code is you set again the collection view frame equal to view frame in viewDidLayoutSubviews.
You just need to remove it and your code will work perfectly fine.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// remove it
//collectionView.frame = view.bounds
}
I am trying to make custom Image Slider with collections view. I want to it to be reusable. So I made separate custom class where all collectionView stuff. and then call that class from UIViewController as shown in code below. And my UICollectonViewCell only contains imageView.
Problem is that in second cell. imageView is not being added and on third cell, it also flickers. I tried to debug these issues but could not.
ImageSlider class and UICollectionViewCell class at end end, with collection view stuff:
class ImageSlider: UIView {
var imgArr = [UIImage(named: "one.jpg"), UIImage(named: "3.jpg"), UIImage(named: "two.jpg"), UIImage(named: "4.jpg")]
var sliderCollection : UICollectionView = {
let widthRatio : Float = 16.0
let heightRatio : Float = 9.0
let collecionWidth = UIScreen.main.bounds.width - 30
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x: 15, y: 100, width: collecionWidth, height: CGFloat((Float(collecionWidth)*heightRatio)/widthRatio)), collectionViewLayout: layout)
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
collectionView.backgroundColor = .systemOrange
collectionView.isPagingEnabled = true
// collectionView.isScrollEnabled = true
collectionView.register(ImageSliderCell.self, forCellWithReuseIdentifier: "ImageSliderCell")
return collectionView
}()
}
extension ImageSlider: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgArr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageSliderCell", for: indexPath) as! ImageSliderCell
cell.imgView.image = imgArr[indexPath.item]
// cell.imgView.contentMode = .scaleAspectFit
print("cell frame : ", "(\(cell.frame.width), \(cell.frame.height)")
print("imgView frame : ", "(\(cell.imgView.frame.width), \(cell.imgView.frame.height)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
// override func awakeFromNib() {
// self.addSubview(imgView)
// }
override init(frame: CGRect) {
super.init(frame: frame)
imgView.frame = frame
self.addSubview(imgView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is ViewController, where I am calling ImageSlider() class.
class ImageSliderVC: UIViewController, UICollectionViewDelegate {
let imageSlider = ImageSlider()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(imageSlider.sliderCollection)
imageSlider.sliderCollection.delegate = imageSlider
imageSlider.sliderCollection.dataSource = imageSlider
imageSlider.sliderCollection.reloadData()
}
}
It looks like it does not work without constrains because UICollectionViewCell could be created with zero frame and it translated to imageView inside the cell. You need to add constrains to imageView to make it visible.
extension UIView {
func centerX(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imgView)
// not sure about the right size of image ...
imgView.setDimensions(height: 100.0, width: 100.0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I am trying to create a custom overlay for a UICollectionViewCell that when a user selects an image it puts a gray overlay with a number (ie. order) that the user selected the image in. When I run my code I do not get any errors but it also appears to do nothing. I added some print statements to help debug and when I run the code I get "Count :0" printed 15 times. That is the number of images I have in the library. When I select the first image in the first row I still get "Count: 0" as I would expect, but when I select the next image I get the print out that you see below. It appears that the count is not working but I am not sure why. What am I doing wrong? I can't figure out why the count is wrong, but my primary issue/concern I want to resolve is why the overlay wont display properly?
Print Statement
Cell selected: [0, 0]
Count :0
Count :0
Count :0
Cell selected: [0, 4]
Count :0
View Controller
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
cell.setupView()
print("Cell selected: \(indexPath)")
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
cell.backgroundColor = nil
cell.imageView.alpha = 1
}
}
Custom Overlay
lazy var circleView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.layer.cornerRadius = self.countSize.width / 2
view.alpha = 0.4
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var countLabel: UILabel = {
let label = UILabel()
let font = UIFont.preferredFont(forTextStyle: .headline)
label.font = UIFont.systemFont(ofSize: font.pointSize, weight: UIFont.Weight.bold)
label.textAlignment = .center
label.textColor = .white
label.adjustsFontSizeToFitWidth = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private func setup(){addSubview(circleView)
addSubview(circleView)
addSubview(countLabel)
NSLayoutConstraint.activate([
circleView.leadingAnchor.constraint(equalTo: leadingAnchor),
circleView.trailingAnchor.constraint(equalTo: trailingAnchor),
circleView.topAnchor.constraint(equalTo: topAnchor),
circleView.bottomAnchor.constraint(equalTo: bottomAnchor),
countLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
countLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
countLabel.topAnchor.constraint(equalTo: topAnchor),
countLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
TestCVCell: UICollectionViewCell
override var isSelected: Bool {
didSet { overlay.isHidden = !isSelected }
}
var imageView: UIImageView = {
let view = UIImageView()
view.clipsToBounds = true
view.contentMode = .scaleAspectFill
view.backgroundColor = UIColor.gray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var count: Int = 0 {
didSet { overlay.countLabel.text = "\(count)" }
}
let overlay: CustomAssetCellOverlay = {
let view = CustomAssetCellOverlay()
view.isHidden = true
return view
}()
func setupView() {
addSubview(imageView)
addSubview(overlay)
print("Count :\(count)")
NSLayoutConstraint.activate([
overlay.topAnchor.constraint(equalTo: imageView.topAnchor),
overlay.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
overlay.leftAnchor.constraint(equalTo: imageView.leftAnchor),
overlay.rightAnchor.constraint(equalTo: imageView.rightAnchor),
])
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = self.bounds
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
fatalError("init(coder:) has not been implemented")
}
Based on your other question, I'm guessing you are trying to do something like this...
Display images from device Photos, and allow multiple selections in order:
and, when you de-select a cell - for example, de-selecting my 2nd selection - you want to re-number the remaining selections:
To accomplish this, you need to keep track of the cell selections in an array - as they are made - so you can maintain the numbering.
Couple ways to approach this... here is one.
First, I'd suggest re-naming your count property to index, and, when setting the value, show or hide the overlay:
var index: Int = 0 {
didSet {
overlay.countLabel.text = "\(index)"
// hide if count is Zero, show if not
overlay.isHidden = index == 0
}
}
When you dequeue a cell from cellForItemAt, see if the indexPath is in our "tracking" array and set the cell's .index property appropriately (which will also show/hide the overlay).
Next, when you select a cell:
add the indexPath to our tracking array
we can set the .index property - with the count of our tracking array - directly to update the cell's appearance, because it won't affect any other cells
When you de-select a cell, we have to do additional work:
remove the indexPath from our tracking array
reload the cells so they are re-numbered
Here is a complete example - lots of comments in the code.
CircleView
class CircleView: UIView {
// simple view subclass that keeps itself "round"
// (assuming it has a 1:1 ratio)
override func layoutSubviews() {
layer.cornerRadius = bounds.width * 0.5
}
}
CustomAssetCellOverlay
class CustomAssetCellOverlay: UIView {
lazy var circleView: CircleView = {
let view = CircleView()
view.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var countLabel: UILabel = {
let label = UILabel()
let font = UIFont.preferredFont(forTextStyle: .headline)
label.font = UIFont.systemFont(ofSize: font.pointSize, weight: UIFont.Weight.bold)
label.textAlignment = .center
label.textColor = .white
label.adjustsFontSizeToFitWidth = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private func setup(){addSubview(circleView)
addSubview(circleView)
addSubview(countLabel)
NSLayoutConstraint.activate([
// circle view at top-left
circleView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4.0),
circleView.topAnchor.constraint(equalTo: topAnchor, constant: 4.0),
// circle view Width: 28 Height: 1:1 ratio
circleView.widthAnchor.constraint(equalToConstant: 28.0),
circleView.heightAnchor.constraint(equalTo: circleView.widthAnchor),
// count label constrained ot circle view
countLabel.leadingAnchor.constraint(equalTo: circleView.leadingAnchor),
countLabel.trailingAnchor.constraint(equalTo: circleView.trailingAnchor),
countLabel.topAnchor.constraint(equalTo: circleView.topAnchor),
countLabel.bottomAnchor.constraint(equalTo: circleView.bottomAnchor),
])
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
}
TestCVCell
class TestCVCell: UICollectionViewCell {
var imageView = UIImageView()
var index: Int = 0 {
didSet {
overlay.countLabel.text = "\(index)"
// hide if count is Zero, show if not
overlay.isHidden = index == 0
}
}
let overlay: CustomAssetCellOverlay = {
let view = CustomAssetCellOverlay()
view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
view.isHidden = true
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
contentView.addSubview(imageView)
contentView.addSubview(overlay)
imageView.translatesAutoresizingMaskIntoConstraints = false
overlay.translatesAutoresizingMaskIntoConstraints = false
// constrain both image view and overlay to full contentView
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
overlay.topAnchor.constraint(equalTo: imageView.topAnchor),
overlay.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
overlay.leadingAnchor.constraint(equalTo: imageView.leadingAnchor),
overlay.trailingAnchor.constraint(equalTo: imageView.trailingAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
TrackSelectionsViewController
class TrackSelectionsViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UINavigationControllerDelegate {
var myCollectionView: UICollectionView!
// array to track selected cells in the order they are selected
var selectedCells: [IndexPath] = []
// to load assests when needed
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
// will be used to get photos data
var fetchResult: PHFetchResult<PHAsset>!
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)
// request Options to be used in cellForItemAt
requestOptions.isSynchronous = false
requestOptions.deliveryMode = .opportunistic
// vertical stack view for the full screen (safe area)
let mainStack = UIStackView()
mainStack.axis = .vertical
mainStack.spacing = 0
mainStack.translatesAutoresizingMaskIntoConstraints = false
// add it to the view
view.addSubview(mainStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: g.topAnchor, constant:0.0),
mainStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
mainStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
mainStack.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
// create a label
let label = UILabel()
// add the label to the main stack view
mainStack.addArrangedSubview(label)
// label properties
label.textColor = .white
label.textAlignment = .center
label.text = "Select Photos"
label.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
// setup the collection view
setupCollection()
// add it to the main stack view
mainStack.addArrangedSubview(myCollectionView)
// start the async call to get the assets
grabPhotos()
}
func setupCollection() {
let layout = UICollectionViewFlowLayout()
myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
myCollectionView.delegate = self
myCollectionView.dataSource = self
myCollectionView.backgroundColor = UIColor.white
myCollectionView.allowsMultipleSelection = true
myCollectionView.register(TestCVCell.self, forCellWithReuseIdentifier: "cvCell")
}
//MARK: CollectionView
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
// add newly selected cell (index path) to our tracking array
selectedCells.append(indexPath)
// when selecting a cell,
// we can update the appearance of the newly selected cell
// directly, because it won't affect any other cells
cell.index = selectedCells.count
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// when de-selecting a cell,
// we can't update the appearance of the cell directly
// because if it's not the last cell selected, the other
// selected cells need to be re-numbered
// get the index of the deselected cell from our tracking array
guard let idx = selectedCells.firstIndex(of: indexPath) else { return }
// remove from our tracking array
selectedCells.remove(at: idx)
// reloadData() clears the collection view's selected cells, so
// get a copy of currently selected cells
let curSelected: [IndexPath] = collectionView.indexPathsForSelectedItems ?? []
// reload collection view
// we do this to update all cells' appearance,
// including re-numbering the currently selected cells
collectionView.reloadData()
// save current Y scroll offset
let saveY = collectionView.contentOffset.y
collectionView.performBatchUpdates({
// re-select previously selected cells
curSelected.forEach { pth in
collectionView.selectItem(at: pth, animated: false, scrollPosition: .centeredVertically)
}
}, completion: { _ in
// reset Y offset
collectionView.contentOffset.y = saveY
})
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard fetchResult != nil else { return 0 }
return fetchResult.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cvCell", for: indexPath) as! TestCVCell
imgManager.requestImage(for: fetchResult.object(at: indexPath.item) as PHAsset, targetSize: CGSize(width:120, height: 120),contentMode: .aspectFill, options: requestOptions, resultHandler: { (image, error) in
cell.imageView.image = image
})
// get the index of this indexPath from our tracking array
// if it's not there (nil), set it to -1
let idx = selectedCells.firstIndex(of: indexPath) ?? -1
// set .count property to index + 1 (arrays are zero-based)
cell.index = idx + 1
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 photos
func grabPhotos(){
DispatchQueue.global(qos: .background).async {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)]
self.fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
if self.fetchResult.count == 0 {
print("No photos found.")
}
DispatchQueue.main.async {
self.myCollectionView.reloadData()
}
}
}
}
Note: This is example code only!!! It should not be considered "production ready."
Shouldn't your var count: Int = 0 be set at your CollectionView delegate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
cell.setupView()
cell.count = indexPath.item
print("Cell selected: \(indexPath)")
}
}
I'm trying to pass an image from a view controller to a UICollectionViewCell whenever I segue to the UICollectionViewController. Normally, I would just use the following code to pass the variable directly to the UICollectionViewController.
let myCollectionViewController = MyCollectionViewController(collectionViewLayout: UICollectionViewFlowLayout())
myCollectionViewController.selectedImage = myImageView?.image
navigationController?.pushViewController(myCollectionViewController, animated: true)
However, I have subclassed my UICollectionViewCell and have set up the cell as follows:
import UIKit
class myCollectionViewCell: UICollectionViewCell {
let imageView:UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .red
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
return iv
}()
var selectedImage: UIImage? {
didSet {
self.imageView.image = selectedImage
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
How do I directly pass my image directly to my UICollectionViewCell subclass during the segue?
Hope this helps you-:
import Foundation
class HomeController: UIViewController{
// STORED VARIABLE FOR CollectionView
lazy var CollectionView : UICollectionView = {
var layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
var collectionViews = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionViews.translatesAutoresizingMaskIntoConstraints = false
collectionViews.backgroundColor = UIColor.white
collectionViews.showsHorizontalScrollIndicator = false
collectionViews.showsVerticalScrollIndicator = false
collectionViews.dataSource = self
collectionViews.delegate = self
return collectionViews
}()
// APPLY CONSTRAINTS FOR CollectionView
func setUpCollectionView(){
view.addSubview(CollectionView)
CollectionView.register(HomeControllerCell.self, forCellWithReuseIdentifier: "cell")
CollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
CollectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
CollectionView.topAnchor.constraint(equalTo: view.topAnchor,constant:92).isActive = true
CollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant:-50).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
setUpCollectionView()
}
}
// EXTENSION FOR COLLECTION VIEW PARENT
extension HomeController:UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout{
// NUMBER OF SECTION IN TABLE
public func numberOfSections(in collectionView: UICollectionView) -> Int{
return 1
}
// NUMBER OF ROWS IN PARENT SECTION
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return 5
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let Cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeControllerCell
// PASS IMAGE (whatever you have) TO COMPUTED VARIABLE image
Cell.image = pass image here
return Cell
}
// SIZE FOR PARENT CELL COLLECTION VIEW
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
return CGSize(width: view.frame.width, height: 220)
}
}
CollectionViewCellClass-:
class HomeControllerCell: UICollectionViewCell {
//INITIALIZER
override init(frame: CGRect) {
super.init(frame: frame)
setUpview()
}
// STORED VARIABLE imageVIEW
let imageView:UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .red
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.clipsToBounds = true
return iv
}()
// COMPUTED VARIABLE image
var image: UIImage? {
didSet {
self.imageView.image = image
}
}
// APPLY CONSTRAINTS FOR CELL VIEWS
func setUpview(){
// ADD imageView AS SUBVIEW
addSubview(imageView)
// APPLY CONSTRAINT ON IMAGE VIEW
imageView.leftAnchor.constraint(equalTo: self.leftAnchor,constant:5).isActive = true
//menuHeader.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: self.topAnchor,constant:12).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
}
// REQUIRED INIT
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have a tableViewCell with a collectionView, collectionView's cells are custom ones, they contains just a imageView.
Here is my test project
Here are DataSource required methods from my CollectionView class:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
let image = UIImage(named: listItems[indexPath.row])
cell.testImageView.image = image
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listItems.count
}
When I try to set image for cell's imageView I get this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have checked image, it isn't nil, but testImageView is, I get this error when I try to set image to collectionViewCell's testImageView.
How can I fix it?
EDIT1
Here is method called from tableViewController to fill collectionView's listItem
func load(listItem: [String]) {
self.listItems = listItem
reloadData()
}
Also if I remove code from collectionView cellForItemAt indexPath with this one all is working fine
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath)
let imageView = UIImageView(image:UIImage(named: listItems[indexPath.row]))
cell.backgroundView = imageView
You have mistaken your two view controllers. Your IB outlet is connected to a cell in a different view controller. I mean you can have multiple views in different controllers connected to a same IBOutlet, but in your case the one that loads first is not connected, so that is why it crashes.
This is the cell your outlet was connected to.
This is that you are trying to load (but did not connect IBOutlet to image view):
Just in case you want to use code instead..
import UIKit
class ImageCell : UICollectionViewCell {
private var imageView: UIImageView!
private var descLabel: UILabel!
public var image: UIImage? {
get {
return self.imageView.image
}
set {
self.imageView.image = newValue
}
}
public var imageDesc: String? {
get {
return self.descLabel.text
}
set {
self.descLabel.text = newValue
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initControls()
self.setTheme()
self.doLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initControls()
self.setTheme()
self.doLayout()
}
override func awakeFromNib() {
super.awakeFromNib()
}
func initControls() {
self.imageView = UIImageView()
self.descLabel = UILabel()
}
func setTheme() {
self.imageView.contentMode = .scaleAspectFit
self.descLabel.numberOfLines = 1
self.descLabel.lineBreakMode = .byWordWrapping
self.descLabel.textAlignment = .center
self.descLabel.textColor = UIColor.black
self.contentView.backgroundColor = UIColor.white
}
func doLayout() {
self.contentView.addSubview(self.imageView)
self.contentView.addSubview(self.descLabel)
self.imageView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 5).isActive = true
self.imageView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -5).isActive = true
self.imageView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 0).isActive = true
self.descLabel.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 5).isActive = true
self.descLabel.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -5).isActive = true
self.descLabel.topAnchor.constraint(equalTo: self.imageView.bottomAnchor, constant: 5).isActive = true
self.descLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -5).isActive = true
for view in self.contentView.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
}
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView!
private var dataSource: Array<String>!
override func viewDidLoad() {
super.viewDidLoad()
self.initDataSource()
self.initControls()
self.setTheme()
self.registerClasses()
self.doLayout()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initDataSource() {
self.dataSource = ["Image1", "Image2", "Image3", "Image4", "Image5", "Image6"]
}
func initControls() {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 117, height: 125)
layout.invalidateLayout()
self.collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
func setTheme() {
self.collectionView.backgroundColor = UIColor.clear
self.edgesForExtendedLayout = UIRectEdge(rawValue: 0)
self.view.backgroundColor = UIColor.blue
}
func registerClasses() {
self.collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCellIdentifier")
}
func doLayout() {
self.view.addSubview(self.collectionView)
self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
for view in self.view.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCellIdentifier", for: indexPath) as! ImageCell
let imageName = self.dataSource[indexPath.row]
cell.image = UIImage(named: imageName)
cell.imageDesc = imageName
return cell
}
}
http://imgur.com/o7O7Plw
maybe the "testImageView" outlet variable is not connected from the interface builder or there is no CollectionViewCell with reuseIdentifier "ImageCell". Check whether cell is nil or not using LLDB po command.