Swift 3: Be able to tap view behind a collectionViewCell? - swift

I am working on a new feature in my app where I want a user to have an image and then be able to swipe through a collectionView on top of that image, adding "filters". The issue that I have is that I want the user to still be able to interact with the image, but it is behind the collectionView. If I set the collectionViews inUsersInteractionEnabled to false, of course I then cannot use the collectionView at all. I am going for a similar functionality to Snapchats "scroll through filters" feature. Any help will be highly appreciated. Thank you guys. Code is below:
import UIKit
class FirstCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class SecondCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let cellView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.alpha = 0.25
return view
}()
func setupViews() {
backgroundColor = .clear
addSubview(cellView)
cellView.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class SecondController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.dataSource = self
cv.delegate = self
cv.translatesAutoresizingMaskIntoConstraints = false
cv.isPagingEnabled = true
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(FirstCell.self, forCellWithReuseIdentifier: "firstCell")
collectionView.register(SecondCell.self, forCellWithReuseIdentifier: "secondCell")
view.backgroundColor = .white
setupViews()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCell", for: indexPath) as! FirstCell
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath) as! SecondCell
return cell
}
}
lazy var imageView: UIImageView = {
let iv = UIImageView()
let image = UIImage(named: "img")
iv.image = image
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.isUserInteractionEnabled = true
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
return iv
}()
func handleTap() {
print(123)
}
func setupViews() {
view.addSubview(imageView)
view.addSubview(collectionView)
imageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
imageView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
}
}

There is a hitTest(_::) method available to override in any of the UIView subclass, which you can implement in your custom UICollectionView subclass and forward taps from it to any other view (the image behind collection view in your case).
override func hitTest(_ point: CGPoint,
with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
if self.parentViewController.forwardTapToImageIfNeeded(point) {
return nil
}
return hitView
}
Where parentViewController is a reference to your SecondController instance, which should have forwardTapToImageIfNeeded(_) function to check if user's tap is on top of the image frame, and if it is — automatically pass tap event to it.
func forwardTapToImageIfNeeded(_ p: CGPoint) -> Bool {
/* Convert point to local coordinates */
let pointInSuperView = self.convert(p, from: collectionView)
/* Check if tap is on top of the image frame */
if imageBehindCollection.frame.contains(pointInSuperView) {
// Forward tap to the image `imageBehindCollection`
return true
}
return false
}
Note: I didn't test this particular code by myself, so it might need a few fixes in Xcode.

Related

Adding Leading and Trailing into swift image view

