Calling `reloadItems(at:)` causes program to crash - ios

I am trying to create a program which dynamically updates the collectoinView as the user enters text into the UITextField. Below is the extension I have created to achieve this
I have updated code based on your suggestions to include insert and delete statements,I am still in the process of refactoring and adding delegation but while the code doesn't result in an error it doesn't allow the user to keep typing and closes the keyboard have I implemented everything correctly
Update #2 (Thank you for all of your Help)
import UIKit
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate, UISearchBarDelegate {
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.delegate = self
cv.dataSource = self
return cv
}()
var genericTagsArray:[String] = ["tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za","tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za"]
var currentTagsArray:[String] = [String]() {
didSet {
collectionView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(collectionView)
collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
collectionView.register(Header.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header")
collectionView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: self.view.leadingAnchor, bottom: self.view.bottomAnchor, trailing: self.view.trailingAnchor, padding: .init(top: 30, left: 0, bottom: 30, right: 0))
collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return currentTagsArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
cell.label.text = currentTagsArray[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header", for: indexPath) as! Header
header.searchBar.delegate = self
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.contentSize.width, height: 75)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.frame.size.width, height: 50)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.currentTagsArray = self.genericTagsArray.filter { (text) -> Bool in
return text.contains(searchText.lowercased())
}
}
}
class Cell : UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.backgroundColor = .gray
self.addSubview(label)
label.anchor(top: self.topAnchor, leading: self.leadingAnchor, bottom: self.bottomAnchor, trailing: self.trailingAnchor)
}
}
class Header: UICollectionReusableView{
var searchBar = UISearchBar(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.backgroundColor = .gray
self.addSubview(searchBar)
searchBar.anchor(top: self.topAnchor, leading: self.leadingAnchor, bottom: nil, trailing: self.trailingAnchor, padding: .init(top: 0, left: 5, bottom: 0, right: 5), size: .init(width: 0, height: 50))
}
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing, constant: -padding.right).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
}
Updated Function With Edits
#objc func textFieldDidChange(){
guard(!(feedSearchBar.text?.isEmpty)!) else{
VC.currentTagsArray = VC.genericTagsArray
VC.feedScreenCollectionView.reloadData()
return
}
VC.currentTagsArray = VC.genericTagsArray.filter({letter -> Bool in
if feedSearchBar.text!.count > letter.count{
return false
}
let stringRange = letter.index(letter.startIndex, offsetBy: feedSearchBar.text!.count)
let subword = letter[..<stringRange]
return subword.lowercased().contains(feedSearchBar.text!.lowercased())
})
if VC.currentTagsArray.isEmpty{
VC.feedScreenCollectionView.deleteItems()
VC.currentTagsArray.insert(feedSearchBar.text!, at: 0)
VC.feedScreenCollectionView.insertItems(at: [IndexPath(item: 0, section: 0)])
//VC.feedScreenCollectionView.reloadItems(inSection: 0)
// VC.feedScreenCollectionView.reloadItems(at: [IndexPath(item: 0, section: 0)])
}
//VC.feedScreenCollectionView.reloadData()
VC.feedScreenCollectionView.reloadItems(inSection: 0)
}
extension UICollectionView{
func reloadItems(inSection section:Int = 0) {
var indicies:[IndexPath] = [IndexPath]()
print("current number of items to reload \(self.numberOfItems(inSection: section))")
for i in 0..<self.numberOfItems(inSection: section){
indicies.append(IndexPath(item: i, section: section))
print("current item number: \(i)")
}
self.reloadItems(at: indicies)
// self.insertItems(at: indicies)
}
func deleteItems(inSection section:Int = 0){
var indicies:[IndexPath] = [IndexPath]()
for i in 0..<self.numberOfItems(inSection: section){
indicies.append(IndexPath(item: i, section: section))
}
self.deleteItems(at: indicies)
}
}
Original Function:
extension UICollectionView{
func reloadItems(inSection section:Int = 0) {
var indicies:[IndexPath] = [IndexPath]()
for i in 0..<self.numberOfItems(inSection: section){
indicies.append(IndexPath(item: i, section: section))
}
self.reloadItems(at: indicies)
}
}
However when ever I call this function after the textField has been edited the program crashes with the error
* Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.54.4/UICollectionView.m:5867
2018-07-18 16:28:02.226606-0400 FeedScreenReuseableView[87752:9704142] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert item 6 into section 0, but there are only 6 items in section 0 after the update'
I am not sure how to fix this. Is there a better way to go about reloading cells that does not affect the header. Thank you for all of your help in advance. Below is the code from the project in its entirety.
import UIKit
class ViewController: UIViewController,UICollectionViewDelegateFlowLayout,UICollectionViewDelegate,UICollectionViewDataSource,printDelegateWorkedDelegate,updateCollectionView{
func updateCollectionView() {
self.feedScreenCollectionView.reloadData()
}
func printDelegateWorkedDelegate() {
print("The delegate worked")
}
var genericTagsArray:[String] = ["tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za","tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za"]
var currentTagsArray:[String] = [String]()
var tagsSelected:[String] = [String]()
let keyboardSlider = KeyboardSlider()
var header:feedViewHeader = feedViewHeader()
#IBOutlet weak var feedScreenCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
keyboardSlider.subscribeToKeyboardNotifications(view: view)
currentTagsArray = genericTagsArray
let viewTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(feedViewHeader.viewTapped(gestureRecognizer:)))
viewTapGestureRecognizer.cancelsTouchesInView = false
self.feedScreenCollectionView.addGestureRecognizer(viewTapGestureRecognizer)
feedScreenCollectionView.delegate = self
//
feedScreenCollectionView.dataSource = self
//
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 5, left: 1, bottom: 0, right: 1)
layout.minimumLineSpacing = 0
layout.headerReferenceSize = CGSize(width: 50, height: 75)
layout.sectionHeadersPinToVisibleBounds = true
feedScreenCollectionView.collectionViewLayout = layout
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: 50, height: 75)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "feedViewHeader", for: indexPath) as! feedViewHeader
header.VC = self
return header
}
//
//Data Source
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return currentTagsArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "feedViewCell", for: indexPath) as! feedViewCell
cell.feedImageView.backgroundColor = .blue
cell.feedImageView.clipsToBounds = true
cell.feedImageView.layer.cornerRadius = CGFloat((cell.feedImageView.frame.width)/5)
cell.feedLabel.text = currentTagsArray[indexPath.item]
return cell
}
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 {
let collectionViewWidth = collectionView.bounds.width/4.0
let collectionViewHeight = collectionViewWidth
return CGSize(width: collectionViewWidth-4, height: collectionViewHeight+25)
}
var lastContentOffset:CGFloat = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.lastContentOffset > self.feedScreenCollectionView.contentOffset.y && self.feedScreenCollectionView.contentOffset.y > 0 && self.feedScreenCollectionView.contentOffset.y < self.feedScreenCollectionView.frame.maxY {
self.lastContentOffset = scrollView.contentOffset.y
header.isHidden = false
}
else if (self.lastContentOffset < self.feedScreenCollectionView.contentOffset.y) && (self.feedScreenCollectionView.contentOffset.y < self.feedScreenCollectionView.frame.maxY) && (self.feedScreenCollectionView.contentOffset.y > 0) {
print("you scrolled down,content offSet: \(scrollView.contentOffset.y)->\(self.feedScreenCollectionView.contentOffset.y)")
header.isHidden = true
}
else{
self.lastContentOffset = scrollView.contentOffset.y
print("content offSet: \(scrollView.contentOffset.y)")
print("Nothing happened")
// self.headerDelegate?.hideHeaderView(hide: true)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardFrameChangeNotification(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
var offsetY:CGFloat = 0
#objc func keyboardFrameChangeNotification(notification: Notification) {
}
}
class feedViewCell:UICollectionViewCell{
#IBOutlet weak var feedImageView: UIImageView!
#IBOutlet weak var feedLabel: UILabel!
let keyboardSlider = KeyboardSlider()
override func awakeFromNib() {
super.awakeFromNib()
feedLabel.translatesAutoresizingMaskIntoConstraints = false
feedImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
feedImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
feedImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
feedImageView.bottomAnchor.constraint(equalTo: self.feedLabel.topAnchor).isActive = true
feedImageView.translatesAutoresizingMaskIntoConstraints = false
feedLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
feedLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
feedLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
feedLabel.topAnchor.constraint(equalTo: self.feedImageView.bottomAnchor).isActive = true
feedLabel.textAlignment = .center
}
}
class feedViewHeader:UICollectionReusableView,UITextFieldDelegate,UICollectionViewDelegate{
#IBOutlet weak var feedSearchBar: UITextField!
var delegateWorked:printDelegateWorkedDelegate?
var updateCV:updateCollectionView?
var VC:ViewController!
var collectionView:UICollectionView?
var stringToBeSet = "String to be set"
override func awakeFromNib() {
super.awakeFromNib()
feedSearchBar.delegate = self
feedSearchBar.autocorrectionType = .no
feedSearchBar.keyboardType = .default
feedSearchBar.addTarget(self, action: #selector(feedViewHeader.textFieldDidChange), for: .editingChanged)
self.feedSearchBar.borderStyle = .roundedRect
self.feedSearchBar.layer.borderColor = UIColor.black.cgColor
self.feedSearchBar.layer.borderWidth = 4
var searchBarHeight = self.feedSearchBar.bounds.height
self.feedSearchBar.placeholder = "Tap To Search"
self.feedSearchBar.returnKeyType = .search
self.feedSearchBar.rightViewMode = .always
}
#objc func viewTapped(gestureRecognizer:UIGestureRecognizer){
if feedSearchBar.isFirstResponder{
feedSearchBar.resignFirstResponder()
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
VC.feedScreenCollectionView.reloadData()
//VC.feedScreenCollectionView.reloadSections([0])
return true
}
/// Helper to dismiss keyboard
#objc func didStopEditing() {
}
func textFieldDidEndEditing(_ textField: UITextField) {
UIView.setAnimationCurve(UIViewAnimationCurve.easeInOut)
UIView.animate(withDuration: 0.2) {
self.VC.view.frame.origin.y = 0
}
}
#objc func textFieldDidChange(){
guard(!(feedSearchBar.text?.isEmpty)!) else{
VC.currentTagsArray = VC.genericTagsArray
VC.feedScreenCollectionView.reloadData()
return
}
VC.currentTagsArray = VC.genericTagsArray.filter({letter -> Bool in
if feedSearchBar.text!.count > letter.count{
return false
}
let stringRange = letter.index(letter.startIndex, offsetBy: feedSearchBar.text!.count)
let subword = letter[..<stringRange]
return subword.lowercased().contains(feedSearchBar.text!.lowercased())
})
if VC.currentTagsArray.isEmpty{
VC.currentTagsArray.insert(feedSearchBar.text!, at: 0)
}
VC.feedScreenCollectionView.reloadItems(inSection: 0)
}
}
extension Notification.Name{
static let showKeyboard = Notification.Name("showKeyboard")
}
class KeyboardSlider: NSObject {
// variables to hold and process information from the view using this class
weak var view: UIView?
#objc func keyboardWillShow(notification: NSNotification) {
// method to move keyboard up
// view?.frame.origin.y = 0 - getKeyboardHeight(notification as Notification)
print("made it to keyboard will show")
}
func getKeyboardHeight(_ notification:Notification) -> CGFloat {
// get exact height of keyboard on all devices and convert to float value to return for use
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue
return keyboardSize.cgRectValue.height
}
func subscribeToKeyboardNotifications(view: UIView) {
// assigning view to class' counterpart
self.view = view
// when UIKeyboardWillShow do keyboardWillShow function
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: .UIKeyboardWillShow, object: nil)
}
func unsubscribeFromKeyboardNotifications() {
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
}
}
class blankView:UICollectionReusableView{
}
extension UICollectionView{
func reloadItems(inSection section:Int = 0) {
print("made it to reload")
for i in 0..<self.numberOfItems(inSection: section){
self.reloadItems(at: [IndexPath(item: i, section: section)])
}
}
}

