First, I'm extracting the content from the text fields into a dictionary:
var dict: [String: String] = [:]
for metricPair in metricStackView.arrangedSubviews {
if metricPair.subviews[0] is UITextField {
let unitTextField = metricPair.subviews[0] as! UITextField
let valueTextField = metricPair.subviews[1] as! UITextField
if let textContent = unitTextField.text, let valueTextContent = valueTextField.text {
let trimmedKey = textContent.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedValue = valueTextContent.trimmingCharacters(in: .whitespacesAndNewlines)
dict.updateValue(trimmedValue, forKey: trimmedKey)
}
}
}
And I'm saving it to Core Data:
let goal = Goal(context: self.context)
goal.date = Date()
for item in dict {
goal.metrics?.append(item.key)
}
goal.progress.insert(progress)
My managed object looks like this:
extension Goal {
#nonobjc public class func createFetchRequest() -> NSFetchRequest<Goal> {
return NSFetchRequest<Goal>(entityName: "Goal")
}
#NSManaged public var date: Date
#NSManaged public var metrics: [String]?
#NSManaged public var progress: Set<Progress>
}
I keep getting nil for the metrics property of [String] type even before the context is saved. When I log item.key in:
for item in dict {
goal.metrics?.append(item.key)
}
the content is showing up properly.
As far as I can tell from the above code, metrics array is not initialized.
Try replacing this line:
goal.metrics?.append(item.key)
With these lines:
if case nil = goal.metrics?.append(item.key) {
goal.metrics = [item.key]
}
or with simply these lines:
if goal.metrics == nil {
goal.metrics = []
}
goal.metrics?.append(item.key)
Related
I am trying to create a generic array with different models. I have a parser method like that. But it doesn't work because it returns [Any] and it's not typesafe. I need to access my Movie and CastMember objects after parse method. I will use this array in my tableviewcontroller delegate methods. How can I do that?
static func parseSearchResult(_ data:Dictionary<String, AnyObject>) -> [Any] {
var array = [Any]()
let jsonData = JSON(data)
if let resultData = jsonData["results"].arrayObject {
let result = resultData as! [[String:AnyObject]]
for element in result {
if((element["media_type"]?.isEqual("person"))!){
let person = CastMember(json: element)
array.append(person)
}
else if((element["media_type"]?.isEqual("movie"))!){
let movie = Movie(json: element)
array.append(movie)
}
}
}
return array
}
and these are my structs
struct CastMember{
var id : Int?
var originalName : String?
var castName : String?
var picturePath : String?
init(json: [String:Any]){
originalName = json["name"] as? String
id = json["id"] as? Int
castName = json["character"] as? String
picturePath = "https://image.tmdb.org/t/p/w200/"
picturePath?.append((json["profile_path"] as? String) ?? "")
}
}
struct Movie{
var id : Int?
var title : String?
var imagePath : String?
init(json: [String:Any]){
title = json["title"] as? String
id = json["id"] as? Int
imagePath = "https://image.tmdb.org/t/p/w200/"
imagePath?.append((json["poster_path"] as? String)!)
}
}
Make your Movie and CastMember classes confirm to the protocol Codable.
also you will have to write a struct or class which matches the response data , like it must have an array of results and any other key coming in response.
struct ResponseModel<T> : Codable {
let results : [T]
}
then decode it like this :
let response : ResponseModel = JSONDecoder.decode(T.self)
You should make a protocol
Example:
enum MediaType {
case movie, castMember
}
protocol SearchResult {
var title: String { get }
var mediaType: MediaType { get }
}
struct SearchResultViewModel: SearchResult {
let title: String
let mediaType: MediaType
init(title: String, mediaType: MediaType) {
self.title = title
self.mediaType = mediaType
}
}
Then your parseSearchResult should return an array of [SearchResult] objects that conforms to the protocol, in this case, an array of SearchResultViewModel
I am currently trying to load data from Parse (which I realize will soon be obselete but I have to stay on it for now) and I have a custom class that is to handle all the data from the backend like so:
import Foundation
import Parse
class FeedContent: PFObject {
#NSManaged var address: String
#NSManaged var content: PFFile
#NSManaged var isVideo: Int
#NSManaged var attendeeObjectId: String
#NSManaged var created: NSDate?
#NSManaged var objId: String
init(address: String, content: PFFile, isVideo: Int, attendeeObjectId: String, created: NSDate?, objId: String) {
super.init()
self.address = address
self.content = content
self.isVideo = isVideo
self.attendeeObjectId = attendeeObjectId
self.created = createdAt
self.objId = objId
}
override init() {
super.init()
}
}
extension FeedContent: PFSubclassing {
class func parseClassName() -> String {
return "FeedContent"
}
override class func initialize() {
var onceToken: dispatch_once_t = 0
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
}
and in my Display View Controller I am creating an array to house these items then calling a function to display the data , like here:
func addPFObjectInFeedContent(object : PFObject) {
let newItem = FeedContent()
newItem.address = (object["address"] as? String)!
newItem.isVideo = (object["isVideo"] as? Int)!
newItem.attendeeObjectId = (object["attendeeObjectId"] as? String)!
//newItem.eventObjectId = (object["eventObjectId"] as? String)!
newItem.content = (object["content"] as? PFFile)!
newItem.created = object.createdAt
newItem.objId = object.objectId!
self.feedContentItems.append(newItem)
}
Here is the array as well:
var feedContentItems = [FeedContent]()
On this line specifically is where the EXC_Breakpoint error occurs:
newItem.isVideo = (object["isVideo"] as? Int)!
so i was curious if anyone had any intimation of what may be happening?
func loadParseFeed() {
let contentQuery = PFQuery(className: "FeedContent")
contentQuery.whereKey("address", equalTo: defaults.objectForKey("newLocation")! as! String)
//contentQuery.whereKey("flagged", notEqualTo: true)
contentQuery.orderByDescending("createdAt")
if blacklist.count != 0 {
for var item = 0; item <= blacklist.count; item += 1 {
contentQuery.whereKey("attendeeObjectId", equalTo: blacklist[item])
}
}
contentQuery.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?,error: NSError?) -> Void in
if error == nil {
if let object = objects as [PFObject]? {
if objects!.count > 0 {
for thing in objects! {
// let newItem = FeedContent()
// newItem.isVideo = (thing["isVideo"] as? Int)!
// newItem.attendeeObjectId = (thing["attendeeObjectId"] as? String)!
// newItem.eventObjectId = (thing["eventObjectId"] as? String)!
// newItem.content = (thing["content"] as? PFFile)!
// newItem.created = thing.createdAt
// newItem.objId = thing.objectId!
// self.feedContentItems.append(newItem)
self.addPFObjectInFeedContent(thing)
}
self.advanceFeed()
// self.overviewTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "advanceFeed", userInfo: nil, repeats: true)
// self.overviewTimer.fire()
}
}
}
})
}
I want to save the response from JSON in a file and fetch from it when the network is not available. However on trying to fetch idea by disabling the wifi, the app always crashes. Are there any other ways for offline fetching in swift except saving in database??
This is the error I am getting : Could not cast value of type 'Swift._NSContiguousString' (0x109e22320) to 'NSArray'
This is what I have done so far:
Create a model
class Directory : NSObject, NSCoding {
var data : [AnyObject]
var tid : String
var vid : String
var name : String
var imgThumbnail : String
var imgMedium : String
var imgLarge : String
var child : String
// MARK: Archiving Paths
init(data:[AnyObject],tid:String,vid:String,name:String,imgThumbnail:String,imgMedium:String,imgLarge:String,child:String) {
self.data = data ?? []
self.tid = tid ?? ""
self.vid = vid ?? ""
self.name = name ?? ""
self.imgThumbnail = imgThumbnail ?? ""
self.imgMedium = imgMedium ?? ""
self.imgLarge = imgLarge ?? ""
self.child = child ?? ""
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(data, forKey:Constants.kData)
aCoder.encodeObject(name, forKey:Constants.Directory.kName )
aCoder.encodeObject(tid, forKey: Constants.Directory.tid)
aCoder.encodeObject(vid, forKey: Constants.Directory.vid)
aCoder.encodeObject(imgThumbnail, forKey:Constants.Directory.kImageThumbnail)
aCoder.encodeObject(imgMedium, forKey: Constants.Directory.kImageMedium)
aCoder.encodeObject(imgLarge, forKey: Constants.Directory.kImageLarge)
aCoder.encodeObject(child, forKey: Constants.Directory.kChild)
}
required convenience init?(coder aDecoder: NSCoder) {
let data = aDecoder.decodeObjectForKey(Constants.kData) as! [AnyObject]
let name = aDecoder.decodeObjectForKey(Constants.Directory.kName) as! String
let tid = aDecoder.decodeObjectForKey(Constants.Directory.tid) as! String
let vid = aDecoder.decodeObjectForKey(Constants.Directory.vid) as! String
let imgThumbnail = aDecoder.decodeObjectForKey(Constants.Directory.kImageThumbnail) as! String
let imgMedium = aDecoder.decodeObjectForKey(Constants.Directory.kImageMedium) as! String
let imgLarge = aDecoder.decodeObjectForKey(Constants.Directory.kImageLarge) as! String
let child = aDecoder.decodeObjectForKey(Constants.Directory.kChild) as! String
// Must call designated initializer.
self.init(data:data,tid:tid,vid:vid,name:name,imgThumbnail:imgThumbnail,imgMedium: imgMedium,imgLarge: imgLarge, child: child)
}
}
Code for saving and loading the data from file
class func loadSavedFile(fileName: String) -> AnyObject? {
let pathString: String = Utility.fetchFilePathString(fileName)
print("Here the pathString is \(pathString)")
if NSFileManager.defaultManager().fileExistsAtPath(pathString) {
return NSKeyedUnarchiver.unarchiveObjectWithFile(pathString)!
} else {
return "File doesn't exist"
}
return ""
}
class func saveObject(object: AnyObject, toFile fileName: String) {
let pathString: String = Utility.fetchFilePathString(fileName)
NSKeyedArchiver.archiveRootObject(object, toFile: pathString)
}
class func fetchFilePathString(fileName: String) -> String {
let pathAray: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.AllDomainsMask, true)
let pathString = pathAray.last!
return NSURL(fileURLWithPath: pathString as! String).URLByAppendingPathComponent(fileName).absoluteString
}
Checking for network connection in the view controller
var directoryArr = [Directory]()
override func viewDidLoad() {
super.viewDidLoad()
if Utility.isNetworkReachable() {
Utility.saveObject([], toFile: Constants.File.kDirectory)
self.serviceCallDirectory()
} else {
self.directorie = (Utility.loadSavedFile(Constants.File.kDirectory) as? [Directory])!
self.tableView.reloadData()
}
Service Call
func serviceCallDirectory() -> Void {
let stringUrl = Constants.baseUrl + Constants.kDirectoryUrl
WebService.getRequestAPI(stringUrl, withSuccess: {(responseDic, Statusflag,error) in
if Statusflag {
self.tableView.backgroundColor = UIColor.clearColor()
self.tableView.hidden = false
let tempInfo = responseDic![Constants.kData] as! [AnyObject]
var imgthumbnail : String = ""
var imgmedium : String = ""
var imglarge : String = ""
var name : String = ""
var child : String = ""
if tempInfo.count != 0 {
for info in tempInfo {
let tid = info[Constants.Directory.tid] as! String
let vid = info[Constants.Directory.vid] as! String
if let names = info[Constants.Directory.kName] as? String {
name = names
}
if let childs = info[Constants.Directory.kChild] as? String {
child = childs
}
if let imgthumb = info[Constants.Directory.kImageThumbnail] as? String {
imgthumbnail = imgthumb
} else {
imgthumbnail = ""
}
if let imgmediumd = info[Constants.Directory.kImageMedium] as? String {
imgmedium = imgmediumd
} else {
imgmedium = ""
}
if let imglarges = info[Constants.Directory.kImageLarge] as? String {
imglarge = imglarges
}
let myModel = Directory(
data: tempInfo,
tid: tid,
vid: vid,
name: name,
imgThumbnail: imgthumbnail,
imgMedium: imgmedium,
imgLarge: "",
child: child
)
self.directorie.append(myModel)
}
I don't know that this is the only issue, but this code
class func loadSavedFile(fileName: String) -> AnyObject? {
let pathString: String = Utility.fetchFilePathString(fileName)
print("Here the pathString is \(pathString)")
if NSFileManager.defaultManager().fileExistsAtPath(pathString) {
return NSKeyedUnarchiver.unarchiveObjectWithFile(pathString)!
} else {
return "File doesn't exist"
}
return ""
}
Either returns an object or a string. That's not very sensible. It should return a success flag or a tuple or use a completion block. When you call this function your code expects to get back an array of directory, which in a number of cases won't happen
self.directorie = (Utility.loadSavedFile(Constants.File.kDirectory) as? [Directory])!
The error in your question indicates a different kind of data mismatch. You should try not to use AnyObject, let swift help you by type checking what you're doing...
I'm testing Realm, but I cant find a easy way to convert my object to JSON.
I need to push the data to my REST interface.
How can I do it using swift?
class Dog: Object {
dynamic var name = ""
}
class Person : Object {
dynamic var name = ""
let dogs = List<Dog>()
}
I'm trying something like this, but I can't iterate unknown objects (List)
extension Object {
func toDictionary() -> NSDictionary {
let props = self.objectSchema.properties.map { $0.name }
var dicProps = self.dictionaryWithValuesForKeys(props)
var mutabledic = NSMutableDictionary()
mutabledic.setValuesForKeysWithDictionary(dicProps)
for prop in self.objectSchema.properties as [Property]! {
if let objectClassName = prop.objectClassName {
if let x = self[prop.name] as? Object {
mutabledic.setValue(x.toDictionary(), forKey: prop.name)
} else {
//problem here!
}
}
}
return mutabledic
}
}
**sorry for ugly code.
I am also new to Realm but I think the easiest way is to reflect on Object's schema:
class Person: Object {
dynamic var name = ""
dynamic var age = 0
}
let person = Person()
let schema = person.objectSchema
let properties = schema.properties.map() { $0.name }
let dictionary = person.dictionaryWithValuesForKeys(properties) // NSDictionary
println(properties)
println(dictionary)
I think that I found the solution.
I'm not reliant about performance.
extension Object {
func toDictionary() -> NSDictionary {
let properties = self.objectSchema.properties.map { $0.name }
let dicProps = self.dictionaryWithValuesForKeys(properties)
var mutabledic = NSMutableDictionary()
mutabledic.setValuesForKeysWithDictionary(dicProps)
for prop in self.objectSchema.properties as [Property]! {
if let objectClassName = prop.objectClassName {
if let nestedObject = self[prop.name] as? Object {
mutabledic.setValue(nestedObject.toDictionary(), forKey: prop.name)
} else if let nestedListObject = self[prop.name] as? ListBase {
var objects = [AnyObject]()
for index in 0..<nestedListObject._rlmArray.count {
if let object = nestedListObject._rlmArray[index] as? Object {
objects.append(object.toDictionary())
}
}
mutabledic.setObject(objects, forKey: prop.name)
}
}
}
return mutabledic
}
}
Here is my solution. use unsafeBitCast to avoid cast fail warning.
extension Object {
func toDictionary() -> [String:AnyObject] {
let properties = self.objectSchema.properties.map { $0.name }
var dicProps = [String:AnyObject]()
for (key, value) in self.dictionaryWithValuesForKeys(properties) {
if let value = value as? ListBase {
dicProps[key] = value.toArray()
} else if let value = value as? Object {
dicProps[key] = value.toDictionary()
} else {
dicProps[key] = value
}
}
return dicProps
}
}
extension ListBase {
func toArray() -> [AnyObject] {
var _toArray = [AnyObject]()
for i in 0..<self._rlmArray.count {
let obj = unsafeBitCast(self._rlmArray[i], Object.self)
_toArray.append(obj.toDictionary())
}
return _toArray
}
}
I have a User Struct that I'm casting to Json to be able to get into NSUserDefaults...
import Foundation
struct User {
var name = ""
var stores: [Store] = []
init?(json: [String: AnyObject]) {
if let name = json["name"] as? String,
storesJSON = json["stores"] as? [[String: AnyObject]]
{
self.name = name
self.stores = storesJSON.map { Store(json: $0)! }
} else {
return nil
}
}
init() { }
func toJSON() -> [String: AnyObject] {
return [
"name": name,
"stores": stores.map { $0.toJSON() }
]
}
}
and I am using a Data Manager class (Singleton) to add a new User. But I can't figure out what to pass into updateValue in my addPerson function below? Alternatively is there another way to get this object into NSUserDefaults?
import Foundation
class DataManager {
static let sharedInstance = DataManager()
var users = [String : User]()
init() {
let userDefaults = NSUserDefaults.standardUserDefaults()
if let var userFromDefaults = userDefaults.objectForKey("users") as? [String : User] {
users = userFromDefaults
}
else {
// add default values later
}
}
var userList: [String] {
var list: [String] = []
for userName in users.keys {
list.append(userName)
}
list.sort(<)
return list
}
func addPerson(newUserName: String) {
users.updateValue(User(), forKey: newUserName)
// saveData()
}
You should change your interface of the addPerson function, use addPerson(newUser: User) instead of using addPerson(newUserName: String) as #iosDev82 said:
// Because your addPerson function needs two parameters: a name and a user object
func addPerson(newUser: User) {
users.updateValue(newUser, forKey: newUser.name)
// saveData()
}
so you can:
let newName = textField.text.capitalizedString
let newUser = User(["name": newName, "stores" : []])
DataManager.sharedInstance.addPerson(newUser)
I think you already know how to create a User object. And that is what you should pass as an argument to your following function. Something like this.
var aUser = User(["name": textField.text. capitalizedString])
DataManager.sharedInstance.addPerson(aUser)
func addPerson(newUser: User) {
users[newUser.name] = newUser
// saveData()
}