Detect last cell in full page UICell UICollectionView - ios

I have a checkout flow using a UICollectionViewController and a full page UICell. After the last cell(the last item) is displayed I want to add a summary page of all the items that have been added to the shopping cart along with a total price. I tried to check if the last item = itemsArray.count - 1 however it keeps printing "last item" in the cell before last. Also in the last cell, it prints "not last".
class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, PostCellDelegate {
var totalPrice = Float()
private var hiddenRows = Set<Int>()
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PostCell
cell.finalLabel.text = String(totalPrice)
cell.delegate = self
let item = itemsArr[indexPath.row]
cell.set(name: item.name, brand: item.brand, price: item.price)
if indexPath.row == itemsArr.count - 1 {
print("last item")
}else {
print("not last")
}
if hiddenRows.contains(indexPath.row) {
cell.myButton.isHidden = true
cell.removeButton.isHidden = false
}else{
cell.removeButton.isHidden = true
cell.myButton.isHidden = false
}
cell.finalLabel.text = String(totalPrice)
return cell
}
#objc func addTapped(cell: PostCell) {
guard let indexPath = self.collectionView.indexPath(for: cell) else {return}
hiddenRows.insert(indexPath.row)
cell.removeButton.isHidden = false
let item = itemsArr[indexPath.row]
print(item.price)
totalPrice += Float(item.price) ?? 0
cell.finalLabel.text = String(totalPrice)
}
#objc func removeButtonTapped(cell: PostCell) {
guard let indexPath = self.collectionView.indexPath(for: cell) else {return}
hiddenRows.insert(indexPath.row)
cell.myButton.isHidden = false
let item = itemsArr[indexPath.row]
totalPrice -= Float(item.price) ?? 0
cell.finalLabel.text = String(totalPrice)
}
}
protocol PostCellDelegate {
func removeButtonTapped(cell: PostCell)
func addTapped(cell: PostCell)
func didPressButton(_ tag: Int)
}
class PostCell: UICollectionViewCell {
var delegate: PostCellDelegate?
func set(name: String, brand: String, price: String){
nameLabel.text = name
brandLabel.text = brand
priceLabel.text = price
}
override init(frame: CGRect) {
super.init(frame: frame)
self.myButton.addTarget(self, action: #selector(addButtonTapped(sender:)), for: .touchUpInside)
self.removeButton.addTarget(self, action: #selector(subButtonTapped(sender:)), for: .touchUpInside)
setupCellConstraints()
}
#objc func buttonPressed(_ sender: UIButton) {
delegate?.didPressButton(sender.tag)
}
#objc func addButtonTapped(sender: UIButton){
self.delegate?.addTapped(cell: self)
sender.isHidden = true
}
#objc func subButtonTapped(sender: UIButton){
self.delegate?.removeButtonTapped(cell: self)
sender.isHidden = true
}
}

Related

Access correct indexPath Row for user