You are adding a new string to your VC.currentTagsArray but you aren't calling insertItems(at:) on your collection view, so when numberOfItemsInSection suddenly starts returning more items than it did previously you get the consistency exception.
A couple of other observations on style:
I would use delegation to pass events back to the view controller rather than having the header view and cells holding a reference to the VC and tightly coupling the objects.
the variable VC should be vc. Capital first letter is a class
Just call it currentTags not currentTagsArray

Your problem is because you are only calling insertItemsAt, which does exactly that, inserts an item. You are forgetting to deleteItemsAt, which should delete all the items that you don’t want to display any longer.
BTW you should really consider refactoring your code. It is not easy to read and things are not being done in the right place. For example, your header should never be the one in charge of updating your collection view. You should leave that to the controller itself and use delegation to get the text from the header up to the view controller.
Update
Here is an entire code that does the search for you. It is just an example so you can see how it can be done.
import UIKit
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate, UISearchBarDelegate {
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.delegate = self
cv.dataSource = self
return cv
}()
var genericTagsArray:[String] = ["tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za","tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za"]
var currentTagsArray:[String] = [String]() {
didSet {
collectionView.reloadSections(IndexSet.init(integer: 1))
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(collectionView)
collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
collectionView.register(Header.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header")
collectionView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: self.view.leadingAnchor, bottom: self.view.bottomAnchor, trailing: self.view.trailingAnchor, padding: .init(top: 0, left: 0, bottom: 0, right: 0))
collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 { return 0 }
return currentTagsArray.count
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header", for: indexPath) as! Header
header.searchBar.delegate = self
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return CGSize(width: self.view.frame.width, height: 50)
}
return CGSize()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
cell.label.text = currentTagsArray[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.frame.size.width, height: 50)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.currentTagsArray = self.genericTagsArray.filter { (text) -> Bool in
return text.contains(searchText.lowercased())
}
}
}
class Cell : UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.backgroundColor = .gray
self.addSubview(label)
label.anchor(top: self.topAnchor, leading: self.leadingAnchor, bottom: self.bottomAnchor, trailing: self.trailingAnchor)
}
}
class Header : UICollectionViewCell {
let searchBar = UISearchBar()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.backgroundColor = .gray
self.addSubview(searchBar)
searchBar.anchor(top: self.topAnchor, leading: self.leadingAnchor, bottom: self.bottomAnchor, trailing: self.trailingAnchor)
}
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing, constant: -padding.right).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
}

