Find the indexPath in ViewController - ios

I segued to a new View Controller. This view controller contains array data from the corresponding CollectionViewCell. How do I change the data to present the previous cell array? Without having to go back to the last view controller and selecting the cell from there? I'm using Swift 3 Below is an image that explains what I mean
The first image is in the first View Controller. The second and third images are what's displayed when either of the first two in the list are tapped. You can go to the next index using the arrow keys in the second View Controller. How do I achieve this functionality?
let dataSource = DataSource()
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if inSearchMode {
return filteredAnimal.count
}
return dataSource.anim.filter{ $0.isDefault! }.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AnimIndex", for: indexPath as IndexPath) as! AnimIndex
let animal: Animal
if inSearchMode {
animal = filteredAnimal[indexPath.row]
} else {
animal = dataSource.anim.filter{ $0.isDefault! }[indexPath.row]
}
cell.configureCell(animal)
return cell
}

This is a very common UI pattern. You should search on master/detail. You should be able to find lots of sample projects that give examples of this UI pattern.
Don't think in terms of cells. Think in terms of a model object - a store for your app data. You need a model object that represents the entire list of data you present in your first view controller.
That model object should be reachable from all view controllers that need to display information from it. You can either pass around a reference to the model object or make it a singleton. There are advantages to either approach.
For collection views and table views, which are organized by section and row, an array of arrays of data objects is often a good structure for your model. If your data is in a single section then you can use a single array.
When the user taps on a cell in your first view controller, you would invoke the second view controller (Either from a segue or by manually invoking it.) You'd pass the second view controller the indexPath of the selected object, and might also pass a reference to your model object (or, like I said, you could make the model object a globally accessable singleton, in which case you wouldn't need to pass it.)
Then, in the second (or 3rd) view controller, if the user taps the up/down button, you have access to the model object that stores ALL the data your app is presenting, so you can navigate to the next object from within any view controller. If your data is organzied in sections and rows or rows and columns you might want to add next/previous item methods to your data model so you only have to write that logic once.

Related

Is there a way to -preload- UICollectionViewCell?

