Some UICollectionViewDropDelegateMethods get not called - swift

I have an iPad application where you can drag images from Safari into a collection view. I also want to to drag and drop within the collection view to rearrange the collection View cells.
The drag and drop from safari is working. Dragging a cell within the collection view is also possible, but dropping is not possible. I took a look at the UICollectionViewDropDelegate methods "canLoadObjects", "sessionDidUpdate" and "performDrop" which get called in this order. I made another simple collection view app with drag and drop, so I know what to do.
I recognized that in the simple app, when I drag the cell onto another place "canLoadObjects" get called. I confirmed that with a breakpoint. In the other app this does not happen. Instead only "performDrop" gets called, but not "canLoadObjects" and "sessionDidUpdate". I set the delegate correctly (otherwise "performDrop would not get called either).
Does anybody know why some delegate methods doesn't get called? Thanks!
import UIKit
class ImageGalleryViewController: UIViewController {
var cellWidthScale: CGFloat = 1.0
var currentCellSize = CGSize()
var imageFetcher : ImageFetcher!
var loadAllImages = false
var tappedCellIndex = 0
#IBOutlet weak var collectionView: UICollectionView!{
didSet{
collectionView.delegate = self
collectionView.dataSource = self
collectionView.dragInteractionEnabled = true
collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(adjustCellWidth(with:))))
}
}
var userIsPinching = false
var imageDataTuple : [(NSURL?, CGSize)]? {
didSet{
if loadAllImages == true{
collectionView?.reloadData()
loadAllImages = false
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: "ImageCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "ImageCell")
}
#objc func adjustCellWidth(with recognizer: UIPinchGestureRecognizer){
switch recognizer.state {
case .changed:
userIsPinching = true
cellWidthScale *= recognizer.scale
recognizer.scale = 1.0
collectionView.collectionViewLayout.invalidateLayout()
default: break
}
}
}
//MARK: UICollectionViewDelegate
extension ImageGalleryViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageDataTuple?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath)
if let imageCell = cell as? ImageCollectionViewCell{
imageCell.imageView.image = nil
imageCell.spinner.startAnimating()
if let url = imageDataTuple?[indexPath.row].0{
imageCell.imageUrl = url
//never fetch data on the main queue
DispatchQueue.global(qos: .userInitiated).async {
let urlContent = try? Data(contentsOf: (url as URL).imageURL)
//update the UI always on the main queue
DispatchQueue.main.async {
//check if the url is really the one we want to show
if let imageData = urlContent, url == imageCell.imageUrl, let image = UIImage(data: imageData){
imageCell.spinner.stopAnimating()
imageCell.imageView.image = image
}
else{
imageCell.spinner.stopAnimating()
imageCell.imageView.image = UIImage(systemName: "xmark.octagon.fill")
imageCell.imageView.tintColor = UIColor.red
imageCell.imageView.sizeToFit()
}
}
}
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let checkedImageData = imageDataTuple{
let imageHeightToWidth = checkedImageData[indexPath.row].1.height/checkedImageData[indexPath.row].1.width
let calculatedWidth = (self.view.frame.width/4) * cellWidthScale
currentCellSize = CGSize(width: calculatedWidth, height: calculatedWidth * imageHeightToWidth)
return currentCellSize
}
return CGSize.zero
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedCellIndex = indexPath.item
performSegue(withIdentifier: "ShowImage", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowImage"{
if let destVC = segue.destination as? DetailImageViewController{
destVC.imageUrl = imageDataTuple![tappedCellIndex].0
}
}
}
}
//MARK: UICollectionViewDropDelegate
extension ImageGalleryViewController: UICollectionViewDropDelegate{
//1. Can the dropDelegate handle the object?
func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
//requirement: only drop with image and url should be handled
return session.canLoadObjects(ofClass: NSURL.self)
}
//2. update the session. Return a propsal in which manner the object schould be dropped (.copy; .move...)
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
//Es wird überprüft, ob der collectionView auf dem gedroppt werden soll der ist, von dem das DragItem stammt
let isSelf = (session.localDragSession?.localContext as? UICollectionView) == collectionView
let dropOperation: UIDropOperation = isSelf ? .move : .copy
//intent: Soll eine neue Cell erstellt werden oder in die eingefügt werden, über der man sich gerde befindet?
return UICollectionViewDropProposal(operation: dropOperation, intent: .insertAtDestinationIndexPath)
}
//3. perform the actual drop. update UI and the model
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
let destinationIntexPath = coordinator.destinationIndexPath ?? IndexPath(row: 0, section: 0)
for item in coordinator.items{
//Is the drop from the same collectionView or external? internal --> != nil
if let sourceIndexPath = item.sourceIndexPath{
if let url = item.dragItem.localObject as? NSURL{
collectionView.performBatchUpdates {
imageDataTuple?.remove(at: sourceIndexPath.item)
imageDataTuple?.insert((url, currentCellSize), at: destinationIntexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIntexPath])
}
coordinator.drop(item.dragItem, toItemAt: destinationIntexPath)
}
}
//external --> nil
else{
var dataTupel: (NSURL?, CGSize?) {
didSet{
DispatchQueue.main.async {
if let controllerTitel = self.title, let presentImageSize = dataTupel.1, let presentURL = dataTupel.0{
if ImageGalleryModel.imageURLs[controllerTitel] != nil {
ImageGalleryModel.imageURLs[controllerTitel]!.append((presentURL, presentImageSize))
print(ImageGalleryModel.imageURLs)
self.imageDataTuple = ImageGalleryModel.imageURLs[controllerTitel]!
self.collectionView.insertItems(at: [IndexPath(item: ImageGalleryModel.imageURLs[controllerTitel]!.count - 1, section: 0)])
}
}
}
}
}
item.dragItem.itemProvider.loadObject(ofClass: NSURL.self) { provider, error in
dataTupel.0 = provider as? NSURL
}
item.dragItem.itemProvider.loadObject(ofClass: UIImage.self) { provider, error in
if let receivedImage = provider as? UIImage{
dataTupel.1 = receivedImage.size
}
}
}
}
}
}
//MARK: UICollectionViewDragDelegate
extension ImageGalleryViewController: UICollectionViewDragDelegate{
//Proveide inital drag items
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return dragItem(at: indexPath)
}
//Provide drag items which were added while dragging
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
session.localContext = collectionView
return dragItem(at: indexPath)
}
private func dragItem(at indexPath: IndexPath) -> [UIDragItem]{
if let image = (collectionView.cellForItem(at: indexPath) as? ImageCollectionViewCell)?.imageView.image{
let dragItem = UIDragItem(itemProvider: NSItemProvider(object: image))
dragItem.localObject = image
return [dragItem]
}
return []
}
}
//MARK: New selected Gallery
extension ImageGalleryViewController: GalleryListViewControllerDelegate{
func gallerySelected(name: String) {
loadAllImages = true
imageDataTuple = ImageGalleryModel.imageURLs[name]
self.title = name
}
}