I'm creating a messaging app where users can select who do they want to chat with by a UITableView, the problem is that obviously there needs to be a way to search for an specific user, I had already implemented a UISearchController and I can find the user which I search for. The real problem starts when I select the user with override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) method, because when I select the user, it selects wrong user because of indexPath.row.
Here is some of my code:
NewMessageController:
import UIKit
import Firebase
import FirebaseDatabase
class NewMessageController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
var searchController = UISearchController()
var activityIndicator = UIActivityIndicatorView(style: .large)
var aiView = UIView()
let cellId = "cellId"
var users = [User]()
var filteredUsers = [User]()
override func viewDidLoad() {
super.viewDidLoad()
initSearchController()
setUpActivityIndicator()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(didTapCancelButton))
tableView.register(UserCell.self, forCellReuseIdentifier: cellId)
startAI()
fetchUser()
}
func initSearchController() {
searchController.loadViewIfNeeded()
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.enablesReturnKeyAutomatically = false
searchController.searchBar.returnKeyType = UIReturnKeyType.done
definesPresentationContext = true
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchController.searchBar.scopeButtonTitles = ["All"]
searchController.searchBar.delegate = self
}
func fetchUser() {
Database.database().reference().child("users").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User(dictionary: dictionary)
user.id = snapshot.key
// user.setValuesForKeys(dictionary)
self.users.append(user)
DispatchQueue.main.async {
self.stopAI()
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
func setUpActivityIndicator() {
aiView.layer.zPosition = 0.1
aiView.backgroundColor = UIColor.gray
aiView.alpha = 0
aiView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(aiView)
aiView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true
aiView.centerYAnchor.constraint(equalTo: tableView.centerYAnchor, constant: -60).isActive = true
aiView.heightAnchor.constraint(equalToConstant: 150).isActive = true
aiView.widthAnchor.constraint(equalToConstant: 150).isActive = true
aiView.layer.masksToBounds = true
aiView.layer.cornerRadius = 15
activityIndicator.layer.zPosition = 0.2
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: aiView.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: aiView.centerYAnchor).isActive = true
}
func startAI() {
activityIndicator.startAnimating()
aiView.alpha = 0.80
tableView.isUserInteractionEnabled = false
}
func stopAI() {
self.activityIndicator.stopAnimating()
self.tableView.isUserInteractionEnabled = true
self.aiView.alpha = 0
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
var messagesViewController: MessagesViewController?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss(animated: true) {
let selectedUser: User!
if(self.searchController.isActive)
{
selectedUser = self.filteredUsers[indexPath.row]
}
else
{
selectedUser = self.users[indexPath.row]
}
self.messagesViewController?.showChatControllerForUser(user: selectedUser)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (searchController.isActive) {
return filteredUsers.count
}
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
startAI()
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
let thisUser: User!
if (searchController.isActive) {
thisUser = filteredUsers[indexPath.row]
} else {
thisUser = users[indexPath.row]
}
cell.nameLabel.text = "\(thisUser.firstname!) \(thisUser.surname!)"
cell.usernameLabel.text = thisUser.username
cell.profileImageView.loadImageUsingCacheWithUrlString(urlString: thisUser.userImg!)
cell.timeLabel.text = nil
stopAI()
return cell
}
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let scopeButton = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
let searchText = searchBar.text!
filterForSearchTextAndScopeButton(searchText: searchText, scopeButton: scopeButton)
}
func filterForSearchTextAndScopeButton(searchText: String, scopeButton : String = "All") {
filteredUsers = users.filter {
user in
let scopeMatch = (scopeButton == "All" || user.username!.lowercased().contains(scopeButton.lowercased()))
if(searchController.searchBar.text != "") {
let searchTextMatch = user.username!.lowercased().contains(searchText.lowercased())
return scopeMatch && searchTextMatch
} else {
return scopeMatch
}
}
tableView.reloadData()
}
}
UserModel:
import UIKit
class User: NSObject {
#objc var id: String?
#objc var firstname: String?
#objc var surname: String?
#objc var email: String?
#objc var username: String?
#objc var userImg: String?
init(dictionary: [String: AnyObject]) {
self.id = dictionary["id"] as? String
self.firstname = dictionary["firstname"] as? String
self.surname = dictionary["surname"] as? String
self.username = dictionary["username"] as? String
self.email = dictionary["email"] as? String
self.userImg = dictionary["userImg"] as? String
}
}
The function I use for showing ChatLogController:
#objc func showChatControllerForUser(user: User) {
let chatLogController = ChatLogController(collectionViewLayout: UICollectionViewFlowLayout())
chatLogController.user = user
navigationController?.pushViewController(chatLogController, animated: true)
}
change the way to detect if is search active or not like this.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss(animated: true) {
let selectedUser: User!
if(self.searchController.searchBar.text != "")
{
selectedUser = self.filteredUsers[indexPath.row]
}
else
{
selectedUser = self.users[indexPath.row]
}
self.messagesViewController?.showChatControllerForUser(user: selectedUser)
}
}
some times searchController is active but ther searhbar is "" so is not realizable way to check where is search term or not

Checkout shopping cart UICollectionViewCell