Something that bother me for a long time.
Is there a way to preload UICollectionViewCell ?
something to prepare before loading the data, so the cell will not be create while user is scrolling.
Is there a way to take Full control on WHEN to create the UICollectionViewCell and WHEN to destroy the UICollectionViewCell.
From Apple documentation:
You typically do not create instances of this class yourself. Instead, you register your specific cell subclass (or a nib file containing a configured instance of your class) with the collection view object. When you want a new instance of your cell class, call the dequeueReusableCell(withReuseIdentifier:for:) method of the collection view object to retrieve one.
The cell itself is not a heavy resource, so it makes a little sense to customize its lifetime.
I think that instead of searching for a way to take a control over cell creation, you should ask yourself: why do you want to preload a cell? What kind of heavy resource would you like to preload?
Depending on the answer you can try following optimizations:
If you have complex view hierarchy in your cell, consider refactoring from Autolayout to manual setting frames
If your cell should display results of some complex computations or remote images, you would like to have a separate architecture layer for loading these resources and shouldn't do it in cell class anyway. Use caching when necessary.
You can blackout your screen while you enter into scene and navigate (scroll) to each cell type. When you did scroll each cell you should hide blackout view and present collection view on first cell (top).
It is some kind of workaround. My cells have a lot resources like images, which lag while scrolling collection view.
I can add some actual approach
If you need to preload some data from internet / core data checkout https://developer.apple.com/documentation/uikit/uicollectionviewdatasourceprefetching/prefetching_collection_view_data
tl;dr
first you need your collection view datasource to conform UICollectionViewDataSourcePrefetching in addition to UICollectionViewDataSource protocol
then prefetch your data and store it in cache
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
// Begin asynchronously fetching data for the requested index paths.
for indexPath in indexPaths {
let model = models[indexPath.row]
asyncFetcher.fetchAsync(model.id)
}
}
and finally feed your cell with your cached data
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as? Cell else {
fatalError("Expected `\(Cell.self)` type for reuseIdentifier \(Cell.reuseIdentifier). Check the configuration in Main.storyboard.")
}
let model = models[indexPath.row]
let id = model.id
cell.representedId = id
// Check if the `asyncFetcher` has already fetched data for the specified identifier.
if let fetchedData = asyncFetcher.fetchedData(for: id) {
// The data has already been fetched and cached; use it to configure the cell.
cell.configure(with: fetchedData)
else
... async fetch and configure in dispatch main asyncd
Also u can approach some workaround using delegate methods to handle willDisplay, didEndDisplay events e.g. https://developer.apple.com/documentation/uikit/uicollectionviewdelegate/1618087-collectionview?language=objc

Change variable based on Table View Cell

I need an event, that changes a variable based on which TableViewCell I click. But unlike an action connected to a button, there is no action indicator for table view cells at all. So my question is:
I want to make a TableView that contains items of an array. Based on which item I click, I want to change my variable so that the result on the next ViewController depends on which button you click.
So to make things easier, here is an example what I want the app to look like:
On the first TableViewController I have a list based on an array and on the second ViewController I have a label that shows text based on the variable.
I have a nameArray = ["Mum", "Brother", "Me"] and a weightArray = [140, 160, 120] and a variable weight = 0. The label on the second ViewController tells the var weight. So when you click on "Mum" in the TableView I want the next ViewController to say 140, when I click on "Brother" then 160 and so on...
Until here everything works just fine and I have no problems with anything but changing the var based on what I click.
Long story, short sense:
I want an Action for the TableViewCell that changes the var like in an Action connected to a Button, but there is no Action outlet for Cells at all.
Use this method. Use indexPath.row to find what row number you selected
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell : UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell.labelSubView.text as! String {
case "Mum":
self.weight = weightArray[0]
case "Brother"
self.weight = weightArray[1]
and so on..
..
default:
statements
}
}
Note A better alternative
I also considered a case where you have too many entries in nameArray and switch statement might not be good. In that case you can get the text inside the selected row by cell.labelSubView.text as! String
next you can check if the nameArray contains the cell text and get the index of the name that matches the cell text. Next you can get the required weight at the same index in weightArray. And then do self.weight = weightArray[requiredIndex]
Hope this helps.
Update : My experienced friend #Duncan mentioned down below that switch statement in this case is a bad coding practice . I am not going to delete it because it is a lesson for me and also my fellow programmers who are relatively new to programming. So i have put it in a yellow box, stating that it is not a good code
A better option for this would be :
As Duncan mentions, creating an array of dictionary is a good option
Second option is the option in my answer after my Note
You need to maintain array of dictionaries , those dictionaries have keys like "person", and "weight", then you can easily get weight value after selecting the cell by using table view delegate method UITableViewDelegate's tableView:didSelectRowAtIndexPath:
Create an instance variable in your view controller (a var at the top level after the class definition) for the selected cell.
class MyTableViewController: UIViewController
var selectedRow: Int
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath)
{
selectedRow = indexPath.row
//invoke a segue if desired
performSegueWithIdentifier("someSegue");
}
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?)
{
if segue.identifier == "someSegue"
{
//Cast the destination view controller to the appropriate class
let destVC = DestVCClass(segue.destinationViewController)
destVC.selectedRow = selectedRow
}
}
As Andey says in his answer, it's probably better to create a single array of data objects (dictionaries, structs, or custom data objects). Then when the user taps a cell, instead of passing the index of the selected row to the next view controller, you could pass the whole data object to the destination view controller. Then the destination view controller could extract whatever data it needed. (Weight, in your example.)

Save a UITextField into a UICollectionViewCell

I have an app that allows the user to create categories. A save screen appears with a UITextField. Although, I do not know how to save the user's entry, and allow a new UICollectionViewCell to be added into the current UICollectionView that contains the UITextField words that the user typed. Thanks!! This is also in Swift. This would be used in a UITableView: clothes.name = self.nameTextField.text ,but how would I convert this to a UICollectionView? **clothes is a variable
Your collection view has a data source where you initially have the model for your cells. In your case it's probably an array. The collection view looks at this data source and returns cells based on the entries contained in this data source.
What I am trying to get at is the following:
In order to show the newly created cell, you'll need to update your data source (I'll call it "the blueprint specification" the collection view adheres to) by adding the new cell's "specification" (I think I'm wording this in a more complex fashion than it actually is). Here is an example (If we assume that your data source is an array):
categoryArray.append(CategoryModel(title: yourTextField.text)) // update the data source
After you make changes to the data source you can tell the collection view to reload it's data:
collectionView.reloadData()
That will "parse" the data source to display the cells anew.
Let me know if anything is unclear.
EDIT
Regarding your comment -- given that you use a UINavigationController -- , you can achieve displaying the title like this:
You are probably using this method to go to your next view controller:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let category = categoriesArray[indexPath.row]
let detailVC = DetailViewController()
detailVC.title = category.title // your title
self.navigationController?.pushViewController(detailVC, animated: true, completion: nil)
}
Alternatively, in your DetailViewController you can do:
class DetailViewController : UIViewController{
var category : Category!
override func viewWillAppear(){
super.viewWillAppear()
self.title = self.category.title
}
}

