How is better to manage data details (Swift) - ios

I want to do a little list of data in TableView, and a DetailView.User tap on cell and goes to DetailView. In DetailView I have to check, what kind of data user selected in TableView. I pass name of data (title of cell) and Index(by prepareForSegue method). I can check name of data by two ways: by index, or by name (by switch). Which Way is better. My code for IndexCheck is something like this:
let Details = ["first","second","third"]
var PassedIndex:Int
override func ViewDidLoad () {
super.ViewDidLoad()
self.DetailLabel.text = Details[PassedIndex]
}
And a check by name is something like this:
var PassedName:String?
var DetailText:String
switch PassedName {
case "NumberOne":
DetailText = "#1"
case "NumberTwo":
DetailText = "#2"
case "NumberThree":
DetailText = "#3"
default:
DetailText = "Unknown number"
}
override func ViewDidLoad () {
super.ViewDidLoad()
self.DetailLabel.text = DetailText
}
Which way is the best? Which makes system working faster? I am just a beginner, so I need an advice from more experienced programmer than I

The best way of your two methods to check Details is check-by-index method. Why? Because it takes O(1) to take value that you need. Check by String takes O(n) in the worst case. But i suggest you to create an Enum that contains your Details and pass exactly detail that you need.

Related

How can I determine the index path for the currently focused UITableViewCell using Voice Over?

I have a dynamic UITableView. For each cell, I add a UIAccessibilityCustomAction. When the action fires, I need to know the index path so I can respond accordingly and update my model.
In tableView(_:cellForRowAt:) I add my UIAccessibilityCustomAction like this...
cell.accessibilityCustomActions = [
UIAccessibilityCustomAction(
name: "Really Bad Name",
target: self,
selector: #selector(doSomething)
)
]
I have tried to use UIAccessibility.focusedElement to no avail...
#objc private func doSomething() {
let focusedCell = UIAccessibility.focusedElement(using: UIAccessibility.AssistiveTechnologyIdentifier.notificationVoiceOver) as! UITableViewCell
// Do something with the cell, like find the indexPath.
}
The problem is that casting to a cell fails. The debugger says that the return value type is actually a UITableTextAccessibilityElement, which I could find no information on.
When the action fires, I need to know the index path so I can respond accordingly and update my model.
The best way to reach your goal is to use the UIAccessibilityFocus informal protocol methods by overriding them in your object directly (the table view cell class in your case): you'll be able to catch the needed index path when a custom action is fired.
I suggest to take a look at this answer dealing with catching accessibility focus changed that contains a detailed solution with code snippets if need be.😉
Example snippet...
class SomeCell: UITableViewCell
override open func accessibilityElementDidBecomeFocused() {
// Notify view controller however you want (delegation, closure, etc.)
}
}
I ended up having to solve this myself to bodge an Apple bug. You've likely solved this problem, but this is an option similar to your first suggestion.
func accessibilityCurrentlySelectedIndexPath() -> IndexPath? {
let focusedElement:Any
if let voiceOverObject = UIAccessibility.focusedElement(using: UIAccessibility.AssistiveTechnologyIdentifier.notificationVoiceOver) {
focusedElement = voiceOverObject
} else if let switchControlObject = UIAccessibility.focusedElement(using: UIAccessibility.AssistiveTechnologyIdentifier.notificationSwitchControl) {
focusedElement = switchControlObject
} else {
return nil
}
let accessibilityScreenFrame:CGRect
if let view = focusedElement as? UIView {
accessibilityScreenFrame = view.accessibilityFrame
} else if let accessibilityElement = focusedElement as? UIAccessibilityElement {
accessibilityScreenFrame = accessibilityElement.accessibilityFrame
} else {
return nil
}
let tableViewPoint = UIApplication.shared.keyWindow!.convert(accessibilityScreenFrame.origin, to: tableView)
return tableView.indexPathForRow(at: tableViewPoint)
}
What we're essentially doing here is getting the focused rect (in screen coordinates) and then translating it back to the table view's coordinate space. We can then ask the table view for the indexpath which contains that point. Simple and sweet, though if you're using multi-window you may need to swap UIApplication.shared.keyWindow! with something more appropriate. Note that we deal with the issue you faced where the element was a UITableTextAccessibilityElement when we handle UIAccessibilityElement since UITableTextAccessibilityElement is a private, internal Apple class.

How can I make an if statement to determine what array to use in a tableView?

