Redesign Init (MKAnnotation) - swift

class Point: NSObject, MKAnnotation {
var id: String?
var title: String?
var coordinate: CLLocationCoordinate2D
var subtitle: String?
init(id: String, dictionary: Dictionary<String, AnyObject>){
self.id = id
if let title = dictionary["title"] as? String {
self.title = title
}
if let subtitle = dictionary["subtitle"] as? String {
self.subtitle = subtitle
}
if let coords = dictionary["coordinates"] as? [String:[String:Double]] {
self.coordinate.latitude = coords.values.first!["latitude"]!
self.coordinate.longitude = coords.values.first!["longitude"]!
}
super.init()
}
/*
init(title: String, subtitle: String, coordinate: CLLocationCoordinate2D){
self.title = title
self.subtitle = subtitle
self.coordinate = coordinate
}*/
}
I implemented initializer for creating Point on Map. I must redesign init because I use Firebase and results from database are in dictionary.
It writes: Property self.coordinate not initialized at implicitly generated super.init call

init(id: String, dictionary: Dictionary<String, AnyObject>){
self.id = id
if let title = dictionary["title"] as? String {
self.title = title
}
if let subtitle = dictionary["subtitle"] as? String {
self.subtitle = subtitle
}
if let coords = dictionary["coordinates"] as? [String:[String:Double]] {
self.coordinate.latitude = coords.values.first!["latitude"]!
self.coordinate.longitude = coords.values.first!["longitude"]!
}
super.init()
}
So, the problem here mostly lies in the fact that you only ever initialize your coordinate property within an if. Because coordinate is not marked as an optional, it much be given a value before you call super.init(), as per the rules of Swift initializers.
And because you initialize it within an if only, there's a chance that the if fails and you never initialize it at all. This would leave the instance in a completely invalid state, which is not allowed per the rules of Swift initializers and fails the first safety check.
Safety check 1
A designated initializer must ensure that all of the properties
introduced by its class are initialized before it delegates up to a
superclass initializer.
You could make coordinate an optional instance variable, must like your other instance variables. However, I'd argue that probably makes most sense is that id & title are probably also non-optional, and all of these properties probably should be let constants.
As such, I'd rewrite your class and that initializer as such:
class Point: NSObject, MKAnnotation {
let id: String
let title: String?
let coordinate: CLLocationCoordinate2D
let subtitle: String?
init?(id: String, dictionary: [String: AnyObject]){
self.id = id
title = dictionary["title"] as? String
subtitle = dictionary["subtitle"] as? String
guard let
coordinates = dictionary["coordinates"] as? [String: [String: Double]],
latitude = coordinates.values.first?["latitude"],
longitude = coordinates.values.first?["longitude"] else {
return nil
}
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
super.init()
}
}
Now you have a failable initializer. If any of the values in the dictionary is not correct, the initializer returns nil. Otherwise, your values are all properly initialized.

Related

Inherit from EKCalendarItem

Because EKEvent can not hold extra properties, I was thinking of creating my own class Event and inherit from EKCalendarItem (same as EKEvent class).
But I get a FrozenClass error, which is quite new to me. Does anybody have any idea of what that means? EKCalendarItem is an open class, so as far as I know I should be able to inherit from that. Or... am I wrong here?
The exact error:
'+[MyApp.Event frozenClass]: unrecognized selector sent to class
0x105667068'
My code:
class Event: EKCalendarItem {
// MARK: - Properties
var id: String
var startDate: Date
var endDate: Date
var isAllDay: Bool
// MARK: - Inits
init?(id: String, dictionary: [String: Any]) {
guard
let title = dictionary["title"] as? String,
let startDate = dictionary["startDate"] as? Timestamp,
let endDate = dictionary["endDate"] as? Timestamp,
let isAllDay = dictionary["isAllDay"] as? Bool
else { return nil }
self.id = id
self.startDate = startDate.dateValue()
self.endDate = endDate.dateValue()
self.isAllDay = isAllDay
super.init()
self.location = dictionary["location"] as? String
self.title = title
self.notes = dictionary["notes"] as? String
}
convenience init?(snapshot: QueryDocumentSnapshot) {
self.init(id: snapshot.documentID, dictionary: snapshot.data())
}
}