Related

UICollectionview how to avoide top bar by scrolling

When I start my App everything is fine.
As it should stay.
But as soon I start to scroll down a strange Bar at the Top appears.
Strange Bar by Scrolling down
I looked for some frame issue but I can't find one. I have no Idea what causes this behavior. I coded my UICollectionview programmatically.
Here is my Code:
import UIKit
// MARK: Erzeugung & Füllen des Daten STRUCT
struct Profile {
let name: String
let location: String
let imageName: String
let profession: String
}
var profiles: [Profile] = []
private func populateProfiles() {
profiles = [
Profile(name: "Thor", location: "Boston", imageName: "astronomy", profession: "astronomy"),
...
Profile(name: "Elon Musk", location: "San Francisco", imageName: "graduate", profession: "graduate")
]
}
class ViewController: UIViewController {
private let collectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
// collectionView.backgroundColor = .white
return collectionView
}()
// MARK: Hintergrundbild erzeugen
let imageView : UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named:"Backround-wood")
iv.contentMode = .scaleAspectFill
return iv
}()
private enum LayoutConstant {
static let spacing: CGFloat = 50.0 //Größe der Zwischenräume
static let itemHeight: CGFloat = 500.0 //Zellhöhe
}
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupLayouts()
populateProfiles() // finales Befüllen des STRUCT
collectionView.reloadData()
self.collectionView.backgroundView = imageView //Hintergrundbild einfügen
}
// MARK: Rotation - Resize-Cells & Transition
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(
alongsideTransition: { _ in self.collectionView.collectionViewLayout.invalidateLayout() },
completion: { _ in }
)
}
private func setupViews() {
view.backgroundColor = .white
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(ProfileCell.self, forCellWithReuseIdentifier: ProfileCell.identifier)
collectionView.contentInsetAdjustmentBehavior = .never // Cells starten weiter oben
}
// MARK: Constraints collectionView
private func setupLayouts() {
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor), //view.safeAreaLayoutGuide.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), //view.safeAreaLayoutGuide.bottomAnchor),
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
}
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return profiles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ProfileCell.identifier, for: indexPath) as! ProfileCell
let profile = profiles[indexPath.row]
cell.setup(with: profile)
cell.contentView.backgroundColor = .red
return cell
}
}
extension ViewController: 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
}
}
Can somebody please help?
This is probably a navigation bar that is initially invisible in iOS 15, but becomes visible when content is being scrolled underneath.
You can hide the navigation bar by adding this in viewDidLoad or viewDidAppear :
navigationController?.setNavigationBarHidden(true, animated: false)