The problem was that the auto correction chose delegate methods of the UIDropInteraction-delegate and not from the UICollectionViewDropDelegate. Just picked the correct methods and everything works as intended.

Related

How can I update a UICollectionView from another class?

I am trying this for a couple of days now and haven't achieved anything yet. What I am trying to do is when a user picks a User item form the ViewController class, I want to save it in Realm and show it in the CollectionView of the SavedInterestsViewController class. I use a delegate pattern as suggested in this post How to access and refresh a UITableView from another class in Swift, but unfortunately I still receive nil, I guess because the GC removed the collectionView outlet already right? (please correct me if I misunderstood it). However, how can I get this to work by using a delegate pattern? Here is my code, this is the class where the user Picks a new User-item:
protocol ViewControllerDelegate {
func didUpdate(sender: ViewController)
}
class ViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var collectionView: UICollectionView!
var delegate: ViewControllerDelegate?
let numberOfTweets = 5
let realm = try! Realm()
var image = UIImage()
var imageArray: [String] = []
var userArray: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionCell")
searchBar.delegate = self
}
}
extension ViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print("One second...")
let functionClass = NetworkingFunctions()
var loopCount = 0
functionClass.getWikipediaAssumptions(for: searchBar.text!) { [self] (articleArray) in
self.userArray.removeAll()
for x in articleArray {
functionClass.performWikipediaSearch(with: x, language: WikipediaLanguage("en")) { (user) in
self.userArray.append(user)
collectionView.reloadData()
loopCount += 1
}
}
}
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell
let image = UIImage.init(data: userArray[indexPath.item].image as Data)
cell.userImage.image = image
cell.nameLabel.text = userArray[indexPath.item].name
cell.userImage.layer.borderColor = image?.averageColor?.cgColor
if userArray[indexPath.item].checked == false {
cell.checkmark.isHidden = true
} else {
cell.checkmark.isHidden = false
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if userArray[indexPath.item].checked == false {
userArray[indexPath.item].checked = true
collectionView.reloadData()
let newUser = User()
newUser.image = userArray[indexPath.item].image
newUser.name = userArray[indexPath.item].name
newUser.checked = true
try! realm.write {
realm.add(newUser)
}
self.delegate = SavedInterestsViewController()
self.delegate?.didUpdate(sender: self)
}
else {
userArray[indexPath.item].checked = false
collectionView.reloadData()
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width/3 - 10
let height = width
return CGSize(width: width, height: height)
}
}
class User: Object {
#objc dynamic var image: NSData = NSData()
#objc dynamic var name: String = ""
#objc dynamic var checked: Bool = false
}
... and this is the class where I want to show the selected item, after the User clicked on the 'Back' Button of the navigation controller of the ViewController class:
class SavedInterestsViewController: UIViewController, ViewControllerDelegate {
func didUpdate(sender: ViewController) {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
#IBOutlet weak var addButton: UIBarButtonItem!
#IBOutlet weak var collectionView: UICollectionView!
let realm = try! Realm()
var userArray: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionCell")
fetchDataFromRealm()
}
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "SavedToNew", sender: self)
}
func fetchDataFromRealm() {
userArray.append(contentsOf: realm.objects(User.self))
collectionView.reloadData()
}
}
extension SavedInterestsViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell
let image = UIImage.init(data: userArray[indexPath.item].image as Data)
cell.userImage.image = image
cell.nameLabel.text = userArray[indexPath.item].name
cell.userImage.layer.borderColor = image?.averageColor?.cgColor
if userArray[indexPath.item].checked == false {
cell.checkmark.isHidden = true
} else {
cell.checkmark.isHidden = false
}
return cell
}
}
extension SavedInterestsViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width/3 - 10
let height = width
return CGSize(width: width, height: height)
}
}
in your code you have lines
self.delegate = SavedInterestsViewController()
self.delegate?.didUpdate(sender: self)
but it do mostly nothing - you set the delegate to newly created class, didUpdate it - but I don't see any use of it - you don't present it in any way.
If I understand you right - you have SavedInterestsViewController, from it - you open ViewController, do something and when back to SavedInterestsViewController. (I can be wrong with your flow - correct me if so)
In this flow - you have delegate property in ViewController, but it must be of type SavedInterestsViewController. And you have to set it to SavedInterestsViewController when you open ViewController from it. And later in ViewController you have to call didUpdate method of delegate.