swift 4 model class error: Return from initializer without initializing all stored properties

I created a model class for notes that have the attributes title and content. I am getting an error in the second init that says: Return from initializer without initializing all stored properties. Im feel like there is something I am missing and I cant seem to see it. This is the code:
class Note{
private var _title: String
private var _content: String
var title: String!{
return self._title
}
var content: String!{
return self._content
}
init(title: String, content: String){
self._title = title
self._content = content
}
init(noteData: Dictionary<String, AnyObject>) {
if let title = noteData["title"] as? String {
self._title = title
}
if let content = noteData["content"] as? String {
self._content = content
}
}
}
You have to initialise all properties of your class in its initializer. Regarding your dictionary initializer you should make it fallible and return nil in case of missing key/value pairs:
class Note {
let title: String
let content: String
init(title: String, content: String) {
self.title = title
self.content = content
}
init?(dictionary: [String: Any]) {
guard
let title = dictionary["title"] as? String,
let content = dictionary["content"] as? String
else { return nil }
self.title = title
self.content = content
}
}
Note that if use a struct instead of a class you don't even have to implement an initializer for your custom object:
struct Note {
let title: String
let content: String
init?(dictionary: [String: Any]) {
guard
let title = dictionary["title"] as? String,
let content = dictionary["content"] as? String
else { return nil }
self.title = title
self.content = content
}
}
The problem is that in the second init, if the unwrap fails the properties will not be set. For the init to work correctly the properties that are not optional will all need values. The best way to fix this is to make the properties optional.
Optional properties:
private var title: String?
private var content: String?
By making the properties optional you can avoid doing the if let in the init:
self.title = noteDate["title"]
Make sure to unwrap safely when you use the properties though!

Cannot convert value of type 'String.Type' to expected argument type 'String'