I'm want to add the leading and trailing into Imageview . The Imageview properties pass from collection view cell. I have tried following to add the leading and trailing but still the leading and trilling not added into Imageview .
//imageView.leftAnchor.constraint(equalTo: view.leftAnchor , constant: 10),
//imageView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: 10),
imageView.leadingAnchor.constraint(equalToSystemSpacingAfter: view.leadingAnchor, multiplier: 3),
imageView.trailingAnchor.constraint(equalToSystemSpacingAfter: view.trailingAnchor, multiplier: 10),
Here is the view controller with collection view cell:
class PhotoViewController: UIViewController {
var viewModel : PhotoViewModel
init(viewModel : PhotoViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
private let collectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.backgroundColor = .white
return collectionView
}()
private enum LayoutConstant {
static let spacing: CGFloat = 16.0
static let itemHeight: CGFloat = 230.0
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
return activityIndicator
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupLayouts()
viewModel.fecthPhotoRecord()
collectionView.delegate = self
collectionView.dataSource = self
viewModel.delegate = self
view.backgroundColor = .lightGray
collectionView.reloadData()
}
private func setupViews() {
view.backgroundColor = .white
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: PhotoCollectionViewCell.identifier)
}
private func setupLayouts() {
collectionView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `collectionView`
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
}
}
extension PhotoViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.hits.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCollectionViewCell.identifier, for: indexPath) as! PhotoCollectionViewCell
let row = indexPath.row
let hits = viewModel.hits[indexPath.row]
cell.configureCell(tagName: hits.tags)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let detailVC = PhtotoDetailsViewController()
detailVC.rowSelected = indexPath.row
let navController = UINavigationController(rootViewController: detailVC)
detailVC.photoviewModel = viewModel
// navController.modalPresentationStyle = .fullScreen
present(navController, animated: true, completion: nil)
}
}
extension PhotoViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = itemWidth(for: view.frame.width, spacing: LayoutConstant.spacing)
return CGSize(width: width, height: LayoutConstant.itemHeight)
}
func itemWidth(for width: CGFloat, spacing: CGFloat) -> CGFloat {
let itemsInRow: CGFloat = 2
let totalSpacing: CGFloat = 2 * spacing + (itemsInRow - 1) * spacing
let finalWidth = (width - totalSpacing) / itemsInRow
return floor(finalWidth)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: LayoutConstant.spacing, left: LayoutConstant.spacing, bottom: LayoutConstant.spacing, right: LayoutConstant.spacing)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
}
Code in collection view cell .
import UIKit
protocol ReusableView: AnyObject {
static var identifier: String { get }
}
class PhotoCollectionViewCell: UICollectionViewCell {
private enum Constants {
// MARK: contentView layout constants
static let contentViewCornerRadius: CGFloat = 4.0
// MARK: profileImageView layout constants
static let imageHeight: CGFloat = 180.0
// MARK: Generic layout constants
static let verticalSpacing: CGFloat = 8.0
static let horizontalPadding: CGFloat = 16.0
static let profileDescriptionVerticalPadding: CGFloat = 8.0
}
private let imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFill
return imageView
}()
private let tagLabel: UILabel = {
let label = UILabel(frame: .zero)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
override init(frame: CGRect) {
super.init(frame: .zero)
setupViews()
setupLayouts()
}
private func setupViews() {
contentView.clipsToBounds = true
contentView.layer.cornerRadius = Constants.contentViewCornerRadius
contentView.backgroundColor = .white
contentView.addSubview(imageView)
contentView.addSubview(tagLabel)
}
private func setupLayouts() {
imageView.translatesAutoresizingMaskIntoConstraints = false
tagLabel.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `imageView`
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.heightAnchor.constraint(equalToConstant: Constants.imageHeight)
])
// Layout constraints for `tagLabel`
NSLayoutConstraint.activate([
tagLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.horizontalPadding),
tagLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Constants.horizontalPadding),
tagLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: Constants.profileDescriptionVerticalPadding)
])
}
func configureCell(tagName: String) {
tagLabel.text = tagName
}
func configureImageCell(row: Int, viewModel: PhotoViewModel) {
imageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.imageView.image = image
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension PhotoCollectionViewCell: ReusableView {
static var identifier: String {
return String(describing: self)
}
}
Here is the details view controller:
import UIKit
class PhtotoDetailsViewController: UIViewController {
var photoviewModel : PhotoViewModel?
var peopleDetailsViewModel:PeopleDetailsViewModel?
private let imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let tagLabel: UILabel = {
let label = UILabel(frame: .zero)
label.textAlignment = .center
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(imageView)
view.addSubview(tagLabel)
setUpUI()
setPhoto()
setContrain()
}
private func setContrain(){
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: view.topAnchor,constant: 100),
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: -200),
//imageView.leftAnchor.constraint(equalTo: view.leftAnchor , constant: 10),
//imageView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: 10),
imageView.leadingAnchor.constraint(equalToSystemSpacingAfter: view.leadingAnchor, multiplier: 3),
imageView.trailingAnchor.constraint(equalToSystemSpacingAfter: view.trailingAnchor, multiplier: 10),
tagLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor),
tagLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tagLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
private func setUpUI(){
tagLabel.text = photoviewModel?.hits.first?.tags
}
var rowSelected = 0
private func setPhoto(){
photoviewModel?.downloadImage(row: rowSelected) { [weak self] data in
DispatchQueue.main.async {
let image = UIImage(data: data)
self?.imageView.image = image
}
}
}
}
Debugging results in
Here is the screenshot:
Leading added .

Unable to simultaneously satisfy constraints. Adaptive cell height using UICollectionViewFlowLayout

