I'm trying to create tableview where the arrays are being sorted and put in there respective sections. I followed this tutorial: http://www.yudiz.com/creating-tableview-with-section-indexes/
I managed to make the first one work where the tableview arrays are sorted even though the sections without data still appear.
The second one is about solving the problem in which the sections without data still appear which did not work for me.
Upon following the second one, I could not run it because of this error
'[MyContact]' is not convertible to '[AnyObject]'
Here is my code:
Model for contact:
class MyContact: NSObject {
#objc var name: String!
#objc var mobile: String!
init(name: String, mob: String) {
self.name = name
self.mobile = mob
}
}
Extension for partitioning arrays into sorted subcategories
extension UILocalizedIndexedCollation {
func partitionObjects(array: [AnyObject], collationStringSelector: Selector) -> ([AnyObject], [String]) {
var unsortedSections = [[AnyObject]]()
for _ in self.sectionTitles {
unsortedSections.append([])
}
for item in array {
let index: Int = self.section(for: item, collationStringSelector: collationStringSelector)
unsortedSections[index].append(item)
}
var sectionTitles = [String]()
var sections = [AnyObject]()
for index in 0 ..< unsortedSections.count {
if unsortedSections[index].count > 0 {
sectionTitles.append(self.sectionTitles[index])
sections.append(self.sortedArray(from: unsortedSections[index], collationStringSelector: collationStringSelector) as AnyObject)
}
}
return (sections, sectionTitles)
}
}
Tuple for data source and the line which has the error
let (arrayContacts, arrayTitles) = collation.partitionObjects(array: self.myContacts, collationStringSelector: #selector(getter: MyContact.name)) as! [[MyContact]]
You're trying to force cast a tuple into an array of arrays.
let (arrayContacts, arrayTitles) = collation.partitionObjects(array: self.myContacts, collationStringSelector: #selector(getter: MyContact.name))
will return a tuple of type ([AnyObject], [String]).
Also, you shouldn't be using AnyObject unless you really need something to be a class type. You can re-write like this:
extension UILocalizedIndexedCollation {
func partitionObjects(array: [Any], collationStringSelector: Selector) -> ([Any], [String]) {
var unsortedSections = [[Any]](repeating: [], count: self.sectionTitles.count)
for item in array {
let index = self.section(for: item, collationStringSelector: collationStringSelector)
unsortedSections[index].append(item)
}
var sectionTitles = [String]()
var sections = [Any]()
for index in 0..<unsortedSections.count {
if unsortedSections[index].isEmpty == false {
sectionTitles.append(self.sectionTitles[index])
sections.append(self.sortedArray(from: unsortedSections[index], collationStringSelector: collationStringSelector))
}
}
return (sections, sectionTitles)
}
}
That way you could write MyContact as a struct and it would still work with this function.
Related
I'm having a hard time trying to display/hide my collectionview cells depending on the items child nodes in FB realtime database. The problem consists of three parts: FB database, a collectionview and a segmented control. My goal is to show different cells in the collectionview depending on whether a the item has a child with a certain string value.
My database looks like this:
Items
category1
item1
name: item1
imageUrl: item1Url
logic
one
three
item2
name: item1
imageUrl: item1Url
logic
two
three
category2
item1
name: item1
imageUrl: item1Url
logic
two
four
item2
name: item1
imageUrl: item1Url
logic
one
two
I also have a custom Product class to display my items in their cells:
class Product {
var category: String?
var name: String?
var imageUrl: String?
init(rawData: [String: AnyObject]) {
name = rawData["name"] as? String
imageUrl = rawData["imageUrl"] as? String
category = rawData["category"] as? String
}
}
I load my items from firebase database with this funciton:
func loadCategoryName() {
ref = Database.database().reference().child("Items").child(selectedCategoryFromPreviousVC)
ref.observeSingleEvent(of: .value) { (snapshot) in
if let data = snapshot.value as? [String: AnyObject] {
self.itemArray = []
let rawValues = Array(data.values)
for item in rawValues {
let product = Product(rawData: item as! [String: AnyObject])
product.category = self.selectedCategoryFromPreviousVC
self.itemArray.append(product)
}
// Sort item array by rating; if rating is same, sort by name
self.itemArray.sort { (s1, s2) -> Bool in
if s1.rating == s2.rating {
return s1.name! < s2.name!
} else {
return s1.rating > s2.rating
}
}
self.collectionView?.reloadData()
}
}
}
My itemArray now contains all my items as custom Products and I can display them in their cell.
My segmented control:
func someFunc() {
let segmentController = UISegmentedControl(items: ["one", "two", "three", "four"])
segmentController.selectedSegmentIndex = 0
self.navigationItem.titleView = segmentController
segmentController.addTarget(self, action: #selector(handleSegment), for: .valueChanged)
}
#objc fileprivate func handleSegment() {
print(segmentController.selectedSegmentIndex)
}
with the handleSegment function I'm able to print out which segment has been selected. But this is where the problems occur. I've tried creating new arrays to split the items so that items are in an array depending on their "logic" child nodes. However I'm not able to make the arrays of type Product, so that I can use them to repopulate the collectionview. Also I'm not really sure what the best way of storing the logic part in my database would be.
extend your Product class:
class Product {
...
let logic: [String]?
init(...) {
...
logic = rawData["logic"] as? [String]
}
}
In your CollectionViewDataSource add some variables to store current state
var products: [Products]
var filteredProducts: [Product]
var currentFilter: String? {
didSet {
guard let currentFilter = currentFilter else {
filteredProducts = products
return
}
filteredProducts = products.filter { product in
return product.logic.contains(currentFilter)
}
}
}
extend your handleSegment func:
#objc fileprivate func handleSegment() {
print(segmentController.selectedSegmentIndex)
currentFilter = segmentController.titleForSegment(at: segmentController.selectedSegmentIndex)
collectionView.reloadData()
}
In your collectionView datasource, use filteredProducts to build the cells.
I'm making an app for airports and I'm getting an array of data from one api, like so:
"data":[
{"id":"001","code":"ABZ","name":"Aberdeen","country":"United Kingdom"},
{"id":"002","code":"AUH","name":"Abu Dhabi","country":"United Arab Emirates"},
.
.
.
]
AND :
"airports":[
{"from":"001",
"to":["1","3","11","13","12","20","23","27","29","31","33"]
},
.
.
.
]
I have created realm model classes:
class AirportsDataRealm: Object {
#objc dynamic var name: String = ""
#objc dynamic var id: Int = 0
#objc dynamic var code: String = ""
#objc dynamic var country: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
class AirportsFromToRealm: Object {
#objc dynamic var fromID: Int = 0
var toID = List<Int>()
override static func primaryKey() -> String? {
return "fromID"
}
}
now I want to save it into realm, I'm using swiftyJSON and I have used for-loop to do it and it is working fine but I think it's taking long time since the array is very long, here is what I've done:
// Airports Data
let countData = json["data"].count
for i in 0...countData - 1{
let airportsDataModel = AirportsDataRealm()
airportsDataModel.code = json["data"][i]["code"].stringValue
airportsDataModel.name = json["data"][i]["name"].stringValue
airportsDataModel.country = json["data"][i]["country"].stringValue
airportsDataModel.id = Int(json["data"][i]["id"].stringValue)!
try! realm.write {
realm.add(airportsDataModel, update: true)
}
}
//Airports FROM-TO
let countFromTo = json["airports"].count
for i in 0...countFromTo - 1{
let fromToDataModel = AirportsFromToRealm()
fromToDataModel.fromID = Int(json["airports"][i]["from"].stringValue)!
let arrayTo = json["airports"][i]["to"].arrayValue.map{ $0.intValue }
fromToDataModel.toID.append(objectsIn: arrayTo)
try! realm.write {
realm.add(fromToDataModel, update: true)
}
}
is there any way to save the whole array in realm in one shot without for-loop?
P.S
"there should be a relation between the two tables because each from 'id' has a list of 'to' id's and the id's are from the data table, for now I managed to create this relations when fetching the data using filters ,, so just ignore this"
Thank you
Simply use map method,
First I needed to add initializers to my object classes and pass json array as a parameter, like so:
class AirportsDataRealm: Object {
#objc dynamic var name: String = ""
#objc dynamic var id: Int = 0
#objc dynamic var code: String = ""
#objc dynamic var country: String = ""
convenience required init(withJSON json : JSON) {
self.init()
self.name = json["name"].stringValue
self.id = json["id"].intValue
self.code = json["code"].stringValue
self.country = json["country"].stringValue
}
override static func primaryKey() -> String? {
return "id"
}
}
class AirportsFromToRealm: Object {
#objc dynamic var fromID: Int = 0
var toID = List<Int>()
convenience required init(withJSON json : JSON) {
self.init()
self.fromID = json["from"].intValue
let toArray = json["to"].arrayValue.map{ $0.intValue }
self.toID.append(objectsIn: toArray)
}
override static func primaryKey() -> String? {
return "fromID"
}
}
Then by using map method the code will look like this:
func updateAirport(json: JSON) {
// Airports Data
let airportsData : [AirportsDataRealm]
let airportsDataJsonArray = json["data"].array
airportsData = airportsDataJsonArray!.map{AirportsDataRealm(withJSON: $0)}
//Airports FROM-TO
let airportsFromTo : [AirportsFromToRealm]
let airportsFromToJsonArray = json["airports"].array
airportsFromTo = airportsFromToJsonArray!.map{AirportsFromToRealm(withJSON: $0)}
//Write To Realm
try! realm.write {
realm.add(airportsData, update: true)
realm.add(airportsFromTo, update: true)
}
}
No for loops anymore ^_^
I'm trying to make an Extension to the Array type so to be able to work with 2D arrays. In fact, I did this in Objective-C and the code below worked like a charm. But I really stuck in Swift.
extension Array {
mutating func addObject(anObject : AnyObject, toSubarrayAtIndex idx : Int) {
while self.count <= idx {
let newSubArray = [AnyObject]()
self.append(newSubArray)
}
var subArray = self[idx] as! [AnyObject]
subArray.append(anObject)
}
func objectAtIndexPath(indexPath : NSIndexPath) -> AnyObject {
let subArray = self[indexPath.section] as! Array
return subArray[indexPath.row] as! AnyObject
}
}
I get this error no matter what I do:
Error: Cannot invoke 'append' with an argument list of type '([AnyObject])'
I'd appreciate any help.
#brimstone's answer is close, but if I understand your question correctly, it is an array of [AnyObject], which means it should look like this:
extension Array where Element: _ArrayType, Element.Generator.Element: AnyObject {
mutating func addObject(anObject : Element.Generator.Element, toSubarrayAtIndex idx : Int) {
while self.count <= idx {
let newSubArray = Element()
self.append(newSubArray) // ERROR: Cannot invoke 'append' with an argument list of type '([AnyObject])'
}
var subArray = self[idx]
subArray.append(anObject)
}
func objectAtIndexPath(indexPath: NSIndexPath) -> AnyObject {
let subArray = self[indexPath.indexAtPosition(0)]
return subArray[indexPath.indexAtPosition(1)] as Element.Generator.Element
}
}
You need to say what the array element type is in the extension. Try this:
extension _ArrayType where Generator.Element == AnyObject {
mutating func addObject(anObject: AnyObject, toSubarrayAtIndex idx: Int) {
while self.count <= idx {
let newSubArray = [AnyObject]()
self.append(newSubArray)
}
var subArray = self[idx] as! [AnyObject]
subArray.append(anObject)
}
func objectAtIndexPath(indexPath: NSIndexPath) -> AnyObject {
let subArray = self[indexPath.section] as! Array
return subArray[indexPath.row] as! AnyObject
}
}
From this question: Extend array types using where clause in Swift
I have an array, with custom objects.
I Would like to pop the repeated objects, with the repeated properties:
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
in this case, pop the product2 or product3
Here is an Array extension to return the unique list of objects based on a given key:
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
using this you can so this
let unique = [product,product2,product3].unique{$0.subCategory}
this has the advantage of not requiring the Hashable and being able to return an unique list based on any field or combination
You can use Swift Set:
let array = [product,product2,product3]
let set = Set(array)
You have to make Product conform to Hashable (and thus, Equatable) though:
class Product : Hashable {
var subCategory = ""
var hashValue: Int { return subCategory.hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.subCategory == rhs.subCategory
}
And, if Product was a NSObject subclass, you have to override isEqual:
override func isEqual(object: AnyObject?) -> Bool {
if let product = object as? Product {
return product == self
} else {
return false
}
}
Clearly, modify those to reflect other properties you might have in your class. For example:
class Product : Hashable {
var category = ""
var subCategory = ""
var hashValue: Int { return [category, subCategory].hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.category == rhs.category && lhs.subCategory == rhs.subCategory
}
If Product conforms to Equatable, where a product is equal based on it's subcategory (and you don't care about order), you can add the objects to a set, and take an array from that set:
let array = [product,product2,product3]
let set = NSSet(array: array)
let uniqueArray = set.allObjects
or
let array = [product,product2,product3]
let set = Set(array)
let uniqueArray = Array(set)
If your class conforms to protocol Hashable and you would like to keep the original array order you can create an extension as follow:
extension Array where Element: Hashable {
var uniqueElements: [Element] {
var elements: [Element] = []
for element in self {
if let _ = elements.indexOf(element) {
print("item found")
} else {
print("item not found, add it")
elements.append(element)
}
}
return elements
}
}
class Product {
var subCategory: String = ""
}
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
extension Product : Hashable {
var hashValue: Int {
return subCategory.hashValue
}
}
func ==(lhs: Product, rhs: Product)->Bool {
return lhs.subCategory == rhs.subCategory
}
let set = Set(array)
set.forEach { (p) -> () in
print(p, p.subCategory)
}
/*
Product one
Product two
*/
if an item is part of set or not doesn't depends on hashValue, it depends on comparation. if your product conform to Hashable, it should conform to Equatable. if you need that the creation of the set depends solely on subCategory, the comparation should depends solely on subCategory. this can be a big trouble, if you need to compare your products some other way
Here is a KeyPath based version of the Ciprian Rarau' solution
extension Array {
func unique<T: Hashable>(by keyPath: KeyPath<Element, T>) -> [Element] {
var set = Set<T>()
return self.reduce(into: [Element]()) { result, value in
guard !set.contains(value[keyPath: keyPath]) else {
return
}
set.insert(value[keyPath: keyPath])
result.append(value)
}
}
}
example usage:
let unique = [product, product2, product3].unique(by: \.subCategory)
I have a struct
struct Question {
var title: [String]
var additionalInfo: String?
var answers: [String]
}
and a variable to which i add the data
var questions = [
Question(title: ["What is the color of?", "additional color information"], additionalInfo: nil, answers: [
"Blue",
"Gray"
])
]
This data loads up in a tableView on AppleWatch. Two row types are separated - TitleRowType (for titles array) and AnswersRowType (for answers array).
When i insert values into struct's array - i want to the rows in the tableView be inserted with animation.
I know that there's a insertRowAtIndexes function, but i cannot wrap my head around it. The example provided in Apple's documentation doesn't work for me. That's what i came up with:
let indexSet = NSIndexSet(index: Int) // Int is passed via the function
tableView.insertRowsAtIndexes(indexSet, withRowType: "TitleRowType")
but when i run it - the table doesn't update.
Looking forward to your advices.
You have to do 3 steps:
Add the new data to your array
Insert a row into the table
Populate the row with the new data
Here is a simple example:
class InterfaceController: WKInterfaceController {
#IBOutlet var table: WKInterfaceTable!
var items = ["row1", "row2", "row3"]
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
loadTable()
}
func loadTable() {
table.setNumberOfRows(items.count, withRowType: "tableRow")
var rowIndex = 0
for item in items {
if let row = table.rowControllerAtIndex(rowIndex) as? TableRowController {
row.label.setText(item)
}
rowIndex++
}
}
#IBAction func insertRow() {
items.append("row4")
let newIndex = items.count
table.insertRowsAtIndexes(NSIndexSet(index: newIndex), withRowType: "tableRow")
if let row = table.rowControllerAtIndex(newIndex) as? TableRowController {
row.label.setText(items[newIndex])
}
}
}
TableRowController is a NSObject subclass that has one WKInterfaceLabel outlet to display the number of the row.
I used a button to trigger insertRow()