UICollectionView with SwiftUI + Drag and drop reordering possible?

I'm implementing a UICollectionView inside my SwiftUI View (also with SwiftUI View cells).
So I have a Hosting+Representable Combo.
Now I want to reorder my cells through drag and drop, but nothing happens.
The idea is to use a longpressGesture together with the given functions canMoveItemAt and moveItemAt.
Here is the full code:
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
CollectionComponent()
}
}
struct CollectionComponent : UIViewRepresentable {
func makeCoordinator() -> CollectionComponent.Coordinator {
Coordinator(data: [])
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
var data: [String] = []
init(data: [String]) {
for index in (0...20) {
self.data.append("\(index)")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
cell.cellView.rootView = AnyView(CellView(text: data[indexPath.item]))
return cell
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
print("Changing the cell order, moving: \(sourceIndexPath.row) to \(destinationIndexPath.row)")
}
}
func makeUIView(context: Context) -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 150, height: 150)
layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .white
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
let longPressGesture = UILongPressGestureRecognizer(target: self, action: Selector(("handleLongGesture:")))
collectionView.addGestureRecognizer(longPressGesture)
func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case UIGestureRecognizerState.began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
break
}
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case UIGestureRecognizerState.changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case UIGestureRecognizerState.ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
return collectionView
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
}
}
class Cell: UICollectionViewCell {
public var cellView = UIHostingController(rootView: AnyView(EmptyView()))
public override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
contentView.addSubview(cellView.view)
cellView.view.preservesSuperviewLayoutMargins = false
cellView.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
cellView.view.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
cellView.view.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
cellView.view.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
cellView.view.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
])
}
}
struct CellView: View {
let text: String
var body: some View {
ZStack {
Text(text)
}
.frame(width: 150, height: 150)
.background(Color.blue)
}
}
Thanks!
Just use UICollectionViewDragDelegate and UICollectionViewDropDelegate to drag and drop cell views inside UICollectionView. It works perfectly. Here is the sample code...
struct ContentView: View {
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var body: some View {
GeometryReader { proxy in
GridView(self.numbers, proxy: proxy) { number in
Image("image\(number)")
.resizable()
.scaledToFill()
}
}
}
}
struct GridView<CellView: View>: UIViewRepresentable {
let cellView: (Int) -> CellView
let proxy: GeometryProxy
var numbers: [Int]
init(_ numbers: [Int], proxy: GeometryProxy, #ViewBuilder cellView: #escaping (Int) -> CellView) {
self.proxy = proxy
self.cellView = cellView
self.numbers = numbers
}
func makeUIView(context: Context) -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
let collectionView = UICollectionView(frame: UIScreen.main.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .white
collectionView.register(GridCellView.self, forCellWithReuseIdentifier: "CELL")
collectionView.dragDelegate = context.coordinator //to drag cell view
collectionView.dropDelegate = context.coordinator //to drop cell view
collectionView.dragInteractionEnabled = true
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.contentInset = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
return collectionView
}
func updateUIView(_ uiView: UICollectionView, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDragDelegate, UICollectionViewDropDelegate {
var parent: GridView
init(_ parent: GridView) {
self.parent = parent
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return parent.numbers.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CELL", for: indexPath) as! GridCellView
cell.backgroundColor = .clear
cell.cellView.rootView = AnyView(parent.cellView(parent.numbers[indexPath.row]).fixedSize())
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: ((parent.proxy.frame(in: .global).width - 8) / 3), height: ((parent.proxy.frame(in: .global).width - 8) / 3))
}
//Provides the initial set of items (if any) to drag.
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let item = self.parent.numbers[indexPath.row]
let itemProvider = NSItemProvider(object: String(item) as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
//Tells your delegate that the position of the dragged data over the collection view changed.
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
if collectionView.hasActiveDrag {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
return UICollectionViewDropProposal(operation: .forbidden)
}
//Tells your delegate to incorporate the drop data into the collection view.
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
var destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationIndexPath = indexPath
} else {
let row = collectionView.numberOfItems(inSection: 0)
destinationIndexPath = IndexPath(item: row - 1, section: 0)
}
if coordinator.proposal.operation == .move {
self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
}
}
private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
if let item = coordinator.items.first, let sourceIndexPath = item.sourceIndexPath {
collectionView.performBatchUpdates({
self.parent.numbers.remove(at: sourceIndexPath.item)
self.parent.numbers.insert(item.dragItem.localObject as! Int, at: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
}, completion: nil)
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
}
}
}
}
class GridCellView: UICollectionViewCell {
public var cellView = UIHostingController(rootView: AnyView(EmptyView()))
public override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
contentView.addSubview(cellView.view)
cellView.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
cellView.view.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 5),
cellView.view.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -5),
cellView.view.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
cellView.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
])
cellView.view.layer.masksToBounds = true
}
}
You can see the final result here https://media.giphy.com/media/UPuWLauQepwi5Q77PA/giphy.gif
Thanks. X_X