I have a shopping cart check out flow using a UICollectionView with full page UICollectionViewCells. When the add button is pressed, the remove button is then visible and vice versa. For some reason when add and remove are repeatedly pressed it disrupts the other cells. It will show remove button on another cell when the add button on that cell was never even pressed. I am not sure what's wrong with my logic.
protocol PostCellDelegate {
func removeButtonTapped(cell: PostCell)
func addTapped(cell: PostCell)
}
class PostCell: UICollectionViewCell {
var currentPrice: Float = 0
var delegate: PostCellDelegate?
func set(name: String, brand: String, price: String, image: String){
nameLabel.text = name
brandLabel.text = brand
priceLabel.text = "$\(price)"
photoImageView.loadImage(urlString: image)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.myButton.addTarget(self, action: #selector(addButtonTapped(sender:)), for: .touchUpInside)
self.removeButton.addTarget(self, action: #selector(subButtonTapped(sender:)), for: .touchUpInside)
self.contentView.addSubview(containerView)
setupCellConstraints()
}
#objc func addButtonTapped(sender: UIButton){
self.delegate?.addTapped(cell: self)
sender.isHidden = true
}
#objc func subButtonTapped(sender: UIButton){
self.delegate?.removeButtonTapped(cell: self)
sender.isHidden = true
}
}
class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, PostCellDelegate {
var totalPrice = Float()
private var hiddenRows = Set<Int>()
var finalList = [Item]()
#objc func addTapped(cell: PostCell) {
guard let indexPath = self.collectionView.indexPath(for: cell) else {return}
hiddenRows.insert(indexPath.row)
cell.removeButton.isHidden = false
let item = itemsArr[indexPath.row]
finalList.append(item)
collectionView?.reloadData()
totalPrice += Float(item.price) ?? 0
}
#objc func removeButtonTapped(cell: PostCell) {
guard let indexPath = self.collectionView.indexPath(for: cell) else {return}
hiddenRows.insert(indexPath.row)
cell.myButton.isHidden = false
let item = itemsArr[indexPath.row]
finalList.removeAll{ $0.name == item.name}
totalPrice -= Float(item.price) ?? 0
}
extension CollectionViewController {
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! PostCell
cell.delegate = self
let item = itemsArr[indexPath.row]
let page = itemsArr[indexPath.item]
cell.set(name: item.name, brand: item.brand, price: item.price, image: item.image_url)
if hiddenRows.contains(indexPath.row) {
cell.myButton.isHidden = true
cell.removeButton.isHidden = false
}else{
cell.removeButton.isHidden = true
cell.myButton.isHidden = false
}
return cell
}
Cells are reused you need to restore to default here in cellForItemAt
// restore default look here ( hide / show what you need )
if hiddenRows.contains(indexPath.row) {
cell.myButton.isHidden = true
cell.removeButton.isHidden = false
}else{
cell.removeButton.isHidden = true
cell.myButton.isHidden = false
}
It was because in removeButtonTapped I had hiddenRows.insert(indexPath.row) instead of hiddenRows.remove(indexPath.row). I don't know how I missed that.

Saved object array not showing up in new ViewController

Every time my add button is pressed I am attempting to append that specific item into an array of Item. It prints in the console for each new cell, however when I push to a new ViewController, which will be a summary of all the items added, it does not print the items. Only an empty array is printed.
class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, PostCellDelegate {
var finalList = [Item]()
#objc func addTapped(cell: PostCell) {
guard let indexPath = self.collectionView.indexPath(for: cell) else {return}
hiddenRows.insert(indexPath.row)
cell.removeButton.isHidden = false
let item = itemsArr[indexPath.row]
finalList.append(item)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! PostCell
cell.delegate = self
let item = itemsArr[indexPath.row]
cell.set(name: item.name, brand: item.brand, price: item.price)
print(finalList)
return cell
}
#objc private func handleNext() {
let nextIndex = min(pageControl.currentPage + 1, itemsArr.count - 1)
let indexPath = IndexPath(item: nextIndex, section: 0)
if pageControl.currentPage == 4{
let checkoutView = FinishViewController()
self.navigationController?.pushViewController(checkoutView, animated: true)
checkoutView.modalPresentationStyle = .overCurrentContext
present(checkoutView, animated: true)
print("last item")
}else {
print("not last")
}
pageControl.currentPage = nextIndex
collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
lazy var pageControl: UIPageControl = {
let pc = UIPageControl()
pc.currentPage = 0
pc.numberOfPages = 4
pc.currentPageIndicatorTintColor = .red
pc.pageIndicatorTintColor = UIColor(red: 249/255, green: 207/255, blue: 224/255, alpha: 1)
return pc
}()
class FinishViewController: UIViewController {
let cV = CollectionViewController()
override func viewDidLoad() {
print(cV.finalList)
super.viewDidLoad()
view.backgroundColor = .red
}
The issue is that in the FinishViewController you are initializing a new instance of CollectionViewController, so the property has the default empty array value.
You need to pass in the array to the FinishViewController when you present or segue to it.
In FinishViewController add the following:
var finalList = [Item]()
And in handleNext make sure you set finalList correctly:
let checkoutView = FinishViewController()
checkoutView.finalList = self.finalList
#objc func addTapped(cell: PostCell) {
guard let indexPath = self.collectionView.indexPath(for: cell) else
{return}
hiddenRows.insert(indexPath.row)
cell.removeButton.isHidden = false
let item = itemsArr[indexPath.row]
finalList.append(item)
collectionView?.reloadData() <---- add this
}