accessing an array of images from 1 view controller to another, to add swipe gesture recognizer

i have a collection view which has an array of images. when i press on any of the images it will open that image in full screen in another class. i tried to add swipe gesture recognizer in the second view controller but i dont know how to access the array that is in the first view controller.
This is my first view controller that displays the images in collection view
class sowrController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var collectionView: UICollectionView!
var albums = [AlbumModel]()
let db : DBHelperMo2lfat = DBHelperMo2lfat()
var selectedIndex : Int = -1
var posts : Post!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
self.albums.removeAll()
self.albums.append(contentsOf: self.db.fetchAllImages())
self.collectionView.reloadData()
DataService.ds.REF_POSTS_SOWR.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
self.albums.removeAll()
for snap in snapshot {
print ("SNAP: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject>{
let album : AlbumModel = AlbumModel(id: postDict["id"] as! String, name: postDict["image_name"] as! String, path: postDict["image_path"] as! String, url: postDict["image_path"] as! String, localPath: "")
if let items = snap.children.allObjects as? [FIRDataSnapshot] {
for itemSnap in items {
if let albumSnap = itemSnap.value as? Dictionary<String, AnyObject> {
album.childAlbums.append(AlbumModel(id: albumSnap["id"] as! String, name: albumSnap["image_name"] as! String, path: albumSnap["image_path"] as! String, url: albumSnap["image_path"] as! String, localPath: ""))
}
}
}
self.albums.append(album)
}
}
self.collectionView.reloadData()
}
})
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.albums.count
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.BookCellReuseIdentifier, for: indexPath) as? collectionViewCellSowr {
let album = albums[indexPath.item]
cell.initWithAlbumModel(album: album)
return cell
}else {
return collectionViewCellSowr()
}
}
private struct Constants {
static let BookCellReuseIdentifier = "cell"
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.selectedIndex = indexPath.row
self.performSegue(withIdentifier: "showAlbum", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "showAlbum"
{
let vc = segue.destination as! imageFullScreen
vc.images = self.albums[self.selectedIndex]
}
}
This is the second view controller that makes the images go in full screen
class imageFullScreen: UIViewController{
var images : AlbumModel?
let db : DBHelperMo2lfat = DBHelperMo2lfat()
#IBAction func pictureSwipe(_ sender: Any) {
}
#IBOutlet weak var caption: UILabel!
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.caption.text = images?.imageName
let url = URL(string: (images?.imagePath)!)
self.imageView.sd_setImage(with: url, placeholderImage: nil, options: [.progressiveDownload,.retryFailed])
}
EDIT:
Ok, so here is a collection view controller that creates image view as a subview and responding to swipe gestures. Please make sure you have two images "Image" and "Image-1" in your assets folder.
//
// CollectionViewController.swift
// test
//
// Created by Yonatan Vainer on 05/08/2017.
// Copyright © 2017 Sensus Healthcare LLC. All rights reserved.
//
import UIKit
private let reuseIdentifier = "id"
class CollectionViewController: UICollectionViewController {
var imageView = UIImageView(frame: CGRect(x: 0, y: 100, width: 300, height: 300))
var index = 0;
let names = ["Image","Image-1"]
override func viewDidLoad() {
super.viewDidLoad()
//For left swipe
let left = UISwipeGestureRecognizer(target: self, action: #selector(self.goLeft(_:)))
left.direction = .left
imageView.addGestureRecognizer(left)
//For right swipe
let right = UISwipeGestureRecognizer(target: self, action: #selector(self.goRight(_:)))
right.direction = .right
imageView.addGestureRecognizer(right)
imageView.isUserInteractionEnabled = true
self.view.addSubview(imageView)
self.view.layoutSubviews()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return names.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
// Configure the cell
let nail = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
nail.image = UIImage(named: names[indexPath.row])
cell.backgroundView = nail
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
imageView.image = UIImage(named: names[indexPath.row])
index = indexPath.row
}
func goLeft(_ gesture: UISwipeGestureRecognizer){
index += 1
if index<0{
index = 0
}
imageView.image = UIImage(named: names[index])
}
func goRight(_ gesture: UISwipeGestureRecognizer){
index -= 1
if index>1{
index = 1
}
imageView.image = UIImage(named: names[index])
}
// MARK: UICollectionViewDelegate
/*
// Uncomment this method to specify if the specified item should be highlighted during tracking
override func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
return true
}
*/
/*
// Uncomment this method to specify if the specified item should be selected
override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return true
}
*/
/*
// Uncomment these methods to specify if an action menu should be displayed for the specified item, and react to actions performed on the item
override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
return false
}
override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return false
}
override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
}
*/
}
==================================================================
In storyboard, click on your collection view and embed navigation controller.
This will add a top bar with the back button.
Attached image.
I'm not sure I completely understand your question, because I don't understand what the array has to do with a gesture recognizer, but if you are just trying to access the array from the previous ViewController, this should work if you have a navigation controller :
let vcIndex = self.navigationController?.viewControllers.index(where: { (viewController) -> Bool in
if let _ = viewController as? sowrController {
return true
}
return false
})
let prevVC = self.navigationController?.viewControllers[vcIndex!] as! sowrController
let albums:[AlbumModel] = prevVC.albums

3D Touch doesn't in CollectionView

I created a soundboard and I would like when you long push on the tile (CollectionView Cell) that become a 3D Touch popup with the text of the tile.
I have a ViewCollection with the storyboard ID 'tilePreview'. And the below code, but the 3D touch doesn't.
What do I wrong? Can anybody help me?
import UIKit
import AVFoundation
class CollectionViewController: UICollectionViewController, UIViewControllerPreviewingDelegate {
#IBOutlet var soundBoard: UICollectionView!
var list = [String]()
var buttonOn: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
if traitCollection.forceTouchCapability == UIForceTouchCapability.available {
registerForPreviewing(with: self, sourceView: collectionView!)
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> MyCollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionViewCell
let tileText = list[indexPath.row]
cell.cellTitle.text = tileText
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
Speak(currentTitle: list[indexPath.row])
}
// MARK: - Preview tile
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = collectionView?.indexPathForItem(at: location) else { return nil }
guard let cell = collectionView?.cellForItem(at: indexPath) else { return nil }
guard let detailVC = storyboard?.instantiateViewController(withIdentifier: "tilePreview") as? DetailViewController else { return nil }
previewingContext.sourceRect = cell.frame
return detailVC
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
show(viewControllerToCommit, sender: self)
}
}
1) in viewDidLoad check 3D Touch is available or not
override func viewDidLoad() {
super.viewDidLoad()
//3D touch
if(traitCollection.forceTouchCapability == .available){
registerForPreviewing(with: self, sourceView: view)
}
}
2) Delegate method for 3D touch
//3D Touch delegate method
extension PostViewController : UIViewControllerPreviewingDelegate {
#available(iOS 9.0, *)
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
self.navigationController?.show(viewControllerToCommit, sender: self)
}
#available(iOS 9.0, *)
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
let locationCell : CGPoint = self.collectionView.convert(location, from: self.view)
guard let indexPath = self.collectionView?.indexPathForItem(at: locationCell) else {
print(locationCell)
print("Cell Not found")
return nil
}
print("indexPath.row :: \(indexPath.row)")
guard let cell = self.collectionView?.cellForItem(at: indexPath) else { return nil }
let didSelectProduct : ProductDM = self.products[indexPath.row]
let destination: ProductDetailsViewController = UIStoryboard(storyboard: .main).instantiateViewController()
destination.selectedProduct = didSelectProduct
//destination.preferredContentSize = CGSize(width: 0.0, height: 300)
previewingContext.sourceRect = self.collectionView.convert(cell.frame, to: self.collectionView.superview)
return destination
}
}