SwipeCellKit does not swipe cell

I want to implement UITableView's UISwipeActionsConfiguration for a UICollectionView. In order to do so, I am using SwipeCellKit - github
My UICollectionView adopts to the SwipeCollectionViewCellDelegate protocol. And the cell inherits from SwipeCollectionViewCell.
ViewController with UICollectionView
class SwipeViewController: UIViewController {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(SwipeableCollectionViewCell.self, forCellWithReuseIdentifier: SwipeableCollectionViewCell.identifier)
collectionView.showsVerticalScrollIndicator = false
collectionView.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 4, right: 0)
collectionView.backgroundColor = UIColor(white: 0.97, alpha: 1)
collectionView.dataSource = self
collectionView.delegate = self
return collectionView
}()
var items: [String] = {
var items = [String]()
for i in 1 ..< 20 {
items.append("Item \(i)")
}
return items
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.setConstraints(topAnchor: view.topAnchor,
leadingAnchor: view.leadingAnchor,
bottomAnchor: view.bottomAnchor,
trailingAnchor: view.trailingAnchor,
leadingConstant: 10,
trailingConstant: 10)
}
}
extension SwipeViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 80)
}
}
extension SwipeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SwipeableCollectionViewCell.identifier, for: indexPath) as! SwipeableCollectionViewCell
cell.backgroundColor = BackgroundColor.colors[indexPath.row]
cell.delegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
extension SwipeViewController: SwipeCollectionViewCellDelegate {
func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
}
return [deleteAction]
}
}
SwipeCollectionViewCell
class SwipeableCollectionViewCell: SwipeCollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(nameLabel)
nameLabel.setConstraints(topAnchor: self.topAnchor,
leadingAnchor: self.leadingAnchor,
bottomAnchor: self.bottomAnchor,
trailingAnchor: self.trailingAnchor)
self.backgroundColor = .white
}
static let identifier = "TaskListTableViewCell"
private let nameLabel: UILabel = {
let label = UILabel()
label.text = "Simulator user has requested new graphics quality"
label.numberOfLines = 0
return label
}()
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
After doing so, when I swipe the cell, the deleteAction overlaps with the content of the cell.
As you see in the screenshot, the cell's content overlaps with the deleteAction text.
Update
setConstraints sets views's translatesAutoresizingMaskIntoConstraints to false
You must add nameLabel to contentView.
Change
self.addSubview(nameLabel)
nameLabel.setConstraints(topAnchor: self.topAnchor,
leadingAnchor: self.leadingAnchor,
bottomAnchor: self.bottomAnchor,
trailingAnchor: self.trailingAnchor)
to
self.contentView.addSubview(nameLabel)
nameLabel.setConstraints(topAnchor: self.contentView.topAnchor,
leadingAnchor: self.contentView.leadingAnchor,
bottomAnchor: self.contentView.bottomAnchor,
trailingAnchor: self.contentView.trailingAnchor)
Result:

