Ios Swift (UITableView with several level) - swift

I am new developer in Swift.
I did a single ViewController, with UITableView inside --> list management.
User can add/modify information in the list.
Seems to be OK.
--> Information are stored in DB (CoreData : entity/attributes)
example :
table 1 :
Entity = type of food
Attributes = name : string
Question :
I don't know how to create a view were I can have 2 levels of TableView.
Example :
table 1 :
Entity = type of food
Attributes = name : string
example : vegetables, meat, fruit, ...
in the view, user can add a new kind of food himself (example fish)
table 2 :
Entity = Aliment
Attributes = name : string
example : beef <-- (meat), banana <-- (fruit) etc ...).
but linked with table 1 (beef is meat and not fruit)
how to manage this kind of list in an UIview :
create/modify/delete/display a type of food and add some "aliment" in it ?
like a two level list and list2 is linked with the list 1 ?
Any example in swift ?

That's actually simple, you just have to create a new instance of tableview and add it as a subview of your cell in your cellForRowAtIndexPath method.
Example :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
let tableview = UITableView(frame: CGRectMake(0, 0, cell.frame.width, 50)
cell.addSubview(tableview)
return cell
}

Related

Model and ViewModel Relation in Swift (MVVM)

I'm trying to learn MVVM pattern for almost a month and got a little bit struggling with a certain scenario :
There is an array of boolean = arrayOfItems
There are 3 TableViewController inside a PageViewController = table A B C
ABC will show data from an arrayOfItems, so all of them will show the same data
A has removeData function, if it is triggered then BC should be updated
ABC have selectItem function (cell being selected), let says that ABC got its own array to control isSelectedItem, so when it is triggered it only impact the current tableVC
arrayOfItems is being updated every 10 seconds, if it is updated ABC should refreshAllData without changing isSelectedItem array
So far what I have done are :
Create a model called Item which has: var dataList: [Bool] = [] and a removeItemAtIndex func
Create ItemProtocol which has: var isSelectedItems: [Bool] { get }
Create ItemViewModel conform ItemProtocol:
class ItemViewModel: NSObject, ItemProtocol {
var item: Item
// MARK: ItemProtocol
var isSelected: [Bool] = []
// MARK: Init
init(withItem item: Item) {
self.item = item
for _ in self.item.dataList {
self.isSelected.append(true)
}
}
func updateIsSelected(isSelected: Bool, index: Int) {
self.isSelected[index] = isSelected
}
}
and then inside my pageVC i will assign :
A.viewModel = ItemViewModel(withItem: self.item)
B.viewModel = ItemViewModel(withItem: self.item)
C.viewModel = ItemViewModel(withItem: self.item)
the item is coming from previous page, but in pageVC we need to monitor its status so item will updated every 10s
The problem when I use my approach is when I remove the an item on A, BC will not updated.
the question is what is the best approach to control arrayOfItem without disrupting the isSelectedItems using MVVM pattern, and where should I call updateItem? in model Item or in the ItemViewModel?

How to get multidimensional array working in tableview

I am creating an iOS Swift 3 app and in this app I have a tableview with data coming from an API. I want to display this data in the tableview but I want to do it grouped by firstname. I have managed to first group the data (see below) but xCode is complaining that XXX.
This is the declaration of the sections:
var sections = [String: [User]]()
This is how I group the data:
self.sections = Dictionary(grouping: self.contacts.filter({ !$0.firstname!.isEmpty })) {
$0.firstname!.prefix(1).capitalized
}
This is my output:
["D": [motto.User(id: Optional(1), firstname: Optional("Dan"), lastname: Optional("Meeler"), email: Optional("time#example.com"))], "M": [coiqo.User(id: Optional(3), firstname: Optional("Mia"), lastname: Optional("Kallas"), email: Optional("mia#ka.com"))]]
This is the error I got:
Cannot subscript a value of type '[String : [User]]' with an index of type 'Int'
In this function for tableview:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
How can I get this array to work in a tableview?
Update
I just had to add this to dasblinkenlight answer and it worked!
if(groupKeys.count > 0) {
return sections[groupKeys[section]]!.count
} else {
return sections.count
}
Your self.sections is Dictionary, and dictionaries are unordered.
Once you've made user groups from API results, make a separate array composed of group keys:
let groupKeys : [String] = Array(self.sections.keys)
Sort that array in the way that you wish your sections to appear (alphabetical, reverse alphabetical, natural, etc.)
Now you can write your function like this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[groupKeys[section]].count
}

How to get a user attribute from an AWS Cognito user pool in Swift?