ReloadData in UICollectionView

I use CoreData and want to reload the UICollectionView when I save a new entry in CoreData but the only self.collectionview.reloaddata()don´t run it reload nothing.
Thanks for your Help.
My CoreData and UICollectionView I have try it to reload the UICollectionView when clockblogalarm become a new entry:
var clockblogalarm = [ClockBlogAlarm] () {
didSet { //self.ClockCollectionView.reloadData()
}
}
var mgdContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
func loadClockAlarms () {
let LoadRequestBlogAlarm: NSFetchRequest<ClockBlogAlarm> = ClockBlogAlarm.fetchRequest()
clockblogalarm = try! mgdContext.fetch(LoadRequestBlogAlarm as! NSFetchRequest<NSFetchRequestResult>) as! [ClockBlogAlarm]
}
#IBOutlet var ClockCollectionView: UICollectionView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.loadClockAlarms()
}
Here I save the Entry in CoreData. When I click the Button Done view.bottomButtonHandler{} then I want to reload the data and show it in the UICollectionView with loadClockAlarms() .
else if index == 1 {
let view = AddClockView.instantiateFromNib()
let window = UIApplication.shared.delegate?.window!
view.closeButtonHandler = {[weak modal] in
modal?.closeWithLeansRandom()
return
}
view.bottomButtonHandler = {[weak modal] in
modal?.closeWithLeansRandom()
self.loadClockAlarms()
return
}
modal.show(modalView: view, inView: window!)
}
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clockblogalarm.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AlarmBlogCell", for: indexPath) as! AlarmBlogCell
cell.layer.cornerRadius = 8
let clockblogalarms = clockblogalarm[indexPath.row]
if clockblogalarms.lock == "ClockblogAlarm" {
cell.Title.text = clockblogalarms.title
cell.Time.text = "SSSSS"
}
return cell
}
swift 5 & 4.2:
DispatchQueue.main.async {
self.collectionView.reloadData()
}
Ensure the reloadData() is called in the main thread, as it is the UI-responsible action:
dispatch_async(dispatch_get_main_queue()) {
self.ClockCollectionView.reloadData()
}