Pass IndexPath row value to delegate function instead of Sender.tag to delete Image item CollectionView Swift

I am using collectionView to display photos and delegate functions to view custom alert.
On photos I have cross mark which is for delete photo. My delegate functions and display items all working fine.
But I am getting Issue when I have to delete photo from server. Because I need to pass exact image ID to web service to remove it from server. If I use cell.tag thing it gives me row value which is 1 but actual imgID is 40992. How can I pass this value to my delete delegate function?
Structure:
cell items display --tap gesture call removeImage func --- custom alert -- on Delete button -- didDeleteButtonClicked called.
Main Value which I need in cellForItem:
let imgId = AppData?.imageList?[indexPath.row].projectUnitImageId
PhotoViewController:
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellName, for: indexPath) as! PhotoCollectionViewCell
if(indexPath.row < (AppData?.imageList?.count ?? 0)){
cell.imageView.isHidden = false
cell.closeIcon.isHidden = false
cell.addIcon.isHidden = true
let dic = AppData?.imageList?[indexPath.row].url ?? " "
cell.imageView.image = UIImage(url: URL(string: dic))
let imgId = AppData?.imageList?[indexPath.row].projectUnitImageId
print(imgId)
cell.closeIcon.isUserInteractionEnabled = true
cell.closeIcon.tag = imgId ?? 0
deleteAlertView.delegate = self
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(removeImage(_:)))
cell.closeIcon.addGestureRecognizer(tapGestureRecognizer)
} else {
cell.imageView.isHidden = true
cell.closeIcon.isHidden = true
cell.addIcon.isHidden = false
}
return cell
}
#objc func removeImage(_ sender: AnyObject){
print(imgId)
let fotoXib = Bundle.main.loadNibNamed("FotoDeleteAlert", owner: self, options: nil)
let alertView = fotoXib?.first as! FotoDeleteAlert
alertView.delegate = self
self.view.addSubview(alertView)
}
//MARK: - Delegate Function
extension PhotoCollectionViewController: handleDeleteAction {
func didDeleteButtonClicked(_ sender: UIButton) {
print("delegate")
let row = sender.tag
print(row)
// I have to call my delete webServices here and have to pass imgId
deleteWebservices(imgId)
}
}
FotoAlert Xib custom alert:
protocol handleDeleteAction {
func didDeleteButtonClicked(_: UIButton)
}
#IBDesignable class FotoDeleteAlert: UIView {
var delegate: handleDeleteAction?
#IBOutlet weak var deleteBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
layoutIfNeeded()
deleteBtn.addTarget(self, action: #selector(didDelete(_:)), for: .touchUpInside)
}
#IBAction func didCancel(_ sender: Any) {
removeFromSuperview()
}
#IBAction func didDelete(_ sender: UIButton) {
self.delegate?.didDeleteButtonClicked(sender)
removeFromSuperview()
}
}
TL;DR;
You set tag of cell.closeIcon but then you use tag of FotoDeleteAlert button.
To fix that you need to add
class FotoDeleteAlert: ... {
...
func setButtonTag(imageId: Int) {
button.tag = imageId
}
}
#objc func removeImage(_ sender: UIView){
print(imgId)
let fotoXib = Bundle.main.loadNibNamed("FotoDeleteAlert", owner: self, options: nil)
let alertView = fotoXib?.first as! FotoDeleteAlert
alertView.setButtonTag(imageId: sender.tag
alertView.delegate = self
self.view.addSubview(alertView)
}
extension PhotoCollectionViewController: handleDeleteAction {
func didDeleteButtonClicked(_ sender: UIButton) {
print("delegate")
let imgId = sender.tag
// I have to call my delete webServices here and have to pass imgId
deleteWebservices(imgId)
}
}
Now let's cleanup your spaghetti code
Most of your collectionView(_:, cellForItemAt: ) can be moved into PhotoCollectionViewCell.
I wouldn't send id via tag, instead you can create own delegate
Finally I would rewrite it into something like:
class Controller {
func displayAlert(for info: PhotoInfo) {
// I bet this code is invalid (view isn't correctly aligned
let fotoXib = Bundle.main.loadNibNamed("FotoDeleteAlert", owner: self, options: nil)
let alertView = fotoXib?.first as! FotoDeleteAlert
alertView.delegate = self
self.view.addSubview(alertView)
}
}
extension Controller: UICollectionViewDelegate {
func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: cellName, for: indexPath
) as! PhotoCollectionViewCell
let imgInfo: PhotoInfo? = AppData?.imageList?[indexPath.row]
cell.display(info: imgInfo)
cell.delegate = self
return cell
}
}
extension Controller: PhotoCollectionViewCellDelegate {
func photoCollectionViewCell(didPressCloseBtn cell: PhotoCollectionViewCell) {
guard let indexPath = collectionView.indexPath(for: cell) else { return }
if let imgInfo: PhotoInfo = AppData?.imageList?[indexPath.row] {
displayAlert(for: imgInfo)
}
}
}
extension Controller: FotoDeleteAlertDelegate {
func fotoDeleteAlert(
didPressDelete view: FotoDeleteAlert, for item: PhotoInfo?
) {
guard let item: PhotoInfo = item else { return }
deleteWebservices(item.projectUnitImageId)
}
}
protocol PhotoCollectionViewCellDelegate: class {
func photoCollectionViewCell(didPressCloseBtn: PhotoCollectionViewCell)
}
class PhotoCollectionViewCell: UICollectionViewCell {
weak var delegate: PhotoCollectionViewCellDelegate?
var closeIcon: UIButton! {
didSet {
button.addTarget(
self, action: #selector(closeTap), for: .touchUpInside
)
}
}
func display(info: PhotoInfo?) {
imageView.isHidden = info == nil
closeIcon.isHidden = info == nil
addIcon.isHidden = info != nil
if let info = info {
imageView.image = UIImage(url: URL(string: info.url))
}
}
#objc func closeTap() {
delegate?.photoCollectionViewCell(didPressCloseBtn: self)
}
}
protocol FotoDeleteAlertDelegate: class {
func fotoDeleteAlert(
didPressDelete view: FotoDeleteAlert, for item: PhotoInfo?
)
}
class FotoDeleteAlert {
weak var delegate: FotoDeleteAlertDelegate?
var deleteButton: UIButton! {
didSet {
button.addTarget(
self, action: #selector(deleteTap), for: .touchUpInside
)
}
}
private var item: PhotoInfo?
func display(item: PhotoInfo) {
self.item = item
}
#objc func deleteTap() {
delegate?.fotoDeleteAlert(didPressDelete: self, for: item)
}
}
You need
guard let id = AppData?.imageList?[row].projectUnitImageId else { return }
deleteWebservices(id)
Tags are a fragile way to find the particular cell that a user interacts with. Instead, I suggest using the coorindates of the buttons.
I wrote an answer to this thread: swift: how to get the indexpath.row when a button in a cell is tapped?
In that answer I wrote an extension to UITableView, indexPathForView(_:) That function takes a view (which is contained by the table view cell ) and returns the IndexPath of the cell that contains that view.
You can use that exact same approach for collection views. Table views have the function indexPathForRow(at:) and collection views have the equivalent function indexPathForItem(at:).
The extension for a UICollection view would look something like this: (not tested)
import UIKit
public extension UICollectionView {
/**
This method returns the indexPath of the cell that contains the specified view
- Parameter view: The view to find.
- Returns: The indexPath of the cell containing the view, or nil if it can't be found
*/
func indexPathForView(_ view: UIView) -> IndexPath? {
let center = view.center
//The center of the view is a better point to use, but we can only use it if the view has a superview
guard let superview = view.superview else {
//The view we were passed does not have a valid superview.
//Use the view's bounds.origin and convert from the view's coordinate system
let origin = self.convert(view.bounds.origin, from: view)
let indexPath = self.indexPathForItem(at: origin)
return indexPath
}
let viewCenter = self.convert(center, from: superview)
let indexPath = self.indexPathForItem(at: viewCenter)
return indexPath
}
}
Refactor your FotoDeleteAlert to have an imgID property. Have its didDeleteButtonClicked method pass back the image ID, not the tapped button:
protocol handleDeleteAction {
func didDeleteButtonClickedForImageID(_: Integer)
}
Then you would need to rewrite your removeImage(_:) function to take the gesture recognizer and use that to find the IndexPath:
#objc func removeImage(_ tapper: UIGetstureRecognizer) {
//Find the view associated with the tap gesture recognizer
guard let view = tapper.view,
//use the view to find the indexPath of the cell
let indexPath = collectionView. indexPathForView(view) else {
return
}
let imgId = AppData?.imageList[indexPath.row].projectUnitImageId
let fotoXib = Bundle.main.loadNibNamed("FotoDeleteAlert", owner: self, options: nil)
let alertView = fotoXib?.first as! FotoDeleteAlert
alertView.delegate = self
//Pass the image ID to the FotoDeleteAlert
alertView.imgID = imgID
self.view.addSubview(alertView)
}
And then in your delete handler for the FotoDeleteAlert, you can fetch the image ID and use that to issue the delete command to your server.

