I'm having this problem, im trying to retrieve data from the firebase database, but it's saying the profilepic isn't a key, but it is. This is the object class.
class User: NSObject {
var firstname: String?
var lastname: String?
var username: String?
var profilepic: String?
}
and this is me trying to retrieve
func getusers(){
ref.child("Friends").child(currentuser!).observe(.childAdded, with: { (snapshot) in
print(snapshot)
if let dic = snapshot.value as? [String: AnyObject]{
let user = User()
user.setValuesForKeys(dic)
print(user.firstname, user.lastname)
DispatchQueue.main.async {
self.tableview.reloadData()
}}
}) { (error) in
print(error.localizedDescription)
}
}
and the crash is here user.setValuesForKeys(dic).
Anyone has any idea why isn't working?
Try this instead:
#objcMembers
class User: NSObject {
...
}
The setValuesForKeys API is implemented by the Foundation framework and requires Objective-C compatibility.
The rules for exposing Swift code to Objective-C have changed considerably in Swift 4. Please check the evolution proposal for further information.
User class design. If any of the four properties can be missing from Firebase than your class design might be a good fit. Otherwise, consider declaring the fixed properties as non-optional and creating an initializer instead.
Change your User declaration to this:
class User: NSObject {
#objc var firstname: String?
#objc var lastname: String?
#objc var username: String?
#objc var profilepic: String?
}
In Swift 4, Objective-C cannot see your instance properties unless you expose them explicitly.
Related
I'm trying to instantiate my viewmodel for testing, in this case I don't need its parameters, but as it asks me to add them, when I try I get an error "Constant 'data' used before being initialized"
This is my code:
struct Account: Codable {
let details: [Details]?
}
struct Details: Codable {
let id: String?
let currency: String?
let interest: Float64?
let date: String?
}
class DetailViewModel {
private let data: Details?
init(data: Details) {
self.data = data
}
}
Tests:
class DetailViewModelTest: QuickSpec {
override func spec() {
var viewModel : DetailViewModel!
let data: Details!
viewModel = DetailViewModel(data: data) // I have error in this line
}
}
Is there a way to instantiate my viewmodel without parameters? Or a better way to handle this?
To use Details in a test with hardcoded values you either need to create it from some json or add another init to initialise all values, here I am using the latter. I am adding it in an extension and created a static method that uses the init to create an object with hard coded values.
extension Details {
private init(id: String, currency: String, interest: Float64, date: String) {
self.id = id
self.currency = currency
self.interest = interest
self.date = date
}
static func createStub() -> Details {
Details(id: "1", currency: "EUR", interest: 1.23, date: "2022-02-12")
}
}
This is one way of doing it, the init could be designed in many ways but this is to show you how to move forward.
This can then be used in the test class
class DetailViewModelTest: QuickSpec {
override func spec() {
let viewModel = DetailViewModel(data: Details.createStub())
//...
}
}
you should:
let data: Details = Details() // create your data
Ok I thought its not a major issue but I am wrong. Currently I am working on a project where I get huge chunk of JSON return. I am fetching those and making my model. Now in my model I am checking if there any value is nil by guard statement. Here is a sample of my model:
import Foundation
import SwiftyJSON
class profileModel {
var _phone_no: String?
var _full_name: String?
var _image: String?
var _email: String?
var _profile_pic: String?
var _rating: Int?
var _dob: String?
var _gender: String?
var _firstName: String?
var _lastName: String?
required init?(phone_no: String, full_name: String, image: String, email: String, profile_pic: String, rating: Int, dob: String, gender: String, firstName: String, lastName: String) {
self._phone_no = phone_no
self._full_name = full_name
self._image = image
self._email = email
self._profile_pic = profile_pic
self._rating = rating
self._dob = dob
self._gender = gender
self._firstName = firstName
self._lastName = lastName
}
convenience init?(json: JSON){
guard let phone_no = json["phone_no"].string,
let full_name = json["full_name"].string,
let image = json["profile_pic"].string,
let email = json["email"].string,
let profile_pic = json["profile_pic"].string,
let rating = json["rating"].int,
let dob = json["dob"].string,
let gender = json["gender"].string,
let firstName = json["first_name"].string,
let lastName = json["last_name"].string else {
print("Profile Detail Model Error")
return nil
}
self.init(phone_no: phone_no, full_name: full_name, image: image, email: email, profile_pic: profile_pic, rating: rating, dob: dob, gender: gender, firstName: firstName, lastName: lastName)
}
}
But how can I prevent crashes when any key is missing from JSON return? Seems like when I check both key & values the class got really really big, there must be some better way.
Making properties optionals is a good approach, however you can take advantage of the new Codable from Swift 4 where you can parse JSON to any data model that conformance to Codable.
In your case you can write the model like this:
class ProfileModel: Codable {
var phone_no: String?
var full_name: String?
var profile_pic: String?
var email: String?
// var profile_pic: String?
var rating: String?
var dob: String?
var gender: String?
var first_name: String?
var last_name: String?
}
And when you need to decode from the server use:
let profile = try JSONDecoder().decode(ProfileModel.self, from: json1)
If you get an array of "Profile" just change the above line to:
let profiles = try JSONDecoder().decode([ProfileModel].self, from: json1)
There is no need to use the library SwiftyJSON any more.
You should have a look at the Codable protocol: The following Playground shows what happens, when you try to parse a Json, that is missing a particular key.
//: Playground - noun: a place where people can play
import Foundation
At first, we create our ProfileModel class and mock a related json.
class ProfileModel: Codable {
//...
var _firstName: String?
var _lastName: String?
}
let profile = ProfileModel()
profile._firstName = "Hans"
profile._lastName = "Peter"
let json = try! JSONEncoder().encode(profile)
Parsing works as expected:
do {
let profileAgain = try JSONDecoder().decode(ProfileModel.self, from: json)
print(profileAgain._firstName) // "Optional("Hans")\n"
print(profileAgain._lastName) // "Optional("Peter")\n"
} catch {
print("something went wrong")
}
So what happens, when we add another property to our class (_phone_no), that is not included in our Json? Nothing really changes, if this new property is optional:
class AnotherProfileModel: Codable {
//...
var _firstName: String?
var _lastName: String?
var _phone_no: Int?
}
do {
let anotherProfile = try JSONDecoder().decode(AnotherProfileModel.self, from: json)
print(anotherProfile._firstName) // "Optional("Hans")\n"
print(anotherProfile._lastName) // "Optional("Peter")\n"
print(anotherProfile._phone_no) // "nil\n"
} catch {
print("something went wrong")
}
But if this property is not an optional, the decoder will throw an error:
class AndYetAnotherProfileModel: Codable {
//...
var _firstName: String?
var _lastName: String?
var _phone_no: Int
}
do {
let andYetAnotherProfileModel = try JSONDecoder().decode(AndYetAnotherProfileModel.self, from: json)
} catch {
print("something went wrong") // "something went wrong\n"
}
I hope this working example will help you, to get a better understanding of the Codable protocol :)
I have a viewcontroller in Swift that works well, and I would like to use it from an Objective-C class. My problem is that I don't know how to use it.
I try to add the delegate in the header of my main Objective-C class.
#interface MainViewController : UIViewController <PlanningSearchDelegate>
But I think as it is a swift class, it doesn't work. The error is:
Cannot find protocol declaration for PlanningSearchDelegate
Then, I tried to add the #objc word in the swift class.
import UIKit
#objc protocol PlanningSearchDelegate
{
func planningSearchDidSelectResult(_ result:DALSearchResult)
}
class PlanningSearchViewController: UIViewController
{
}
extension PlanningSearchViewController: UITableViewDelegate
{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let result = _results[indexPath.row]
delegate?.planningSearchDidSelectResult(result)
}
}
But I have an error message:
Method cannot be a member of an #objc protocol because the type of the parameter cannot be represented in Objective-C.
I put the delegate = self in the prepareForSegue of the main Objective-C class without success :
else if ([segue.identifier isEqualToString:#"showSearchViewSegue"])
{
PlanningSearchViewController *searchViewController = segue.destinationViewController;
searchViewController.delegate = self;
}
The error message is:
Property 'delegate' not found on object of type "PlanningSearchViewController *'
Here is the DALSearchResult Class :
import Foundation
class DALSearchResult
{
var contactId: Int
var firstname: String
var lastname: String
var email: String
var phone: String
var city: String
var registrationId: Int?
var slotId: Int?
var vehicleId: Int?
var startDate: Date?
var vehicleName: String?
init(contactId:Int, firstname:String, lastname:String, email:String, phone:String, city:String)
{
self.contactId = contactId
self.firstname = firstname
self.lastname = lastname
self.email = email
self.phone = phone
self.city = city
}
convenience init(registrationId:Int, slotId:Int, vehicleId:Int, contactId:Int, firstname:String, lastname:String, email:String, phone:String, city:String, startDate:Date, vehicleName: String)
{
self.init(contactId: contactId, firstname: firstname, lastname: lastname, email: email, phone: phone, city: city)
self.registrationId = registrationId
self.vehicleId = vehicleId
self.slotId = slotId
self.startDate = startDate
self.vehicleName = vehicleName
}
}
Anyone has an idea ?
Thanks in advance.
Make DALSearchResult class Objective-C compatible. If your DALSearchResult does not extend from NSObject, you can extend it form NSObject.
Can you share how the declaration of DALSearchResult looks like? Maybe it's an enum which cannot be translated to Obj-C. In this case you might need to pass the result in a different way.
To access swift from objective-C you have to import the swift.h always in your class that you want to use it like so
#import "Yourprojectname-Swift.h"
I'm currently writing an iPhone application in Swift. I have a global class instance of my user database, like this:
var currentUser = UserDB()
class UserDB: Object {
dynamic var id:String = "0"
override class func primaryKey() -> String {
return "id"
}
var userName: String?
var firstName: String?
var lastName: String?
}
Then I try to print my user info with a print in another class:
UserDB {
id = 56826e22971f34731a07ba09;
userName = aze;
firstName = (null);
lastName = (null);
}
But if I try to print a single value, it won't works:
print(currentUser.userName)
Will print:
nil
Do you have any idea why?
By the way, is it a good idea to deal with user info like this? I put these info inside a Realm database on exit, when going to background, or after account upgrade.
You need to declare the properties as dynamic if you want Realm to map them. It seems that you did this only for the id property.
dynamic var userName: String?
dynamic var firstName: String?
dynamic var lastName: String?
I've been trying to implement Parse in my application and can't seem to get my Subclass to save to the backend. I've been using the guides provided by Parse here and here but still can't get it to work.
My Subclass looks like this:
import Foundation
import Bolts
import Parse
class Fillup : PFObject, PFSubclassing {
#NSManaged var username: String?
#NSManaged var amount: String?
#NSManaged var cost: String?
#NSManaged var date: NSDate?
#NSManaged var location: CLLocation?
override class func initialize() {
var onceToken : dispatch_once_t = 0;
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
static func parseClassName() -> String {
return "Fillup"
}
Accessing the variables works fine using let x = fillup.amount as String!.
However, in my save method the variables always end up being nil.
Here's a small example from my save method:
#IBAction func saveTapped(sender: AnyObject) {
// instantiate new Fillup object
var fillup :Fillup?
//check for nil on variables
if let username = PFUser.currentUser()?.username {
println(username)
fillup?.username = username
}else{
println("Username is nil.")
}
println(fillup?.username)
//save object to backend
fillup?.saveInBackgroundWithBlock({ (success, error) -> Void in
if error != nil {
println("Error: " + error!.localizedDescription)
}else{
println("Fillup saved!")
}
})
}
The output always looks like:
mforrest3
nil
and as the variable is nil it doesn't save successfully to the backend.
I've not included the code for the other variables for simplicity's sake however I think this should still work.
If anyone could point me in the direction of a good guide or help me come up with an answer/reason that would be great.
This line
var fillup :Fillup?
Is not creating a new instance, it's just defining an optional local variable of the appropriate type. You should have
var fillup = Fillup.object()
To actually call the constructor for the class (and allow the compiler to determine the class in this case)