CollectionView Not Responding to Touch Events

I have a UICollectionView inside of a UIViewController declared as follows:
class CollectionViewController : UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate {
private var tables = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
var collectionView : UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = UIColor.clear
collectionView.register(TableBookingCell.self, forCellWithReuseIdentifier: "TableBookingCell")
collectionView.isUserInteractionEnabled = true
self.view.isUserInteractionEnabled = true
self.view.addSubview(collectionView)
collectionView.snp.makeConstraints {
make in
make.edges.equalTo(view)
}
}
//MARK: - CollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tables.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.frame.width, height: 80)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TableBookingCell", for: indexPath) as! TableBookingCell
cell.isUserInteractionEnabled = true
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row < self.tables.count {
print("logging click")
}
}
}
I am next embedding this CollectionViewController inside of another UIViewController and adding it's view to sit inside of a UIView nested in a ScrollView as shown below:
class HomeController: UIViewController, UIScrollViewDelegate {
private let scrollView = UIScrollView()
private let contentContainer = UIView()
private let collectionViewController = CollectionViewController()
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentInsetAdjustmentBehavior = .never
scrollView.delegate = self
contentContainer.backgroundColor = .clear
view.addSubview(scrollView)
scrollView.addSubview(contentContainer)
self.addChild(collectionViewController)
contentContainer.addSubview(collectionViewController.view)
collectionViewController.didMove(toParent: self)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.scrollView.contentSize = CGSize(width: self.scrollView.contentSize.width , height: self.collectionViewController.collectionView.contentSize.height + self.scrollView.contentSize.height)
}
scrollView.snp.makeConstraints {
make in
make.edges.equalTo(view)
}
imageContainer.snp.makeConstraints {
make in
make.top.equalTo(scrollView)
make.left.right.equalTo(view)
make.height.equalTo(imageContainer.snp.width).multipliedBy(0.7)
}
infoContainer.snp.makeConstraints {
make in
make.top.equalTo(imageView.snp.bottom).offset(-70)
make.bottom.equalTo(imageView).offset(-10)
make.left.equalTo(imageView).offset(10)
make.right.equalTo(imageView).offset(-50)
}
imageView.snp.makeConstraints {
make in
make.left.right.equalTo(imageContainer)
//** Note the priorities
make.top.equalTo(view).priority(.high)
//** We add a height constraint too
make.height.greaterThanOrEqualTo(imageContainer.snp.height).priority(.required)
//** And keep the bottom constraint
make.bottom.equalTo(imageContainer.snp.bottom)
}
contentContainer.snp.makeConstraints {
make in
make.top.equalTo(imageContainer.snp.bottom)
make.left.right.equalTo(view)
make.bottom.equalTo(scrollView)
}
collectionViewController.view.snp.makeConstraints {
make in
// make.edges.equalTo(contentContainer).inset(14)
make.left.right.equalTo(view)
make.top.equalTo(contentContainer)
make.bottom.equalTo(view)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.scrollIndicatorInsets = view.safeAreaInsets
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: view.safeAreaInsets.bottom, right: 0)
}
//MARK: - Scroll View Delegate
private var previousStatusBarHidden = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if previousStatusBarHidden != shouldHideStatusBar {
UIView.animate(withDuration: 0.2, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
previousStatusBarHidden = shouldHideStatusBar
}
}
}
however the UICollectionView is not responding to any touchUpInside events.
It seems contentContainer is missing a height constraints. If adding the following code, you will see what I mean
contentContainer.clipsToBounds = true
In this case, you cannot see collectionView at all.
Add the height constraint and align the bottom of contentContainer and CollectionViewController can make everything look right.
contentContainer.heightAnchor.constraint(equalToConstant: 3000).isActive = true
collectionViewController.view.topAnchor.constraint(equalTo: contentContainer.topAnchor).isActive = true
collectionViewController.view.bottomAnchor.constraint(equalTo: contentContainer.bottomAnchor).isActive = true
collectionViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
collectionViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
It's not all the story but specifically answer why the collectionView won't respond to touch event. Hope you got it.