How to draw on collectionView cells using UICollectionView's selectItem?

I added the repo I am working here:
https://github.com/AlexMarshall12/singleDayTimeline/tree/master/singleDayTimeline
Basically I have 900 collectionView cells (with a custom XIB layout).
let cellIdentifier = "DayCollectionViewCell"
class ViewController: UIViewController, UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var button: UIButton!
var dates = [Date?]()
var startDate: Date?
#IBOutlet weak var daysCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
daysCollectionView.register(UINib.init(nibName: "DayCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: cellIdentifier)
let allDates = Helper.generateRandomDate(daysBack: 900, numberOf: 10)
self.dates = allDates.sorted(by: {
$0!.compare($1!) == .orderedAscending
})
startDate = self.dates.first! ?? Date()
daysCollectionView.delegate = self
daysCollectionView.dataSource = self
// Do any additional setup after loading the view, typically from a nib.
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 900
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = daysCollectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! DayCollectionViewCell
let cellDate = Calendar.current.date(byAdding: .day, value: indexPath.item, to: self.startDate!)
if Calendar.current.component(.day, from: cellDate!) == 15 {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM"
let monthString = dateFormatter.string(from: cellDate!)
cell.drawMonth(month: monthString)
}
if Calendar.current.component(.day, from: cellDate!) == 1 && Calendar.current.component(.month, from: cellDate!) == 1 {
print("drawYEAR")
cell.drawYear(year:Calendar.current.component(.year, from: cellDate!))
}
if self.dates.contains(where: { Calendar.current.isDate(cellDate!, inSameDayAs: $0!) }) {
print("same")
cell.backgroundColor = UIColor.red
} else {
print("not me")
//cell.backgroundColor = UIColor.lightGray
}
return cell
}
// func collectionView(_ collectionView: UICollectionView,
// layout collectionViewLayout: UICollectionViewLayout,
// sizeForItemAt indexPath: IndexPath) -> CGSize {
// return CGSize(width: 2, height: daysCollectionView.bounds.size.height/2 )
// }
#IBAction func buttonPressed(_ sender: Any) {
let randomIndex = Int(arc4random_uniform(UInt32(self.dates.count)))
let randomDate = self.dates[randomIndex]
let daysFrom = randomDate?.days(from: self.startDate!)
let indexPath = IndexPath(row: daysFrom!, section: 0)
// if let cell = daysCollectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as DayCollectionViewCell? {
// print("found it")
// } else {
// print("didn't find it")
// }
daysCollectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredHorizontally)
daysCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
Then here is the cell:
class DayCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var arrowImage: UIImageView!
override var isSelected: Bool{
didSet{
arrowImage.isHidden = !isSelected
}
}
override func awakeFromNib() {
super.awakeFromNib()
arrowImage.isHidden = true
}
override func prepareForReuse() {
self.backgroundColor = UIColor.clear
}
func drawMonth(month: String){
}
func drawYear(year: Int){
}
}
It looks like this:
So the plan is that when that button is pressed, you can see in the #IBAction func buttonPressed that a random date is chosen and scrolled to, then that cell is selected in the collectionView. Then that makes the cell's arrow drawn with arrowImage.isHidden = !isSelected in the override var isSelected function.
Currently, this works almost perfectly. The arrow is redrawn under the selected cell EXCEPT when the new index which gets randomly selected is far enough away from the current index. My theory is that if the index difference is big enough, the next cell hasn't been loaded/dequeued yet and thus isSelected is never called. However I am not sure still why its not working properly
1- Add a reloadCell function to change ui of cell. Then you should remove override var isSelected and arrowImage.isHidden = true from awakeFromNib function.
func reloadCell(_ isSelected:Bool){
arrowImage.isHidden = !isSelected
}
2- You should define a variable on ViewController.swift class private var selectedIndexPath: IndexPath? and then you should add this code for to check if arrow is hidden or not.
if let selectedRow = selectedIndexPath {
cell.reloadCell(selectedRow == indexPath)
} else {
cell.reloadCell(false)
}
3- And if you change your button action function like this below, it would be worked.
#IBAction func buttonPressed(_ sender: Any) {
let randomIndex = Int(arc4random_uniform(UInt32(self.dates.count)))
let randomDate = self.dates[randomIndex]
let daysFrom = randomDate?.days(from: self.startDate!)
let indexPath = IndexPath(row: daysFrom!, section: 0)
self.selectedIndexPath = indexPath;
daysCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
daysCollectionView.reloadData()
}
All codes here.
ViewController.swift
import UIKit
let cellIdentifier = "DayCollectionViewCell"
class ViewController: UIViewController, UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var button: UIButton!
var dates = [Date?]()
var startDate: Date?
private var selectedIndexPath: IndexPath?
#IBOutlet weak var daysCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
daysCollectionView.register(UINib.init(nibName: "DayCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: cellIdentifier)
let allDates = Helper.generateRandomDate(daysBack: 900, numberOf: 10)
self.dates = allDates.sorted(by: {
$0!.compare($1!) == .orderedAscending
})
startDate = self.dates.first! ?? Date()
daysCollectionView.delegate = self
daysCollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 900
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = daysCollectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! DayCollectionViewCell
let cellDate = Calendar.current.date(byAdding: .day, value: indexPath.item, to: self.startDate!)
if let selectedRow = selectedIndexPath {
cell.reloadCell(selectedRow == indexPath)
} else {
cell.reloadCell(false)
}
if Calendar.current.component(.day, from: cellDate!) == 15 {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM"
let monthString = dateFormatter.string(from: cellDate!)
cell.drawMonth(month: monthString)
}
if Calendar.current.component(.day, from: cellDate!) == 1 && Calendar.current.component(.month, from: cellDate!) == 1 {
print("drawYEAR")
cell.drawYear(year:Calendar.current.component(.year, from: cellDate!))
}
if self.dates.contains(where: { Calendar.current.isDate(cellDate!, inSameDayAs: $0!) }) {
print("same")
cell.backgroundColor = UIColor.red
} else {
print("not me")
//cell.backgroundColor = UIColor.lightGray
}
return cell
}
#IBAction func buttonPressed(_ sender: Any) {
let randomIndex = Int(arc4random_uniform(UInt32(self.dates.count)))
let randomDate = self.dates[randomIndex]
let daysFrom = randomDate?.days(from: self.startDate!)
let indexPath = IndexPath(row: daysFrom!, section: 0)
self.selectedIndexPath = indexPath;
//daysCollectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredHorizontally)
daysCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
daysCollectionView.reloadData()
}
}
DayCollectionViewCell.swift
import UIKit
class DayCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var arrowImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func prepareForReuse() {
self.backgroundColor = UIColor.clear
}
func drawMonth(month: String){
}
func drawYear(year: Int){
}
func reloadCell(_ isSelected:Bool){
arrowImage.isHidden = !isSelected
}
}

Resources