Loading coredata for two collection views and one table view in swift

So I have this issue where I have to load one entity on two collection views, and one table view. The thing is, primarily I just had to load one entity on the VC where the tableview is located. And that data entity has these parameters for i.e.:
Engine type (diesel or petrol)
Car color
Max.velocity
Year of production
Now, the main point was, to color the cells on the tableview depending on the type of the car engine. So, if the data Entity "Car" has a Bool value of "isEngineDiesel" = true, then the cell would be orange, if false, then it would be light blue. And this worked just fine, a simple if statement on the table view delegate method for loading such cells. But, now I had to implement another VC which has two collection views, in which, the first one loads ONLY Diesel engine Car entity's, and the other Petrol type.
So I guess the issue is already clear here. How can I accomplish this? Because after countless hours of experimenting the only idea I had was two make TWO entity's in which the one is DieselCar and the other PetrolCar. But that means changing the complete data structure, and also, instead of one table view I would actually need two, which doesn't seem like a good idea due the fact that it would "overflow" with all the data there is.
So...any ideas gents?
EDIT:
So far I've only managed to get the cell titles, but the return value of number of cells is still a mystery on how to solve.
The Code for collection view delegate:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CollectionViewCell
if let carData = fetchedResultsController?.objectAtIndexPath(indexPath) as? Car {
cell.layer.cornerRadius = 20
cell.layer.masksToBounds = true
if collectionView == collectionViewDiesel {
if carData.isEngineDiesel == true {
cell.backgroundColor = UIColor(netHex: 0x8DF060)
// Display the cell name
cell.cellTitle.text = carData.cellTitle
}
}else if collectionView == collectionViewPetrol {
if carData.isCarDiesel == false {
cell.backgroundColor = UIColor(netHex: 0xEB9B2D)
// Display the cell name
cell.cellTitle.text = carData.cellTitle
}
}
}
return cell
}
And this is the method I need answered:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
On the other VC you need data structure for two collection views so I think make a dictionary from your data base on the isEngineDiesel if true add objects in one array and if false in another array then set those arrays for the respective diesel and petrol keys .
In you collection view delegate there always a UICollectionView argument place check on that (you can use tags or even == operator if you have outlet) to load the data from dictionary base on keys .

How to build swipe view controller in swift

I have a UITableViewController and when I click on it, it will show a DetailViewUIController.
I want to add a functionality which when I am in DetailViewUIController and I swipe to right, it will show the DetailViewUIController of next item and when left, it will show the previous item.
I find a link which kind of do that in swift. But it only has 3 static subviewcontroller.
https://medium.com/swift-programming/ios-swipe-view-with-swift-44fa83a2e078
The number of entries in my TableView can be pretty long, how can I do the same thing dynamically, i.e. without having static number of subviewcontroller created and add as 'addChildViewController'?
Update:
Thanks again #rdelmar for your help.
I am having trouble getting the ' set the contentOffset of the collection view to (collection view width) * selectedRow' to work.
In my prepareForSegue function, I have added:
x = Float(indexPath.row) * 375.0
var point = CGPointMake(CGFloat(x), 0)
println ("***x=")
println (x)
detailController.collectionView?.setContentOffset(point , animated: false)
where detailController is UICollectionViewController.
and in the
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as DetailViewCell
// Configure the cell
println("DetailViewController in cellForItemAtIndexPath()")
println(indexPath.row)
cell.myitem = allItems[indexPath.row]
return cell
}
And I always see cellForItemAtIndexPath trying to get 0th element of the allItems (that is the whole collections of all my objects.
So setContentOffset() does not change what I am displaying in the Detail View regardless which item I click in my TableView to launch the Detail View.
Any idea to solve this?
Thank you.
There are a lot of different ways you could do this. One way would be to make your detail view controller's view be a collection view (with paging enabled), whose cells are the same size as the screen. You would need to pass in the array that you use to populate your table so the collection view could populate its cells. This would be quite efficient, since the collection view would only ever need to instantiate two cells.

Resources