Swift 4 / Xcode 9.3 / OS X 10.13.4 / iOS 11.3 & 11.2.6
I'm trying to build my app and I'm getting the above error message. I've checked the code over and over and over and I can't figure out why I'm getting this error. I'm not certain which part of the code you need to see, but here is the page I'm getting the error on. The error code is flagging the very last line of code.
import UIKit
import os.log
class Bonus: NSObject, NSCoding {
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("bonuses")
//MARK: Properties
var bonusCode: String
var category: String
var name: String
var value: Int
var city: String
var state: String
var photo: UIImage?
//MARK: Initialization
init?(bonusCode: String, category: String, name: String, value: Int, city: String, state: String, photo: UIImage?) {
// The name must not be empty.
guard !name.isEmpty else {
return nil
}
// The value must not be negative.
guard (value >= 0) else {
return nil
}
// Initialize stored properties.
self.bonusCode = bonusCode
self.category = category
self.name = name
self.value = value
self.city = city
self.state = state
self.photo = photo
}
//MARK: Types
struct PropertyKey {
static let bonusCode = "bonusCode"
static let category = "category"
static let name = "name"
static let value = "value"
static let city = "city"
static let state = "state"
static let photo = "photo"
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(bonusCode, forKey: PropertyKey.bonusCode)
aCoder.encode(category, forKey: PropertyKey.category)
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(value, forKey: PropertyKey.value)
aCoder.encode(city, forKey: PropertyKey.city)
aCoder.encode(state, forKey: PropertyKey.state)
aCoder.encode(photo, forKey: PropertyKey.photo)
}
required convenience init?(coder aDecoder: NSCoder) {
// The name is required. If we cannot decode a name string, the initializer should fail.
guard let bonusCode = aDecoder.decodeObject(forKey: PropertyKey.bonusCode) as? String else {
os_log("Unable to decode the Code for a Bonus object.", log: OSLog.default, type: .debug)
return nil
}
// Because photo is an optional property of Meal, just use conditional cast
let photo = aDecoder.decodeObject(forKey: PropertyKey.photo) as? UIImage
let category = aDecoder.decodeObject(forKey: PropertyKey.category)
let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
let city = aDecoder.decodeObject(forKey: PropertyKey.city)
let state = aDecoder.decodeObject(forKey: PropertyKey.state)
// Must call designated initializer.
self.init(bonusCode: String, category: String, name: String, value: Int, city: String, state: String, photo: UIImage?)
}
}
The error is flagging on the bonusCode: String, specifically on the S in String.
I'm pretty new to programming, but I only found one other search result for this specific question, and the other similar ones seemed to be very specific to the code being used.
You have to pass the decoded values rather than the types in the last line and the line to decode the name is missing and you have to cast the other string objects. The force unwrapping is safe because all non-optional values are encoded properly.
let name = aDecoder.decodeObject(forKey: PropertyKey.name) as! String
let category = aDecoder.decodeObject(forKey: PropertyKey.category) as! String
let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
let city = aDecoder.decodeObject(forKey: PropertyKey.city) as! String
let state = aDecoder.decodeObject(forKey: PropertyKey.state) as! String
...
self.init(bonusCode: bonusCode, category: category, name: name, value: value, city: city, state: state, photo: photo)
self.init(bonusCode: String,
category: String,
name: String,
value: Int,
city: String,
state: String,
photo: UIImage?)
This is a function call, not a function declaration.
You are passing types instead of values into a function call.
You should be doing this instead:
self.init(bonusCode: bonusCode,
category: category,
name: name,
value: value,
city: city,
state: state,
photo: photo)
So finally, your init should look like (with a little improvement):
required convenience init?(coder aDecoder: NSCoder) {
//NOTE: `decodeObject(forKey:)` returns optional `Any` and hence all those `as? String`
//name was missing
let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String
let bonusCode = aDecoder.decodeObject(forKey: PropertyKey.bonusCode) as? String
let category = aDecoder.decodeObject(forKey: PropertyKey.category) as? String
let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
let city = aDecoder.decodeObject(forKey: PropertyKey.city) as? String
let state = aDecoder.decodeObject(forKey: PropertyKey.state) as? String
let photo = aDecoder.decodeObject(forKey: PropertyKey.photo) as? UIImage
/*
Only photo is optional in order to init but the rest are required
Hence the optional binding for the rest below
*/
if let name = name,
let bonusCode = bonusCode,
let category = category,
let city = city,
let state = state {
// Must call designated initializer.
self.init(bonusCode: bonusCode,
category: category,
name: name,
value: value,
city: city,
state: state,
photo: photo)
}
else {
/*
Some required object/s were missing so we can't call the
designated initializer unless we want to give default values.
Hence return nil
*/
return nil
}
}

Using a designated initialiser VS a static method to populate a struct

I have been working on a struct that parses a JSON data, that data is from Dark sky. While I was working on it, something hit my brain and been pondering on it ever since.
Traditionally, we have been using designated initializers (although struct gives you a member-wise initializer for free) to instantiate an object. But, we could easily use a static function that returns itself and that function populates the properties.
like so:
struct WeatherForecastData {
// Weather data
var apparentTemperature: Double?
var icon: String?
var precipProbability: Double?
var pressure: Double?
static func map(_ data: [String: AnyObject]) -> WeatherForecastData {
var p = WeatherForecastData()
p.apparentTemperature = data["apparentTemperature"] as? Double ?? nil
p.icon = data["icon"] as? String ?? nil
p.precipProbability = data["precipProbability"] as? Double ?? nil
p.pressure = data["pressure"] as? Double ?? nil
return p
}
}
notice the static method, if we replace it with a designated initializer, it would be doing the same thing as a static method. My question is when should we use static methods to instantiate an object instead of the traditional designated initializer?
Why wouldn't you just use a custom initializer like this?
struct WeatherForecastData {
var apparentTemperature: Double
var icon: String
var precipProbability: Double
var pressure: Double
init?(data:Dictionary<String,Any>) {
guard
let apparentTemperature = data["apparentTemperature"] as? Double,
let icon = data["icon"] as? String,
let precipProbability = data["precipProbability"] as? Double,
let pressure = data["pressure"] as? Double
else {
print("Invalid Data")
return nil
}
self.apparentTemperature = apparentTemperature
self.icon = icon
self.precipProbability = precipProbability
self.pressure = pressure
}
}