I'm trying to make a news feed like app using UICollectionView with adaptive cell height. I found this tutorial and used the option "2. Solution for iOS 11+" for my code, which works perfectly fine. However, whenever I try to add more subviews to the cell and layout them as required, I get this error: "Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want."
Below is my code which gives me an error.
NewsFeedViewController:
import UIKit
class NewsFeedViewController: UIViewController {
var friends: [Friend] = []
var allPosts: [Post?] = []
var shuffledPosts: [Post?] = []
var collectionView: UICollectionView = {
let layout = NewsFeedFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize // new
layout.scrollDirection = .vertical
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = UIColor.systemRed
collection.isScrollEnabled = true
collection.contentInsetAdjustmentBehavior = .always
collection.translatesAutoresizingMaskIntoConstraints = false
return collection
}()
let cellID = "NewsFeedCollectionViewCell"
override func viewDidLoad() {
super.viewDidLoad()
getPosts()
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(NewsFeedCollectionViewCell.self, forCellWithReuseIdentifier: cellID)
collectionView.pin(to: view)
}
func getPosts() {
friends = FriendFactory().friends
allPosts = friends.flatMap{$0.posts}
var tempPosts = allPosts
for _ in allPosts {
if let index = tempPosts.indices.randomElement() {
let post = tempPosts[index]
shuffledPosts.append(post)
tempPosts.remove(at: index)
}
}
}
}
extension NewsFeedViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return shuffledPosts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! NewsFeedCollectionViewCell
let post = shuffledPosts[indexPath.row]
let friend = friends.first(where: {$0.identifier == post?.authorID})
let text = post?.postText
cell.configurePostText(postText: text!)
return cell
}
}
NewsFeedCollectionViewCell:
import UIKit
class NewsFeedCollectionViewCell: UICollectionViewCell {
var selectedPost = Post()
var likeBarView: LikeBarView = {
let view = LikeBarView()
view.backgroundColor = .systemIndigo
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var postTextLabel: UILabel = {
let label = UILabel()
label.font = label.font.withSize(20)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.backgroundColor = .systemYellow
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addViews() {
addSubview(likeBarView)
addSubview(postTextLabel)
}
func configurePostText(postText: String) {
postTextLabel.text = postText
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
layoutIfNeeded()
layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
func setupConstraints() {
NSLayoutConstraint.activate([
likeBarView.topAnchor.constraint(equalTo: topAnchor, constant: 20),
likeBarView.leadingAnchor.constraint(equalTo: leadingAnchor),
likeBarView.trailingAnchor.constraint(equalTo: trailingAnchor),
likeBarView.heightAnchor.constraint(equalToConstant: 100),
postTextLabel.topAnchor.constraint(equalTo: likeBarView.bottomAnchor, constant: 20),
postTextLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
postTextLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
postTextLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
}
}
NewsFeedFlowLayout:
import UIKit
final class NewsFeedFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
layoutAttributesObjects?.forEach({ layoutAttributes in
if layoutAttributes.representedElementCategory == .cell {
if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else {
fatalError()
}
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}
layoutAttributes.frame.origin.x = sectionInset.left
layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
return layoutAttributes
}
}
I enclose the screenshot of the collection view I'm getting and the screenshot of the analysis of the error done here, which is saying that the height of the cell is not dynamic if I'm getting it right.
What am I doing wrong?
Adding this line of code to NewsFeedViewController silenced the error:
layout.estimatedItemSize = CGSize(width: 375, height: 200)

Swift Add different actions to cells on a UICollectionView

I don't know where to start, I have a UICollectionView with multiple cells filled with a UIImage. I want each cell/UIImage to do a different action on a fingertap. Can someone pinpoint me in the right direction here?
The action that I have is a #IBAction from a UIButton, I know want have that action on a cell in the UICollectionView..
( I Guess I have to do something with the 'let cell = countryCollectionView.dequeueReusableCell' ?
import UIKit
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
#IBOutlet weak var soundsCollectionView: UICollectionView!
lazy var cv: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
var cc = UICollectionView(frame: .zero, collectionViewLayout: layout)
cc.translatesAutoresizingMaskIntoConstraints = false
cc.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
cc.delegate = self
cc.dataSource = self
cc.backgroundColor = .white
return cc
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(cv)
cv.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
cv.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
cv.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
cv.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.tag = indexPath.row
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 100)
}
}
class CustomCell: UICollectionViewCell {
lazy var centerImageView: UIImageView = {
var img = UIImageView()
img.translatesAutoresizingMaskIntoConstraints = false
img.image = UIImage(named: "1")
img.clipsToBounds = true
img.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handler(_:)))
tapGesture.numberOfTapsRequired = 1
img.addGestureRecognizer(tapGesture)
return img
}()
#objc private func handler(_ sender: UITapGestureRecognizer) {
print("tapped tag > ", self.tag)
}
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(centerImageView)
centerImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
centerImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
centerImageView.heightAnchor.constraint(equalToConstant: 80).isActive = true
centerImageView.widthAnchor.constraint(equalToConstant: 80).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I edit as new programmatically example for your problem solution.
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
lazy var cv: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
var cc = UICollectionView(frame: .zero, collectionViewLayout: layout)
cc.translatesAutoresizingMaskIntoConstraints = false
cc.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
cc.delegate = self
cc.dataSource = self
cc.backgroundColor = .white
return cc
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(cv)
cv.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
cv.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
cv.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
cv.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.tag = indexPath.row
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 100)
}
}
class CustomCell: UICollectionViewCell {
lazy var centerImageView: UIImageView = {
var img = UIImageView()
img.translatesAutoresizingMaskIntoConstraints = false
img.image = UIImage(named: "1")
img.clipsToBounds = true
img.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handler(_:)))
tapGesture.numberOfTapsRequired = 1
img.addGestureRecognizer(tapGesture)
return img
}()
#objc private func handler(_ sender: UITapGestureRecognizer) {
print("tapped tag > ", self.tag)
}
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(centerImageView)
centerImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
centerImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
centerImageView.heightAnchor.constraint(equalToConstant: 80).isActive = true
centerImageView.widthAnchor.constraint(equalToConstant: 80).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In this scenario, I would avoid using buttons on your cells. Instead you should hook into the didSelectItemAt delegate method and just add the necessary information into a data model:
struct Country {
let imageName: String
let sound: Sound // You didn't specify what type the sound1 object is but you get the gist
}
So your countries array will now contain this new struct instead of just raw Strings:
let countries = [
Country("country1", sound1),
Country("country2", sound2),
...
]
Then you can get the exact sound you want to play from the indexPath passed into didSelectItemAt:
let sound = self.countries[indexPath.row].sound
sound.play()
You'll also need to adjust how you're setting the cell's image in cellForItemAt:
let imageName = self.countries[indexPath.row].imageName
cell.countryImageView.image = UIImage(named: imageName)