I'm using AWS Cognito user pools with Amazon's Swift sample app. I'm able to create users with a given_name attribute, but it's not obvious how to later retrieve the given_name.
The Amazon sample retrieves attributes as a AWSCognitoIdentityUserGetDetailsResponse and then dumps them to the screen. However, I can't find documentation for AWSCognitoIdentityUserGetDetailsResponse. It appears to be something of an array, but it's not obvious to me how to just pull out a given_name from the returned attributes. One would think that returning attributes as a dictionary would be a good idea, but it doesn't appear that Amazon did things that way.
Any pointers?
EDIT: To clarify, what's returned is an array of AttributeType objects. Here's code in the Cognito sample which displays all returned attributes:
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "attribute", for: indexPath)
let userAttribute = self.response?.userAttributes![indexPath.row]
cell.textLabel!.text = userAttribute?.name
cell.detailTextLabel!.text = userAttribute?.value
return cell
}
Here's the raw response:
Response body:
{"UserAttributes":[{"Name":"sub","Value":"XXXXXXXX-XXXX-XXXX-XXXX-
XXXXXXXXXXXX"},{"Name":"email_verified","Value":"true"},
{"Name":"given_name","Value":"Bob"},
{"Name":"email","Value":"bob#example.com"}],"Username":"AAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"}
It's just not obvious to me how to pull out given_name without iterating through the whole array.
Not an iOS expert here, but from what I can see in the SDK implementation, it looks like they copy the details from the AWSCognitoIdentityProviderGetUserResponse, which documentation shows it has the user attributes in the form of a map. Did you try to look for an userAttributes array in the response?
Also, the raw GetUser API says that the UserAttributes should be in the response.
Here's an example of using getDetails() to access userAttributes
self.user?.getDetails().continueOnSuccessWith { (task) -> AnyObject? in // handle all auth setup
DispatchQueue.main.async(execute: {
self.response = task.result // AWSCognitoIdentityUserGetDetailsResponse
if let attributes = task.result?.userAttributes { // https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
for attribute in attributes {
print(attribute.name, attribute.value)
if attribute.name == "name" {
// ... do something with name
}
}
}
})
return task
}
}

How to trigger function by using check boxes in nstableview, in swift

I have nstableview in mac application. One column has names, second - numbers and third has checkboxes. When user make checkbox ON this should trigger function according to the name in first column in the same row. To have the name from first column (for triggering function) I need to know row index, and I use function rowForView(_ view: NSView). Question is what I should use as parameter (_ view: NSView) in this function. Below is the code. I am not sure if I am going in right direction to get what I have described above.
Related question is how I can get array of names from first column of table. I need this because if I will sort table according to the name then I cannot use my array, which used as data to fill the nstableview, I need array of sorted names to correctly trigger function.
#IBOutlet weak var myCheckbox: NSButton!
#IBAction func checkboxToShowRestrctaseSites(sender: AnyObject) {
if myCheckbox.state == NSOnState
{
let objectOfPresentRestrictase = RestrictasesSorting(restrictase: "", dna: inputDnaFromUser.string!)
var tableOfNumberedRestrictase = objectOfPresentRestrictase.makeListOfPresentRestrictase()
var listOfAllRestrictaseWithSites = objectOfPresentRestrictase.searchForRestrictionSiteInList().1
var row = myTable.rowForView(_ view: NSView)
var name = Array(tableOfNumberedRestrictase.keys)[row]
var site = listOfAllRestrictaseWithSites[name]
UPDATE
I have found that triggering function start to work if I remove if myCheckbox.state == NSOnState, and when I used sender as NSView as parameter for function rowForView. To control the ON and Off state I can also use sender in if condition. Code below.
#IBAction func checkboxToShowRestrctaseSites(sender: AnyObject){
if sender.state == NSOnState
{
let objectOfPresentRestrictase = RestrictasesSorting(restrictase: "", dna: inputDnaFromUser.string!)
var tableOfNumberedRestrictase = objectOfPresentRestrictase.makeListOfPresentRestrictase()
var listOfAllRestrictaseWithSites = objectOfPresentRestrictase.searchForRestrictionSiteInList().1
var row = myTable.rowForView(sender as! NSView)
var name = Array(tableOfNumberedRestrictase.keys)[row]
var site = listOfAllRestrictaseWithSites[name]
The documentation of rowForView:
Return Value
The index of the row corresponding to the view. Returns -1 if view is not an instance of NSTableRowView or a subview of an instance of NSTableRowView. In other words, if view is not in a table view, this method returns -1. (Note that this method may also return -1 when a row is being animated away, because view no longer has a valid row.).
Sounds like you can use the checkbox.
Your datasource does the sorting and has the data for the first column.

Retrieve Values from Array with class

I have a custom class defined as such:
public class Location {
var name = String()
var address = String()
var place = String()
}
I'm then populating an Array using that class as follows:
var chosenLocation = [Location]()
var locationResult = Location()
//Code to parse data here//
for result in results! {
locationResult.name = result["name"] as String
locationResult.address = "Bogus Address"
locationResult.place = result["place"] as String
chosenLocation.append(locationResult)
}
This all seems to be working fine, but when I try to get the individual "name" value in cellForRowAtIndexPath I just get the last record over and over. I guess I just don't understand how to reference each entry since it's a class wrapped in an array. The code I believe is in question, and which is returning the same row over and over is:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Default, reuseIdentifier:"cell")
var locationAnswer = Location()
locationAnswer = chosenLocation[indexPath.row
cell.textLabel?.text = locationAnswer.name
return cell
}
I believe it's getting appended to chosenLocation correctly, but since I don't know how to "unwrap" it , a println only shows me that I have the correct number of values and not what's in them.
Thanks a bunch for any help you can provide!
It looks like the bug is that just a single Location object is created and updated, so it contains the data from the very last update
Move the creation to be within the for loop...
// var locationResult = Location() <- Remove this
for result in results! {
var locationResult = Location() // <- Add it here
...
#Jawwad provided a solution to the problem.
Note that your code doesn't work because the item you are adding to the array is an instance of a reference type (class), so you are instantiating once, initializing at each iteration, then adding to the array - but what's added is just a copy of the reference to the instance, and not the instance itself.
Your code would work just fine if you turn the Location class into a struct. Being value types, structs are passed by value and not by reference, so the action of passing the same instance to the append method results in a copy of that instance to be created and passed.