Swift 3 optional parameters

Is it possible to create optional initialization parameters in Swift so I can create an object from JSON with the values returned from the API call, but then when I'm saving that object later I can also save the downloaded UIImage for one of the urls I got before.
Example:
class Story: NSObject, NSCoding {
var id: Int?
var title, coverImageURL: String?
var coverImage: UIImage?
required init?(anId: Int?, aTitle: String?, aCoverImageURL: String?) {
self.id = anId
self.title = aTitle
self.coverImageURL = aCoverImageURL
}
convenience init?(json: [String: Any]) {
let id = json["id"] as? Int
let title = json["title"] as? String
let coverImageURL = json["cover_image"] as? String
self.init(
anId: id,
aTitle: title,
aCoverImageURL: coverImageURL,
)
}
Then Later I want to save objects to memory
//MARK: Types
struct PropertyKey {
static let id = "id"
static let title = "title"
static let coverImageURL = "coverImageURL"
static let coverImage = "coverImage"
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: PropertyKey.id)
aCoder.encode(title, forKey: PropertyKey.title)
aCoder.encode(coverImageURL, forKey: PropertyKey.coverImageURL)
aCoder.encode(coverImage, forKey: PropertyKey.coverImage)
}
required convenience init?(coder aDecoder: NSCoder) {
guard let id = aDecoder.decodeObject(forKey: PropertyKey.id) as? Int else {
os_log("Unable to decode the id for a Story object.", log: OSLog.default, type: .debug)
return nil
}
guard let title = aDecoder.decodeObject(forKey: PropertyKey.title) as? String else {
os_log("Unable to decode the title for a Story object.", log: OSLog.default, type: .debug)
return nil
}
let coverImageURL = aDecoder.decodeObject(forKey: PropertyKey.coverImageURL) as? String
let coverImage = aDecoder.decodeObject(forKey: PropertyKey.coverImage) as? UIImage
self.init(
anId: id,
aTitle: title,
aCoverImageURL: coverImageURL,
coverImage: coverImage,
)
}
Does this make sense? I want to be able to save a Story object as soon as I get the response from the API, but later when I save the story to memory, I want to be able to save the fetched UIImage for the coverImage.
How would I do that?
I'm not sure why no one took the easy points on this answer, but the answer is to simply make your properties optionals, and then you can set them with a value, or nil. You can also create convenience initializers that automatically set certain values to nil if you want. So, using my app as an example, I have a model that gets built from an API call. that model has values like id, created_at, etc that don't exist until a record is saved to the server, but I create objects locally, store them, and eventually send them to the server, so I need to be able to set the above values only when creating an object from JSON, so here is what I did:
class Story: NSObject, NSCoding {
var id: Int?
var title, coverImageURL: String?
var coverImage: UIImage?
required init?(anId: Int?, aTitle: String?, aCoverImageURL: String?) {
self.id = anId
self.title = aTitle
self.coverImageURL = aCoverImageURL
}
convenience init?(json: [String: Any]) {
let id = json["id"] as? Int
let title = json["title"] as? String
let coverImageURL = json["cover_image"] as? String
self.init(
anId: id,
aTitle: title,
aCoverImageURL: coverImageURL,
)
}
convenience init?(aTitle: String, aCoverImage: UIImage?) {
let title = aTitle
let subtitle = aSubtitle
let coverImage = aCoverImage
let isActive = activeStatus
self.init(
anId: nil,
aTitle: title,
aCoverImageURL: nil,
aCoverImage: coverImage,
)
}
As you can see, I only set two of the values when I'm creating an object locally, and the other values are just set to nil. To allow a value to be set to nil, just make it an optional when setting it. Simple!