"Cannot call value of non-function type"

I have just downloaded the Xcode 8 Beta so that I can include some of the new iOS 10 frameworks in my app. However, during the process of converting my code from Swift 2 to Swift 3, I ran into several errors. I fixed all but one super annoying one.
I am getting the error:
Cannot call value of non-function type 'JSQMessagesCollectionView!' at the following line of code:
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! JSQMessagesCollectionViewCell
Here is my entire function for context:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell{
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId {
cell.textView!.textColor = UIColor.whiteColor()
} else {
cell.textView!.textColor = UIColor.blackColor()
}
return cell
}
Does anybody have an ideas?
P.s. If this helps, here is my entire block of code:
import UIKit
import Firebase
import JSQMessagesViewController
class ChatViewController: JSQMessagesViewController {
// MARK: Properties
var rootRef = FIRDatabase.database().reference()
var messageRef: FIRDatabaseReference!
var messages = [JSQMessage]()
var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!
var userIsTypingRef: FIRDatabaseReference! // 1
private var localTyping = false // 2
var isTyping: Bool {
get {
return localTyping
}
set {
// 3
localTyping = newValue
userIsTypingRef.setValue(newValue)
}
}
var usersTypingQuery: FIRDatabaseQuery!
override func viewDidLoad() {
super.viewDidLoad()
// Change the navigation bar background color to blue.
// navigationController!.navigationBar.barTintColor = UIColor.init(red:252/255, green: 87/255, blue: 68/255, alpha: 1)
navigationController!.navigationBar.barTintColor = UIColor.init(red:250/255, green: 69/255, blue: 85/255, alpha: 1)
title = "RoastChat"
setupBubbles()
// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
// Remove file upload icon
self.inputToolbar.contentView.leftBarButtonItem = nil;
messageRef = rootRef.child("messages")
}
func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
observeMessages()
observeTyping()
}
func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
}
func collectionView(collectionView: JSQMessagesCollectionView!,
messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
func collectionView(collectionView: JSQMessagesCollectionView!,
messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.senderId == senderId { // 2
return outgoingBubbleImageView
} else { // 3
return incomingBubbleImageView
}
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return messages.count
}
func collectionView(collectionView: JSQMessagesCollectionView!,
avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
private func setupBubbles() {
let factory = JSQMessagesBubbleImageFactory()
outgoingBubbleImageView = factory?.outgoingMessagesBubbleImage(
// UIColor.init(red:250/255, green: 69/255, blue: 85/255, alpha: 1))
with: UIColor.init(red:47/255, green: 53/255, blue: 144/255, alpha: 1))
incomingBubbleImageView = factory?.incomingMessagesBubbleImage(
with: UIColor.jsq_messageBubbleLightGray())
}
func addMessage(id: String, text: String) {
let message = JSQMessage(senderId: id, displayName: "", text: text)
messages.append(message!)
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell{
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId {
cell.textView!.textColor = UIColor.whiteColor()
} else {
cell.textView!.textColor = UIColor.blackColor()
}
return cell
}
func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!,
senderDisplayName: String!, date: NSDate!) {
let itemRef = messageRef.childByAutoId() // 1
let messageItem = [ // 2
"text": text,
"senderId": senderId
]
itemRef.setValue(String(messageItem)) // 3
// 4
JSQSystemSoundPlayer.jsq_playMessageSentSound()
// 5
finishSendingMessage()
isTyping = false
}
private func observeMessages() {
// 1
let messagesQuery = messageRef.queryLimited(toLast: 25)
// 2
messagesQuery.observe(.childAdded) { (snapshot: FIRDataSnapshot!) in
// 3
let id = snapshot.value!["senderId"] as! String
let text = snapshot.value!["text"] as! String
// 4
self.addMessage(id: id, text: text)
// 5
self.finishReceivingMessage()
}
}
private func observeTyping() {
let typingIndicatorRef = rootRef.child("typingIndicator")
userIsTypingRef = typingIndicatorRef.child(senderId)
userIsTypingRef.onDisconnectRemoveValue()
// 1
usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqual(toValue: true)
// 2
usersTypingQuery.observe(.value) { (data: FIRDataSnapshot!) in
// 3 You're the only typing, don't show the indicator
if data.childrenCount == 1 && self.isTyping {
return
}
// 4 Are there others typing?
self.showTypingIndicator = data.childrenCount > 0
self.scrollToBottom(animated: true)
}
}
func textViewDidChange(textView: UITextView) {
super.textViewDidChange(textView)
// If the text is not empty, the user is typing
isTyping = textView.text != ""
}
func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForCellBottomLabelAtIndexPath indexPath: NSIndexPath!) -> AttributedString! {
return AttributedString(string:"test")
}
}
Try changing the method as:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: NSIndexPath) -> UICollectionViewCell{
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId {
cell.textView!.textColor = UIColor.whiteColor()
} else {
cell.textView!.textColor = UIColor.blackColor()
}
return cell
}
If you are writing an overridden method correctly, Swift complains about missing override keyword. So, if you cannot find any warnings or errors on this overridden method, you are very likely misusing method signature.
And many UICollectionViewDataSources methods are renamed, in Swift3 "cellForItemAtIndexPath" has this signature:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
So, Swift cannot find a method definition matching collectionView(_:cellForItemAtIndexPath:), so assuming the collectionView there would be a property. (Unfortunately, it is declared in JSQMessagesViewController.)
You may have this sort of mismatching method implementations in your class. Better check them all.
Ok, this is how to create and reuse cell properly:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("reuseId", forIndexPath: indexPath) as! UICollectionViewCell
//update cell subviews here
return cell
}
In your case you should create and return JSQMessagesCollectionViewCell instance
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("reuseId", forIndexPath: indexPath) as! JSQMessagesCollectionViewCell