Using xcode 9.4, Swift 4 and ios 11, I want to select an item from a collection view screen that goes to a table view screen. On the table view screen, I have a series of arrays and, depending on what I've chosen on the collection view screen, I want to display a specific array on the table view screen.
I've already set up the prepareForSegue function on the collection view screen that references a var on the table view, but I'm a bit stuck on the "select an array" bit. I'm pretty sure it involves an if statement, but I'm not sure how or where to put this if statement.
Any help would be greatly appreciated.
Here's the if statement that I made (I don't know if its correct):
if cellItem == "jeans" {
tableArray.append(jeanArray[])
} else if cellItem == "skirts" {
tableArray.append(skirtArray[])
}
Then in the functions for the table set up, I've referenced tableArray.
There are a number of ways you can do this, I'm guessing the arrays contain similar information (guessing from jeans and skirts its clothing).
I would have a single value for determining which product type is to be displayed, then you have a few options available....
enum TableMode {
case jeans, skirts, trousers, hats
}
The multiple products array way (not recommended)
class TableViewController: UITableViewController {
var tableMode: TableMode
func tableView(UITableView, numberOfRowsInSection: Int) -> Int {
switch tableMode {
case .jeans:
return jeansArray.count
/// and so on...
}
}
}
Another option would be to have a single array that will be used, just fill it with the correct data: (best method)
// the int value could be the category ID from your API
enum TableMode: Int {
case jeans = 13
case skirts = 2
trousers = 4
hats = 19
}
class TableViewController: UITableViewController {
var tableMode: TableMode
var products: [Products]()
override viewDidLoad() {
super.viewDidLoad()
// fetch all data from api/database where categoryId = .tableMode value
self.products = coreDataFetchResult
self.tableView.reloadData()
}
Last idea, fetch all products and just filter it as needed to show the current product type selection
class TableViewController: UITableViewController {
var tableMode: TableMode
var products: [Products]()
override viewDidLoad() {
super.viewDidLoad()
// fetch all data from core data
self.products = coreDataFetchResult.filter { $0.categoryId = self.tableMode }
self.tableView.reloadData()
}
The best option depends on where you are filling the arrays from. I wouldn't suggest having multiple arrays if you are only going to use one.

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.)

Working with Multiple Segues | How to use Cell data to perform specific Segues

I am displaying data in a collection view, I know how to pass the data on with prepareForSegue function but am trying to have the app determine which segue to use depending on the cell property data. (Each segue goes to a different view controller to display relevant information.)
For e.g.
If the cell.type is equal to "1" then perform segueOne if it is of type "2" then perform segueTwo.
I was trying to do something like this;
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CollectionViewCell
if cell[indexPath].type = "1" {
performSegueWithIdentifier("showPage1", sender: self)
} else if self.cell[indexPath].type = "2" {
performSegueWithIdentifier("showPage2", sender: self)
} else { println("error when selecting cell to segue") }
}
However with this I get an error;
'CollectionViewCell' does not have a member named Subscript
Has anybody got any ideas ?
Assuming the items in your collection view can be re-arranged (or might be some time in the future), the indexPath will not be sufficient to give you the information which cell was selected. Thus, IMO your idea to give the cell a property is a feasible one.
The easiest "quick and dirty" way is to simply hardcode the segue identifier string into your cell. This is not the best design because you are introducing dependencies between app elements that should know of each other.
class MyCell : UICollectionViewCell {
var segue = "DefaultSegue"
}
Now calling the appropriate segue is really easy in didSelectItemAtIndexPath...
self.performSegueWithIdentifier(cell.segue, sender:cell)
It would of course be preferable to use an enum. Safer, more readable and better maintainable.
enum Segue : String {
case ToInfo = "SegueToInfo"
case ToLogin = "SegueToLogin"
// etc.
}
The ivar for MyCell would now be var : Segue = SomeDefaultValue and you can call it the same way.
BTW: Regarding your original question please note the following: as has been pointed out, you cannot subscript a cell. (UICollectionViewCell is not a Dictionary, so cell["key"] does not make sense.) Also, I am not a fan of dequeueing the cell in more than one place - instead you could call cellForItemAtIndexPath or do the work in that method in the first place, as I have suggested.
You're trying to index into a UICollectionViewCell, but of course that class is not an array, so you can't 'subscript' it.
My suggestion is to refactor your code. Whatever data you're storing in your cell you can presumably get from your data model, because that's where it originally came from. You are probably putting that in your cell in cellForIndexPath.
If that is the case, then there is no reason you can't get the same data from the same place in your func ... shouldSelectItemAtIndexPath ... -> Bool. I'd suggest doing it there. Your cell should only contain the data it needs to properly render itself to the screen.
See if that helps.

How to dynamically add elements to UICollectionView as they are loaded from REST service (Swift)

How can elements be added dynamically to an UICollectionView as they are loaded into memory from an REST service?
Right now I'm calling
.reloadData()
on my UICollectionView IBOutlet, but I do not believe that this is best practice.
I've tried with performBatchUpdate, but I didn't manage to get it to work.
self.photoCollView.performBatchUpdates({
self.photoCollView.insertItemsAtIndexPaths(NSArray(object: photo) as [AnyObject])
}, completion: nil)
Here I'm trying to insert a photo object into the photoCollView which is an IBOutlet of UICollectionVIew.
#IBOutlet var photoCollView: UICollectionView!
I would appreciate an example.
I found the solution.
This is my connection to the UICollectionView
#IBOutlet var photoCollView: UICollectionView!
My Photo array is a simple Swift collection of Photo objects
var photos = [Photo]()
which is a simple class
class Photo {
let name: String
let url: String
init(name:String, url:String){
self.name = name
self.url = url
}
}
And this is how I added elements to this collectionView without the use of reloadData() method.
self.photoCollView.performBatchUpdates({
let lastItem = self.photos.count
self.photos.append(photo)
let indexPaths = let indexPaths = map(lastItem..<self.photos.count)
{
NSIndexPath(forItem: $0, inSection: 0)
}
, completion: nil)
Unfortunately I doesn't know exactly whats happening as I'm rather new to the Swift language. But from what I can understand I'm holding on to the array count before adding a new element. Then I'm invoking map with two arguments, the old count and the new one. Inside the map closure a new NSIndexPath is created inSection: 0, which is what I want, because right now I'm only holding 1 section all together.
Those two things I can't understand is what lastItem..<self.photos.count> and forItem: $0 does.
There's many approachs, you can use reloadData() function if you are doing small amount of changes. How small they are, you need to do a performance test.
Another approach to do is perform batch updates, they can be "segmented" in small amounts, you don't need to input all the data which came from the rest service in one input.
Check more about it in the CollectionView Programing Guide by Apple
By the way, the reload data can deal with it pretty good, it's up to you and your needs.

Resources