I'm writing an iOS App in Swift and I'm parsing XML data from a server through a Rest API on the server side using the NSXMLParser delegate.
I have the following data structure:
<alarm>
<rootcause> some properties... </rootcause>
<symptoms>
<symptom> some properties... </symptom>
<symptom> some properties... </symptom>
</symptoms>
</alarm>
Right now I'm parsing the data into an NSmutableArray that contains an NSDictionary for each alarm, that contains a nested dictionary for each RootCause and a NSMutableDictionary with symptoms that contains many instances of NSDictionary for each symptom.
1. NSMutableArray: alarms
2. NSmutableDictionary: alarm
3.NSMutabbleDictionary: rootcause
3.NSMutableDictionary: symptoms
4.NSMutableDictionary: symptom1
4. NSMutableDictionary: symptom2
....
Of course this is a little bit complicated data model, so my question is wether I should create Subclasses of NSObject that contain other nested classes and build my data model or I should keep my data structure of nested NSDictionaries.
Or what would be the best approach to manage changes in the data model in the future and better debugging, etc.
The best way is to create your own data structure, something like this:
class symptom
{
let yourValue = ""
let someOtherValue = 0
}
class alarm
{
var rootcause = ""
var symptoms:[symptom] = []
//or if you have just a string
var symptoms:[String] = []
}
Then all you do is:
var alarms:[alarm] = []
for al in allAramsXML
{
let tmp = alarm()
for sym in al.symptoms
{
let tmpSym = symptom()
tmpSym.yourValue = ""
tmp.symptoms.append(tmpSym)
}
alarms.append(tmp)
}
Converting it gives you compiler validation and you don't have to mess around with string keys to get to your data. So you should make model classes, yes.
An example could look like this:
struct Symptom {
let id : Int
let description : String
}
struct Cause {
let id : Int
}
struct Alarm {
let rootCause : Cause
let symptoms : [Symptom]
}
let alarms : [Alarm] = [Alarm(rootCause: Cause(id: 1), symptoms: [Symptom(id: 2, description: "some description")])]
Related
I want add search functionality in my listing screen. Every item in listing is not view-only but, say for example an object whose few properties are changing based on user action.
To add search functionality, the general way is to keep separate array for search result. Now, in my case user can either alter few values of object from either by searching or without searching. In that case, I will have to update object in both the array based on id of that object but this solution doesn't seems to be proper.
What is the proper way to update the model in this case?
Here is my object
class Course : NSObject
{
var courseId : Int = 0
var name : String = ""
var chapters : [Chapter] = []
var estimatedTime : Int = 0
// Values which will be altered
var userSpentTime : Int = 0
var status : CourseStatus = .pending // Enum
init(_ json: Any?)
{
guard let data = json as? Dictionary else { return }
/* Assignment of values from JSON */
...
...
}
}
In Swift (2.3 currently) I have an NSManagedObject that Ive received through a fetch request, that has a property on it that is contained in a typical Core Data Set. I want to loop through those Core Data objects in the Set, and change properties on them, but Im told that I cannot because they are let constants. Heres my code :
for mediaEntity in aMemory.mediaEntities! {
mediaEntity.remoteId = 0
}
Which gives the error Cannot assign to property : mediaEntity is a let constant
The Core Data property Im trying to change is defined like this :
extension MemoryEntity {
#NSManaged public var mediaEntities: NSSet?
}
Any help much appreciated thanks.
Since you are working with NSSet you need to at least typecast the object. The example I have looks like this:
entity.activities?.forEach { ($0 as? ActivityEntity)?.id = UUID().uuidString }
A more flexible code would then be:
entity.activities?.forEach {
guard let activity = $0 as? ActivityEntity else {
return // Incorrect type. Should never happen
}
activity.id = UUID().uuidString
}
I'm working in Swift 3.0 and in which I'm using Core Data to store info. I'm getting user details in NSManagedObject and so in order to display data I want it to convert in NSMutableArray but it shows a run time error,yet it is working fine with NSArray
I'm working hard to find this error but not getting the exact issue.
Any kind of help will surely be appreciated!!!
We can perform all the operation using "Array" which NSMutableArray can do.
let strConstant = " Wolverine"
let birthDate = "17/5/91"
let birthYear = 1991
let currentyear = 2016
Suppose above are the values which we need to handle with "Array" and "NSMutableArray" in Swift3.0
So below I have create a basic operations which you can perform using Array and NSMutableArray both and in both the cases Output will be same.
You can Put the Coding part in any playground, and you can which line perform which task.
Handle operation with Swift Array :
var newRatingList = [Any]()
newRatingList.append(strConstant)
newRatingList.append(birthDate)
newRatingList.append((currentyear - birthYear))
newRatingList.last
newRatingList[1] = "18/5/91"
newRatingList
Handle operation with NSMutableArray :
var mutableArray = NSMutableArray()
mutableArray.add(strConstant)
mutableArray.add(birthDate)
mutableArray.add((currentyear - birthYear))
mutableArray.lastObject
mutableArray.replaceObject(at: 1, with: "18/05/91")
To understand how array works you can also refer the below link :
https://makeapppie.com/2016/06/23/how-to-use-arrays-in-swift-3-0/
Now related to your question consider this is a example
var locations = [Locations]() // Where Locations = your NSManaged Class
var fetchRequest = NSFetchRequest(entityName: "Locations")
locations = context.executeFetchRequest(fetchRequest, error: nil) as [Locations]
// Then you can use your properties.
for location in locations {
print(location.name)
}
In above example you will get a Swift Array of Locations. So by applying For-in loop you can get the Individual properties and if yo want to convert it into array then you can do something like this,
var a = NSMutableArray()
for location in locations {
var dictionary = [String:AnyObject]()
dictionary["name"] = location.name
dictionary["location"] = location.location
a.addObject(dictionary)
}
In the end, you will get "a" NSMutableArray which contain dictionaries.
Hope it helps!
I would like to ask a question about a good example of how to define a model with many aspects in swift, especially when the project gets bigger and bigger and one model has many aspects. The questions is pretty long but I just would like to know how ppl design a model in a big project. Any comments or thought would be appreciated.
Let's say there is a model called "Book" and it is defined like below:
class Book {
var id: String
var title: String
var author: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
Book has a failable initialiser that parses properties from JSON sent from the server.
On view controller A, it describes the more detailed information about the mode Book, so some properties are added to the model when it is used on view controller r A:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
On another view controller B, we want to show the history of book purchase. So the model needs additional properties as below:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
// required on View Controller B (Not required on VC A)
var purchasedDate: NSDate
var expireDate: NSDate
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
This definition of Book lacks flexibility because the JSON passed to the failabel initialiser must have all of the properties even on an VC that uses only some of the properties.
Solution A:
I think the simplest solution for this is just declaring those additional properties as optional, but I personally think this is not so cool because whenever those optional properties are used they need to be checked if they are not nil.
if let seriesName = book.seriesName {
self.seriesNameLable.title = seriesName
}
This kind of optional binding code will be overflowed all over the codes I assume. Implicit optional binding might be able to used but that is not really safe to use.
Solution B:
Another solution might be to define different models that inherits Book, like BookA and BookB. But what if we need a model that has BookA and BookB's aspects at the same time?
I guess there is no single solution for this kind of problem but I would like to know how other ppl define a model in a big project. (I wonder if someone would have a cool solution using some "swift specific" features like protocol and its extension :). I would appreciate any kind of opinions... Thank you.
Disclaimer: I'm not a Swift programmer, this are extrapolations I make from other languages with the same features and my Swift syntax might not be 100% accurate
Using protocols I would do something like:
class EBook: Book, HasOnlineSource {
...
}
class OtherKindOfBook: Book, WithCollectorEditions, HasBleh, WithFoo {...}
But you must ask yourself:
Do I need this to change dinamically?
If that is the case, you would need to go with Delegation through composition.
Are different parts of the application using the core models differently?
Or in other words, are there different users of those models needing different behavior? In that case, extensions are very useful since allow to expose different behaviors for the same model depending on their context. For instance, the Reporting module can send the message numberOfReaders while the Selling module could ask for promotionalCodes. Both are using the same model, but interacting with different protocols. In your case, you have different controllers wanting different things, so this might apply.
Using delegates
This follows the Composition over inheritance principle, but after reviewing how delegates work in Swift, I understood that they are not a native implementation but still a design pattern (a feature request you might say), the delegation is being made by hand.
Other languages allow you to make a JSSONSerializableBook with a BookProtocol, but instead of implementing what is required on BookProtocol you can set a delegate upon initialization which will implement such protocol. This delegate will be an internal collaborator of JSSONSerializableBook and all messages that are part of BookProtocol received by JSSONSerializableBook will be delegated to it.
Or in other words, the message passing is handled automatically (here is the Kotlin documentatin on delegates if you want to check how other language implements it).
If you want to do the same on Swift, you must explicitly make the message passing to your delegate.
This design has several advantages but since there is no native support for message passing it becomes very verbose to implement.
For further reference you can check the papers on mixins and traits to have some insight on the design decisions behind this features.
This is why Swift has optionals.
If not all Books have, say, a purchasedDate then that should be an optional and then your initialiser doesn't need to fail if that field isn't present in the JSON.
It is your view controllers that will enforce the business logic - so if ViewControllerB gets a Book without a purchasedDate it should generate some sort of error.
It also may be that your base class needs to be decomposed into smaller objects.
For example, you could probably have a PurchaseEvent that is associated with a Book rather than storing that data in the Book itself.
Think of the characteristics of a book in the real world; it has the basic properties you first listed and may even have a price (on the back cover) but it doesn't tell you when it was sold and for how much.
However, #Logain's idea of using Protocols is also good, although it may be tricky when it comes to keeping the server JSON side in sync with the app code.
First of all, if you have a view controller responsible for presenting “more detailed information” about a book, then let's call it BookDetailsViewController, instead of the meaningless “View Controller A”. Similarly we should talk about BookHistoryViewController, not “View Controller B”.
If you want to treat the “more detailed information” as all-or-nothing, then encapsulate it as such, in a Details object that the Book object optionally reference. Same with the history. So here's a model:
class Book {
let id: String
let title: String
let author: String
// These can't be lets because each requires self to initialize.
private(set) var details: Details?
private(set) var history: History?
init?(json: [String: AnyObject]) {
guard let
id = json["id"] as? String,
title = json["title"] as? String,
author = json["author"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.id = ""
self.title = ""
self.author = ""
return nil
}
self.id = id
self.title = title
self.author = author
self.details = Details(book: self, json: json)
self.history = History(book: self, json: json)
}
class Details {
unowned let book: Book
let price: Int
let seriesName: String
let reviewNumber: Int
let detailedDescription: String
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
price = json["price"] as? Int,
seriesName = json["seriesName"] as? String,
reviewNumber = json["reviewNumber"] as? Int,
detailedDescription = json["detailedDescription"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.price = 0
self.seriesName = ""
self.reviewNumber = 0
self.detailedDescription = ""
return nil
}
self.price = price
self.seriesName = seriesName
self.reviewNumber = reviewNumber
self.detailedDescription = detailedDescription
}
}
class History {
unowned let book: Book
let purchasedDate: NSDate
let expireDate: NSDate
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
purchasedDate = (json["purchasedDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) }),
expireDate = (json["expireDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) })
else {
// These assignments will be unnecessary in the next version of Swift.
self.purchasedDate = NSDate()
self.expireDate = NSDate()
return nil
}
self.purchasedDate = purchasedDate
self.expireDate = expireDate
}
}
}
Given this model, you can allow the user to ask for a BookDetailsViewController or a BookHistoryViewController only when the appropriate property isn't nil.
class BookViewController: UIViewController {
#IBOutlet var detailsButton: UIButton!
#IBOutlet var historyButton: UIButton!
var book: Book!
override func viewDidLoad() {
super.viewDidLoad()
detailsButton.hidden = book.details == nil
historyButton.hidden = book.history == nil
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier {
case .Some("details"): prepareForDetailsSegue(segue)
case .Some("history"): prepareForHistorySegue(segue)
case let other: fatalError("unknown segue identifier \(other)")
}
}
private func prepareForDetailsSegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookDetailsViewController
destination.details = book.details!
}
private func prepareForHistorySegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookHistoryViewController
destination.history = book.history!
}
}
class BookDetailsViewController: UIViewController {
var details: Book.Details!
// etc.
}
class BookHistoryViewController: UIViewController {
var history: Book.History!
// etc.
}
I’m new to Swift and have been having some troubles figuring out some aspects of Arrays and Dictionaries.
I have an array of dictionaries, for which I have used Type Aliases - e.g.
typealias myDicts = Dictionary<String, Double>
var myArray : [myDicts] = [
["id":0,
"lat”:55.555555,
"lng”:-55.555555,
"distance":0],
["id":1,
"lat": 44.444444,
"lng”:-44.444444,
"distance":0]
]
I then want to iterate through the dictionaries in the array and change the “distance” key value. I did it like this:
for dict:myDicts in myArray {
dict["distance"] = 5
}
Or even specifically making sure 5 is a double with many different approaches including e.g.
for dict:myDicts in myArray {
let numberFive : Double = 5
dict["distance"] = numberFive
}
All my attempts cause an error:
#lvalue $T5' is not identical to '(String, Double)
It seems to be acting as if the Dictionaries inside were immutable “let” rather than “var”. So I randomly tried this:
for (var dict:myDicts) in myArray {
dict["distance"] = 5
}
This removes the error and the key is indeed assigned 5 within the for loop, but this doesn't seem to actually modify the array itself in the long run. What am I doing wrong?
The implicitly declared variable in a for-in loop in Swift is constant by default (let), that's why you can't modify it directly in the loop.
The for-in documentation has this:
for index in 1...5 {
println("\(index) times 5 is \(index * 5)")
}
In the example above, index is a constant whose value is automatically
set at the start of each iteration of the loop. As such, it does not
have to be declared before it is used. It is implicitly declared
simply by its inclusion in the loop declaration, without the need for
a let declaration keyword.
As you've discovered, you can make it a variable by explicitly declaring it with var. However, in this case, you're trying to modify a dictionary which is a struct and, therefore, a value type and it is copied on assignment. When you do dict["distance"] = 5 you're actually modifying a copy of the dictionary and not the original stored in the array.
You can still modify the dictionary in the array, you just have to do it directly by looping over the array by index:
for index in 0..<myArray.count {
myArray[index]["distance"] = 5
}
This way, you're sure to by modifying the original dictionary instead of a copy of it.
That being said, #matt's suggestion to use a custom class is usually the best route to take.
You're not doing anything wrong. That's how Swift works. You have two options:
Use NSMutableDictionary rather than a Swift dictionary.
Use a custom class instead of a dictionary. In a way this is a better solution anyway because it's what you should have been doing all along in a situation where all the dictionaries have the same structure.
The "custom class" I'm talking about would be a mere "value class", a bundle of properties. This was kind of a pain to make in Objective-C, but in Swift it's trivial, so I now do this a lot. The thing is that you can stick the class definition for your custom class anywhere; it doesn't need a file of its own, and of course in Swift you don't have the interface/implementation foo to grapple with, let alone memory management and other stuff. So this is just a few lines of code that you can stick right in with the code you've already got.
Here's an example from my own code:
class Model {
var task : NSURLSessionTask!
var im : UIImage!
var text : String!
var picurl : String!
}
We then have an array of Model and away we go.
So, in your example:
class MyDict : NSObject {
var id = 0.0
var lat = 0.0
var lng = 0.0
var distance = 0.0
}
var myArray = [MyDict]()
let d1 = MyDict()
d1.id = 0
d1.lat = 55.55
d1.lng = -55.55
d1.distance = 0
let d2 = MyDict()
d2.id = 0
d2.lat = 44.44
d2.lng = -44.44
d2.distance = 0
myArray = [d1,d2]
// now we come to the actual heart of the matter
for d in myArray {
d.distance = 5
}
println(myArray[0].distance) // it worked
println(myArray[1].distance) // it worked
Yes, the dictionary retrieved in the loop is immutable, hence you cannot change.
I'm afraid your last attempt just creates a mutable copy of it.
One possible workaround is to use NSMutableDictionary:
typealias myDicts = NSMutableDictionary
Have a class wrapper for the Swift dictionary or array.
class MyDictionary: NSObject {
var data : Dictionary<String,Any>!
init(_ data: Dictionary<String,Any>) {
self.data = data
}}
MyDictionary.data