How can I make a cell pin to the top of a collectionView like a header?

I am trying to implement a searchBar inside of a collectionView cell, after being unable to get the search bar to work with reloading the cells as a header, I have implemented the searchBar inside of the first dequeued cell. How can I give this cell properties similar to a sticky Header where it stays on top of the screen as the user scrolls up and hides when the user scrolls down? Is this possible thank you?
Below is the code in question
import UIKit
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate, UISearchBarDelegate {
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.delegate = self
cv.dataSource = self
return cv
}()
var genericTagsArray:[String] = ["tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za","tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za"]
var currentTagsArray:[String] = [String]() {
didSet {
collectionView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(collectionView)
collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
currentTagsArray = genericTagsArray
collectionView.register(Header.self, forCellWithReuseIdentifier: "\(Header.self)")
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return currentTagsArray.count + 1
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: self.view.frame.width, height: 50)
}
var searchCell:Header!
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0 && indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(Header.self)", for: indexPath) as! Header
cell.searchBar.delegate = self
searchCell = cell
DispatchQueue.main.async {
if self.currentTagsArray.count != 0 {
self.searchCell?.searchBar.becomeFirstResponder()
}
}
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
cell.label.text = currentTagsArray[indexPath.item - 1]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.frame.size.width, height: 50)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.currentTagsArray = self.genericTagsArray.filter { (text) -> Bool in
return text.contains(searchText.lowercased())
}
}
var lastContentOffset:CGFloat = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.lastContentOffset > self.collectionView.contentOffset.y && self.collectionView.contentOffset.y > 0 && self.collectionView.contentOffset.y < collectionView.frame.maxY {
self.lastContentOffset = scrollView.contentOffset.y
searchCell.isHidden = false
}
else if (self.lastContentOffset < self.collectionView.contentOffset.y) && (self.collectionView.contentOffset.y < self.collectionView.frame.maxY) && (self.collectionView.contentOffset.y > 0) {
self.lastContentOffset = scrollView.contentOffset.y
searchCell.isHidden = true
}
else{
self.lastContentOffset = scrollView.contentOffset.y
print("content offSet: \(scrollView.contentOffset.y)")
print("Nothing happened")
// self.headerDelegate?.hideHeaderView(hide: true)
}
}
}
class Cell : UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.backgroundColor = .gray
self.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
}
}
class Header : UICollectionViewCell {
let searchBar = UISearchBar()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.backgroundColor = .gray
self.addSubview(searchBar)
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
searchBar.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
searchBar.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
searchBar.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
}
}

Resources