Adding an Image to UICollection Header Swift

I'm looking to implement an image within the header of a collection view. This is the code that I have so far, but no header appears when I test. Am I missing something?
func collectionView(_ collectionView: UICollectionView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
let image = UIImageView()
image.frame = CGRect(x: collectionView.frame.width - 10 , y: 0, width: 20, height: 20)
image.image = UIImage.init(named: "trophyCase")
view.addSubview(image)
return view
}
UICollectionViewDelegate doesn't offer such a method as viewForHeaderInSection
Instead you can use viewForSupplementaryElementOfKind method of UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard kind == UICollectionView.elementKindSectionHeader else {
fatalError("Unrecognized element of kind: \(kind)")
}
let view: ReusableHeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: kind, for: indexPath) as! ReusableHeaderView
view.imageView.image = UIImage.init(named: "trophyCase")
view.imageView.frame = CGRect(x: collectionView.frame.width - 10 , y: 0, width: 20, height: 20)
return view
}
You are also required to register elementKindSectionHeader
collectionView.register(ReusableHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: UICollectionView.elementKindSectionHeader)
Following will be your ReusableHeaderView
class ReusableHeaderView: UICollectionReusableView {
let imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupUI()
}
private func setupUI() {
self.addSubview(imageView)
// Instead of settings imageView.frame, add following to use autolayout constraints
self.imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: self.topAnchor),
imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10.0),
imageView.widthAnchor.constraint(equalToConstant: 20.0),
imageView.heightAnchor.constraint(equalToConstant: 20.0)
])
}
}
Create a view subclassing UICollectionReusableView:
class HeaderView: UICollectionReusableView {
let imageView: UIImageView = {
let iv = UIImageView(image: /*put your image here*/)
iv.clipsToBounds = true
iv.contentMode = .scaleAspectFill
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
addSubview(imageView)
imageView.fillSuperview() // Check extension below
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then in your ViewController, first create a reuseIdentifier for your view:
fileprivate let headerId = "headerId"
After that register your custom view in collectionView(lets do it in viewDidLoad):
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerId)
}
Declare your custom view as optional in your vc:
var headerView: HeaderView?
Then override viewForSupplementaryElementOfKind method of the collectionView to initialize headerView:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as? HeaderView
return headerView!
}
Then implement another collectionView method to give your headerView a size:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 340) // edit width and height as you please.
}
Extension for fillSuperView used in initialization of custom view:
extension UIView {
func fillSuperview(withPadding padding: UIEdgeInsets = .zero) {
translatesAutoresizingMaskIntoConstraints = false
if let superview = superview {
topAnchor.constraint(equalTo: superview.topAnchor, constant: padding.top).isActive = true
leftAnchor.constraint(equalTo: superview.leftAnchor, constant: padding.left).isActive = true
rightAnchor.constraint(equalTo: superview.rightAnchor, constant: -padding.right).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -padding.bottom).isActive = true
}
}
}
Now it should work as header view for your collectionView.

collectionViewCells to collectionView position x:0, y:0

I am experiencing some layout issues with collectionViewCells. I am programmatically applying layout constraints and UICollectionViewCells are not being pinned to the 0,0 position in my collectionView. See attached screenshot for reference. Thanks in advance!
class CurrentUserPlaceDetailsVC: UIViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
}
let menuBar: MenuBar = {
let mb = MenuBar()
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|-64-[v0(150)]", views: menuBar)
}
var placesTableVC: PlacesTableVC?
}
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.green
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = .blue
return cell
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension UIView {
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
You need to set the minimumLineSpacing, headerReferenceSize, and/or sectionInset of the UICollectionViewFlowLayout instance you use to initialize your UICollectionView. You can also set the contentInset of the UICollectionView.
Try this:
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.headerReferenceSize = .zero
layout.sectionInset = .zero
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .green
cv.dataSource = self
cv.delegate = self
cv.contentInset = .zero
return cv
}()