I need to present a new ViewController when selecting a UICollectionView Cell and pass the data from the entity used to fill selected cell.
Here is the code used to fill cell data:
let pets = PersistenceManager.shared.fetch(Pet.self)
var _fetchResultsController: NSFetchedResultsController <Pet>?
var fetchResultsController: NSFetchedResultsController <Pet>?{
get{
if _fetchResultsController == nil {
let moc = PersistenceManager.shared.context
moc.performAndWait {
let fetchRequest = PersistenceManager.shared.petsFetchRequest()
_fetchResultsController = NSFetchedResultsController.init(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil) as? NSFetchedResultsController<Pet>
_fetchResultsController?.delegate = self
do {
try self._fetchResultsController?.performFetch()
}catch {
}
}
}
return _fetchResultsController
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionViewHorizontal.dequeueReusableCell(withReuseIdentifier: "HorCell", for: indexPath) as! PRMainHorizontalCollectionViewCell
if let pet= self.fetchResultsController?.fetchedObjects, indexPath.row < pet.count{
let _pet= fetchResultsController!.object(at: indexPath)
// cell UI goes here
}
return cell
}
I understand I need to use didSelectItemAt, I just don't know what information needs to go in the function. Please let me know of anything else needed to better help answer this question. Thank you.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Added the line below based on karthik's answer. But I am unsure how to implement it.
let selectedObj = fetchResultsController!.object(at: indexPath)
let vc = self.storyboard?.instantiateViewController(withIdentifier: "selectedPetViewController") as! PRSelectedPetViewController
navigationController?.pushViewController(vc, animated: true)
}
I prefer the following architecture:
This is the main controller with data.
For a better understanding, I will simplify the data source.
class ViewController: UIViewController {
// code ...
#IBOutlet var collectionView: UICollectionView!
fileprivate var data = [Pet]()
}
extension ViewController: UICollectionViewDataSource {
// code ...
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HorCell", for: indexPath) as? PRMainHorizontalCollectionViewCell else {
return UICollectionViewCell()
}
let pet = data[indexPath.row]
// TODO: configure cell using pet ...
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let row = indexPath.row
let pet = data[row]
// TODO: Get the child controller in any way convenient for you.
let childController = ChildViewController()
// With the help of the delegate, we will receive notification of changes pet.
childController.delegate = self
// Thus, we pass the data to the child controller.
childController.pet = pet
childController.indexPath = indexPath
// TODO: Present the view controller in any way convinient for you.
}
}
extension ViewController: ChildViewControllerDelegate {
func saveButtonPressed(_ controller: ChildViewController) {
guard let pet = controller.pet, let indexPath = controller.indexPath else {
return
}
// We save data and reload the cell whose data we changed.
self.data[indexPath.row] = pet
collectionView.reloadItems(at: [indexPath])
}
func cancelButtonPressed(_ controller: ChildViewController) {
// Do something if necessary...
}
}
In addition to the controller, the child controller also provides a delegate protocol for notification of changes.
protocol ChildViewControllerDelegate {
func saveButtonPressed(_ controller: ChildViewController)
func cancelButtonPressed(_ controller: ChildViewController)
}
// This is the controller you want to show after selecting a cell.
// I assume that in the child controller there is a button to save and cancel.
class ChildViewController: UIViewController {
var delegate: ChildViewControllerDelegate?
// The object whose data we are editing in the controller.
var pet: Pet!
// The location of the object in the main controller.
var indexPath: IndexPath!
override func viewDidLoad() {
// TODO: Configure user interface using self.pet
}
#IBAction func saveButtonPressed(_ button: UIButton) {
delegate?.saveButtonPressed(self)
}
#IBAction func cancelButtonPressed(_ button: UIButton) {
delegate?.cancelButtonPressed(self)
}
}
you can follow this to pass information to another view controller.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedObj = fetchResultsController!.object(at: indexPath)
// instantiate presenting view controller object
// add one property (manange object) in your presenting viewcontroller
// assign the selected object to that property
// present the view controller
}
Related
I have a tableView, with a prototype cell containing a UICollectionView. I’ve setup the tableView according to this tutorial (https://medium.com/#stasost/ios-how-to-build-a-table-view-with-multiple-cell-types-2df91a206429), and the UI is working. I can pass data through my tableView and into the collectionView.
View Layout
When a collectionViewCell is selected it segues to another view.
I haven’t figured out how to access the data from the collectionViewCell and pass it to the new view.
The collectionView is initialized within the tableView prototype cell. I've tried didSelectRow -> prepareForSegue (code below), but the commands do not autocomplete, and are not working.
Here's the code for the tableViewCell, where the collectionView is setup.
EDIT: Removed commented code for clarity
import UIKit
class homeFeedTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var feedCollectionView: UICollectionView!
var selectedEvent : Event?
var collectionItems = [CollectionViewModelItem]()
var collectionItem : CollectionViewModelItem?
#IBOutlet weak var sectionHeadingLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
feedCollectionView.delegate = self
feedCollectionView.dataSource = self
print("collection items \(collectionItems.count)")
for item in collectionItems{print("type: \(item.type), title: \(item.eventTitle)")}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
// setup view model
var item: TableViewModelItem? {
didSet {
// if not right class, skip
guard let item = item as? TableViewModelFeed else {
return
}
sectionHeadingLabel.text = item.sectionTitle
}
}
// create reuse identifier property
static var identifier: String {
return String(describing: self)
}
}
import Foundation
import UIKit
extension homeFeedTableViewCell {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// print("dataCount3: \(collectionItems.count) \(collectionItems[collectionItems.count-1].type)")
return collectionItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// let cell = UICollectionViewCell()
// return cell
self.collectionItem = collectionItems[indexPath.row]
switch collectionItem!.type {
case .yourEvents:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier:YourEventsCollectionViewCell.identifier, for: indexPath) as? YourEventsCollectionViewCell{
cell.item = collectionItem
print(cell.item?.type)
print(".yourEvents")
return cell
}
case .feed:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: mainFeedCollectionViewCell.identifier, for: indexPath) as? mainFeedCollectionViewCell{
cell.item = collectionItem
print(".feed")
return cell
}
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("\(collectionItems[indexPath.row].eventTitle) tapped")
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourEventsToEventViewController" || segue.identifier == "feedToEventViewController"{
print("prepare for segue1")
let destinationVC = segue.destination as! EventViewController
if collectionItem != nil{
print("prepare for segue2")
destinationVC.backgroundImageUrl = collectionItem!.backgroundImageUrl
}
}
}
}
}
A UICollectionView keeps track of its selected indexPaths with the property indexPathsForSelectedItems. Since you trigger your segue in collectionView(didSelectItem: atIndexPath:), your selected indexPath is available during prepare(forSegue:). You could try the following:
class MyViewController: UIViewController, UICollectionViewDelegate {
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "mySegue", sender: self)
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let destinationVC = segue.destination as! EventViewController,
segue.identifier == "mySegue" else { return }
// In this context, your selected cell is the one who fired the segue
if let selectedIndexPaths = collectionView.indexPathsForSelectedItems,
let firstSelectedIndexPath = selectedIndexPaths.first {
let selectedObject = collectionItems[firstSelectedIndexPath.row]
destinationVC?.backgroundUrl = selectedObject.backgroundUrl
}
}
}
The sequence is:
You select a cell (through user interaction, ie tapping).
didSelect performs a segue named "mySegue" (in this example).
In prepareForSegue, you look for your selected index paths. Assuming you aren't using multi-selection, you want your first and only indexPath. Using that index path, you can retrieve your data in your collectionItems array.
I'm trying to load in some data in a feed based off of a user's data through Firebase, however, it isn't working. My application is currently organized so that the user enters on CustomTabBarController and is verified for login and that a profile has been created, retrieving it if needed. Then, I send the user to the feed by:
// Go to home feed
let navController = self.viewControllers![0] as? UINavigationController
let feedVC = navController?.topViewController as? FeedViewController
if feedVC != nil {
feedVC!.getProfilePhotos()
}
My first question - is this the correct way to load in the FeedViewController on a CustomTabBarController? I also make a call to get the profile data ahead of time.
The getProfilePhotos is a set of delegate and protocols, and returns the following way (I have verified that it correctly retrieves photoURLs). The debugger then thinks that there are no more methods to fire after this.
func feedProfilePhotosRetrieved(photoURLs: [String]) {
// Set photos array and reload the tableview
self.photoURLs = photoURLs
cardCollectionView.reloadData()
}
Here is my FeedViewController class, it's properties and viewDidLoad()/viewDidAppear()
var feedModel = FeedViewModel()
var associates = UserProfile.shared().associates
var photoURLs = [String]()
#IBOutlet weak var cardCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
associates = UserProfile.shared().associates
feedModel.delegate = self
cardCollectionView.delegate = self
cardCollectionView.dataSource = self
cardCollectionView.register(CardCollectionViewCell.self, forCellWithReuseIdentifier: "sectionCell")
cardCollectionView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
getProfilePhotos()
}
This is where I create the cells in the collection view. I put a breakpoint at the declaration of "cell", but it isn't firing.
extension FeedViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return associates.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sectionCell", for: indexPath) as! CardCollectionViewCell
let card = UserProfile.shared().associates[indexPath.row]
cell.name.text = card.name
cell.poscomp.text = card.title + ", " + card.company
// Photo that we're trying to display
let p = photoURLs[indexPath.row]
// Display photo
cell.downloadPhoto(p)
cell.layer.transform = animateCell(cellFrame: cell.frame)
return cell
} }
Are there any blatantly visible errors that I'm missing? Do I have to call the above function when reloadingData() as well? Thanks for your help and let me know if you need additional information.
The method numberOfItemsInSection should return photoURLs.count.
extension FeedViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photoURLs.count
}
I want my collectionView that's housed in AllWorkoutsVC to pass videoCode string data to the next viewController that's named VideoViewVC.
My collection view presents data correctly, however is the didSelectItemAt func i am having trouble with... this code runs fine and no warnings or errors are thrown, just that the variable myPassVideoCode not being passed to the target view controller
(i have removed some code for clarity..please ask for more if you feel is needed.)
collectionView class
class AllWorkoutsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var myPassVideoCode = String()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WorkoutsCell", for: indexPath) as? WorkoutsCell {
let workout = workouts[indexPath.row]
cell.updateViews(workout: workout)
print("this is my workouts: " + workout.videoCode)
return cell
} else {
return WorkoutsCell()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let chosenWorkout = DataService.instance.getAllWorkouts()[indexPath.row]
myPassVideoCode = String(chosenWorkout.videoCode)
print("this is my: " + myPassVideoCode)//Note: this whole print does not appear in the console either
performSegue(withIdentifier: "onPlayPressed2", sender: chosenWorkout)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "onPlayPressed2" {
let toNextVC = segue.destination as! VideoViewVC
toNextVC.myPassVideoCode = myPassVideoCode
}
}
}
Target Class...
class VideoViewVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
var myPassVideoCode = String()
#IBOutlet var videoPlayer: YouTubePlayerView!
override func viewDidLoad() {
super.viewDidLoad()
videoPlayer.loadVideoID(myPassVideoCode)
}
The issue is that you're declaring a new local constant with the same name myPassVideoCode in:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt...
You only need to set myPassVideoCode. So change this line:
let myPassVideoCode = String(chosenWorkout.videoCode)
to:
myPassVideoCode = String(chosenWorkout.videoCode)
You are making a new constant inside didSelectItemAt:
let myPassVideoCode = ...
So you are not storing the value to the variable myPassVideoCode that you declared in the beginning of the class definition.
I have a button inside a collection view cell and when pressed I want to go to another view controller and pass a string to that view controller. The only problem I'm having is with passing the data, I don't know how to check from which cell the button was clicked.
extension UserViewController: UICollectionViewDataSource{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PostCell", for: indexPath) as! UsersCollectionViewCell
//cell.post = posts[indexPath.item]
cell.User_Name.text = "\(self.posts[indexPath.item].firstname!) \(self.posts[indexPath.item].lastname!)"
cell.Country.text = self.posts[indexPath.item].Country
//user id is in the posts().uid
return cell
}
//the segue is already made in the storyboard, i am trying to pass the user id in the function below
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Testing1"{
var view = segue.destination as! ViewTheAccount
//user_ID = self.posts[indexPath.item].firstname
}
}
}
Add a string variable to you cell class and give it strValue that you want in cellForRow
// btn action in cell
#IBAction func btnClicked(_ sender: Any)
{
/// access cell and get it's string var , self = cell
/// here use delegate to push another view controller with self.strValue
}
Try this (I assume here that only single selection is allowed here, plus I am assuming that the segues are started by selecting a cell):
if segue.identifier == "Testing1" {
var view = segue.destination as! ViewTheAccount
if let itemIndex = collectionView.indexPathsForSelectedItems?.first?.item {
let selectedItem = self.posts[itemIndex]
// do here what you need
}
}
So one way to do this is to send the cell in the delegate call or callback that you are using.
class SomeViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var collectionView:UICollectionView!
collectionView(method that dequeues the cell){
let yourcell = collection view.dequeue(...) as! SomeCell
yourcell.somecallback = callback
}
func callback(cell: UICollectionViewCell){
//To find out which cell it is just
let indexPath = collection view.indexPathForCell(cell)
//YOU NOW know which cell this was sent from.
}
}
class SomeCell: UICollectionViewCell{
var somecallback:((UICollectionViewCell)->())?
func didPress(sender: UIButton){
somecallback(self)
}
}
I have a question that is very similar to the one found here, only I'm coding in Swift 2.0 (their question/answer is Objective-C), and my case is slightly different.
I have a UICollectionView that is essentially a contact list that pulls from core data. When the user selects a person (or an item within the UICollectionView), I want to present a detail view of the contact. I have that view/segue created and hooked up within the Storyboard, but I'm having trouble passing the selected item to the detail view ViewController so that it knows what data to query from core data.
Here is a snippet of my code with descriptions on each:
First, on my "FamilyCollectionViewController" I have the following viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Family")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.executeFetchRequest(fetchRequest)
userNames = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
Here is the cellForItemAtIndexPath method from the same view controller:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! FamilyCollectionViewCell
let person = userNames[indexPath.row]
cell.familyName!.text = person.valueForKey("name") as? String
print(userNames)
return cell
}
And here is the current didSelectItemAtIndexPath method (this may be where my problem is at, in combination with the prepareForSegue method):
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let selectedPerson = userNames[indexPath.row]
print(selectedPerson)
let selectedName = selectedPerson.valueForKey("name") as? String
let selectedNumber = selectedPerson.valueForKey("phone") as? String
let selectedEmail = selectedPerson.valueForKey("email") as? String
print(selectedName)
}
I attempted to create something similar to the answer as provided in the aforementioned linked question, but it is so laden with errors its not useful at all (the way I created it that is). I've passed data before from other views (using the prepareForSegue method), but the nuance of it now being from a UICollectionView and more, using core data, I'm left stumped. Any support is greatly appreciated.
Here's a complete example.
The contents of ViewController.swift
class ViewController: UICollectionViewController
{
var selectedIndex: Int!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ReuseID", forIndexPath: indexPath) as! CellCollectionViewCell
cell.contentView.backgroundColor = UIColor.whiteColor()
cell.label.text = "\(indexPath.row)"
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
selectedIndex = indexPath.item
performSegueWithIdentifier("OtherSegueID", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "OtherSegueID" {
let otherViewController = segue.destinationViewController as! OtherViewController
otherViewController.selectedIndex = selectedIndex
}
}
}
The contents of CellCollectionViewCell.swift
class CellCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
}
The contents of OtherViewController.swift
class OtherViewController: UIViewController {
var selectedIndex: Int!
override func viewDidLoad() {
super.viewDidLoad()
title = String(selectedIndex)
}
}