How does the closure work in the method mapValues? - ios

I'm trying to understand this how the mapValues method works in the following code from Calendar Heatmap.
First, a function loads a dictionary:
private func readHeatmap() -> [String: Int]? {
guard let url = Bundle.main.url(forResource: "heatmap", withExtension: "plist") else { return nil }
return NSDictionary(contentsOf: url) as? [String: Int]
}
heatmap.plist is a key/value list like this:
<key>2019.5.3</key>
<integer>3</integer>
<key>2019.5.5</key>
<integer>4</integer>
<key>2019.5.7</key>
<integer>3</integer>
A property is initialized using the above function:
lazy var data: [String: UIColor] = {
guard let data = readHeatmap() else { return [:] }
return data.mapValues { (colorIndex) -> UIColor in
switch colorIndex {
case 0:
return UIColor(named: "color1")!
case 1:
return UIColor(named: "color2")!
case 2:
return UIColor(named: "color3")!
case 3:
return UIColor(named: "color4")!
default:
return UIColor(named: "color5")!
}
}
}()
Finally, the data property defined above is used in the following function:
func colorFor(dateComponents: DateComponents) -> UIColor {
guard let year = dateComponents.year,
let month = dateComponents.month,
let day = dateComponents.day else { return .clear}
let dateString = "\(year).\(month).\(day)"
return data[dateString] ?? UIColor(named: "color6")!
}
Apple's documentation states that mapValues returns a dictionary "containing the keys of this dictionary with the values transformed by the given closure."
My questions is, what exactly is the value colorIndex passed into the closure in data.mapValues { (colorIndex) -> UIColor in? Is it from heatmap.plist? I'm confused how a String is passed into date, date[dateString] from the colorFor(dateComponents: ) function, but colorIndex is Int.

Originally, data is like this:
"2019.5.3" : 3
"2019.5.5" : 4
"2019.5.7" : 3
Suppose you did data.mapValues(f), where f is a function, the resulting dictionary will look like this:
"2019.5.3" : f(3)
"2019.5.5" : f(4)
"2019.5.7" : f(3)
So now, the value type of the dictionary changes to the return type of f, while the key type remains unchanged.
what exactly is the value colorIndex passed into the closure?
It's every value in data. Every value will be passed into closure once.
To see this more clearly, I've written one possible way that mapValues could be implemented:
extension Dictionary {
func myMapValues<T>(_ transform: (Value) throws -> T) rethrows -> [Key: T] {
var retVal = [Key: T]()
for entry in self {
retVal[entry.key] = try transform(entry.value)
}
return retVal
}
}
Is it from heatmap.plist?
Indirectly, yes. The contents of the local variable data (the [String: Int]) was originally from heatmap.plist, but mapValues operates directly on the data already read from the file.
I'm confused how a String is passed into data, data[dateString] from the colorFor(dateComponents: ) function, but colorIndex is Int.
colorIndex is irrelevant here. colorIndex is simply the name of the function parameter of the function that you pass to mapValues. mapValues has been called at this point, and the dictionary's values have been transformed.
You can pass a String into data because the data dictionary has Strings as keys. Recall that mapValues doesn't change the key type. Note that this data is different from the local variable data. I'm talking about the lazy property data, of type [String: UIColor].

Related

Safely unwrapping optional values and add it to Alamofire parameters [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have a computed property of type Parameters in my APIRouter
// MARK: - Parameters
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
var params: Parameters = ["main_category_id": doctorsFilter.0, "page": doctorsFilter.1, "specialty_id": doctorsFilter.2, "city_id": doctorsFilter.3, "region_id": doctorsFilter.4, "name": doctorsFilter.5, "company_id": doctorsFilter.6, "order_by": doctorsFilter.7]
return params
default:
return nil
}
}
some values in the Typealias called doctorsFilter are optional.
currently I have a warning asking me to provide default value for the optional values, and I don't want to provide default values , I want to check if the value exist to add it, otherwise i will not add the key and the value
how can I safely unwrap the optional values and add it to the parameters dictionary with out saying if let for all optional values?
example:
if let specialtyID = doctorsFilter.2 {
params["specialty_id"] = specialtyID
}
I don't want to unwrap it this way as I will check for all optional values and it will take more lines of code
EDIT:-
the DoctorsFilter type is documented, when I initialize an instance of type DoctorsFilter the autocomplete tells me which of them is what, I I've thought about making the DoctorsFilter class before but I'm looking for another way if any, maybe a built in reserved word can handle the whole situation! , I want to make it simple as much as possible.
making a function that handles the dictionary and returns it in DoctorsFilter class is an option. I'm thinking of adding this function to the APIRouter, is it fine to add it there? is it the rule of the APIRouter to handle the parameters ? or the APIRouter just interested in taking the parameters and will not handle it ?
There is no "one line" solution, but you can use KeyPaths to reduce the series of if let ... statements down to a loop.
Start by creating a struct for your filter rather than using a tuple.
To facilitate this, we define a protocol for Parameterable - This protocol requires a dictionary that maps parameter names (String) to the property (KeyPath) that holds that parameter name as well as a function to return the parameters dictionary.
protocol Parameterable {
var paramNames: [String:KeyPath<Self,String?>] {get}
func parameters() -> [String:Any]
}
Use an extension to create a default implementation of the parameters() function, as the code will be the same for all Parameterables. It iterates over the dictionary entries and uses the associated KeyPath to access the relevant property and put it in the output dictionary. If a given property is nil then it simply isn't added to the output dictionary, because that is how dictionaries work. No need to explicitly check.
(If you import Alamofire then you can use the typedef Parameters where I have used [String:Any])
extension Parameterable {
func parameters() -> [String:Any] {
var parameters = [String:Any]()
for (paramName,keypath) in self.paramNames {
parameters[paramName]=self[keyPath:keypath]
}
return parameters
}
}
Use this protocol to create a DoctorsFilter implementation:
struct DoctorsFilter: Parameterable {
var mainCategoryId: String?
var page: String?
var specialtyId: String?
var cityID: String?
var regionId: String?
var name: String?
var companyId: String?
var orderBy: String?
let paramNames:[String:KeyPath<Self,String?>] = [
"main_category_id":\.mainCategoryId,
"page":\.page,
"specialty_id":\.specialtyId,
"city_id":\.cityID,
"region_id":\.regionId,
"name":\.name,
"company_id":\.companyId,
"order_by":\.orderBy]
}
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
return doctorsFilter.parameters()
case .someOtherThing(let someOtherThing):
return someOtherThing.parameters()
default:
return nil
}
}
}
The other approach is to simply split your creation of the parameters dictionary into multiple lines; If you assign nil against a dictionary key then there is no key/value pair stored in the dictionary for that key. In this case I have left your tuple approach in place, but you could use the struct (and I strongly suggest you do so)
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
var params: Parameters()
params["main_category_id"] = doctorsFilter.0
params["page"] = doctorsFilter.1
params["specialty_id"] = doctorsFilter.2
params["city_id"] = doctorsFilter.3
params["region_id"] = doctorsFilter.4
params["name"] = doctorsFilter.5
params["company_id"] = doctorsFilter.6
params["order_by"] = doctorsFilter.7
return params
default:
return nil
}
}
If we want to handle mixed properties, rather than just optional strings, we need to modify the code slightly. We need to use PartialKeyPath. This makes the code a little more complex since the subscript operator for a PartialKeyPath returns a double optional. This needs to be handled.
protocol Parameterable {
var paramNames: [String:PartialKeyPath<Self>] {get}
func parameters() -> [String:Any]
}
extension Parameterable {
func parameters() -> [String:Any] {
var parameters = [String:Any]()
for (paramName,keypath) in self.paramNames {
let value = self[keyPath:keypath] as? Any?
if let value = value {
parameters[paramName] = value
}
}
return parameters
}
}
struct DoctorsFilter:Parameterable {
var mainCategoryId: String?
var page: String?
var specialtyId: String?
var cityID: Int
var regionId: String?
var name: String?
var companyId: String?
var orderBy: String?
let paramNames:[String:PartialKeyPath<Self>] =
["main_category_id":\Self.mainCategoryId,
"page":\Self.page,
"specialty_id":\Self.specialtyId,
"city_id":\Self.cityID,
"region_id":\Self.regionId,
"name":\Self.name,
"company_id":\Self.companyId,
"order_by":\Self.orderBy]
}
There are three primary ways to safely unwrap an optional. You can also provide default values if you wish to unwrap an optional.
Guard Statement Unwrapping
var firstString: String?
// In some cases you might performa a break, continue or a return.
guard let someString = firstString else { return }
print(someString)
If Let Unwrapping
var secondString: String?
var thirdString: String?
thirdString = "Hello, World!"
// Notice that we are able to use the same "IF LET" to unwrap
// multiple values. However, if one fails, they all fail.
// You can do the same thing with "Guard" statements.
if let someString = secondString,
let someOtherString = thirdString {
print(someString)
print(someOtherString)
} else {
// With this code snippet, we will ALWAYS hit this block.
// because secondString has no value.
print("We weren't able to unwrap.")
}
Default Value Unwrapping
var fourthString: String?
// The ?? is telling the compiler that if it cannot be unwrapped,
// use this value instead.
print(fourthString ?? "Hello, World")
In Swift it is recommended that anytime you see a ! that you use some form of unwrapping. Swift is very "Type Safe".
Here's a resource you can use for Optionals.
https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html
Your Solution
Your solution might look something like this.
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
if let mainCatID = doctorsFilter.0,
let page = doctorsFilter.1,
let specialtyID = doctorsFilter.2,
let cityID = doctorsFilter.3,
let regionID = doctorsFilter.4,
let name = doctorsFilter.5,
let companyID = doctorsFilter.6,
let orderBy = doctorsFilter.7 {
params: Parameters = ["main_category_id": mainCatID,
"page": page,
"specialty_id": specialtyID,
"city_id": cityID,
"region_id": regionID,
"name": name,
"company_id": companyID,
"order_by": orderBy]
return params
} else {
//Unable to safely unwrap, return nothing.
return nil
}
default:
return nil
}
}

Declaring a multi-type array

I would like to have my function return an array in which the first element is a String and the second is a UIImageView object. E.g.
["An example string", UIImageView()]
How do I tell the function that this will be returned in the section that follows the ->
So basically I want a function like this:
func DoSomething(num:Int) -> Array[String, UIImageView()] {
// Each of the following objects in the Array are UIImageView objects
Let imageViewObjects = [image1, image2, image3]
return [String(num), imageViewObjects[num]]
}
But the part I know I am getting wrong is the
Array[String, UIImageView]
P.S. I need to declare this because if I use [AnyObject] it will raise an error later on in the code basically saying that it cant manipulate an object of type AnyObject
Note that an Array is declared as [Int] or Array<Int> rather than Array[Int]. [Int] and Array<Int> are the same thing. You don't need to use both at once.
The easiest thing to do is use a tuple, declared like this:
(String, UIImageView)
I would use it like this (your code with corrections):
import UIKit
typealias StringView = (String, UIImageView)
// Returning an Optional to pass back that num may be out of range
// note that I'm using the typealias StringView here
func DoSomething(num:Int) -> StringView? {
let image1 = UIImageView()
let image2 = UIImageView()
let image3 = UIImageView()
let imageViewObjects = [image1, image2, image3]
// Need to check that num is a valid index
guard num < imageViewObjects.count else { return nil }
// return the tuple if num is valid
return (String(num), imageViewObjects[num])
}
Example usage:
if let returned = DoSomething(2) {
// printing the first item in returned tuple
print(returned.0)
}
// output: "2"
You can also use protocols to create a common protocol, add it to the classes in an extension, and then use the protocol in the declaration:
protocol Foo {}
extension String : Foo {}
extension UIImageView: Foo {}
var myArray:[Foo] ...
If you're going to use the returned value in a lot of places you might want to make it a full-blown struct or class:
import UIKit
struct StringView {
let string:String
let view:UIImageView
}
// Returning an Optional to pass back that num may be out of range
// note that I'm using the typealias StringView here
func DoSomething(num:Int) -> StringView? {
let imageViewObjects = [UIImageView(),
UIImageView(),
UIImageView()]
// Need to check that num is a valid index
guard num < imageViewObjects.count else { return nil }
// return the tuple if num is valid
return StringView(string: String(num), view: imageViewObjects[num])
}
if let returned = DoSomething(2) {
// printing the member "string" in the returned struct
print(returned.string)
}
// output: "2"
A tuple is usually the better choice unless you are using custom structs and classes.
You can use Dictionary objects in swift, here you can use key as your String and value as array of ImageView
let imageViewObjects = [image1, image2, image3]
let array : [String : UIImageView] = [
String(num) : imageViewObjects[num]
]
If you want to return only array you can do like this
let imageViewObjects = [image1, image2, image3]
var array : [AnyObject] = [AnyObject]()
array.append(String(num))
array.append(imageViewObjects[num])
In this you have to be sure that first object is a String and second is array of UIImageView

Closure argument becomes nil

I have a weird issue trying to validate user input. I'm using a wrapper for a form framework and I want it to be able to validate user input.
The trouble is, when I call the closure with the userValue argument, it ends up being nil and all checks return false:
class FormRowWrap {
var tag: String
var row: AnyObject
var verification: (value: Any?) -> Bool
init(tag: String, row:AnyObject, verification:(Any?) -> Bool) {
self.tag = tag
self.row = row
self.verification = verification
}
}
class InsertViewController: FormViewController {
let rows = [
{
let tag = "Fuel Name"
let row = FormRowWrap(tag: tag,
row:TextRow(tag) {
$0.title = tag
// $0.value = last known user default
},
verification:({(value: Any?) -> Bool in
if let thing = value as? String {
//^----- the value in a **breakpoint here is nil**
//
if !thing.isEmpty {
return true
}
}
return false
}))
return row
}() as FormRowWrap,
{
let tag = "Price"
let row = FormRowWrap(tag: tag,
...
func formValuesAreValid(values: [String: Any?]) -> Bool {
var result = false
for rowWrap in self.rows {
let userValue = values[rowWrap.tag]
print("userValue \(userValue) forTag: \(values[rowWrap.tag])")
// ^---- this prints userValue **Optional(Optional("Ghh")) forTag: Optional(Optional("Ghh"))**
let entryIsValid = rowWrap.verification(value: userValue)
if (!entryIsValid) {
result = false
return result
}
}
result = true
return result
}
If I run rowWrap.verification(value:"test") it returns true, so I think it's an issue about properly unwrapping values.
Your function needs an Optional but userValue is an Optional inside another Optional:
Optional(Optional("Ghh"))
So when you force unwrap it with
let entryIsValid = rowWrap.verification(value: userValue!)
what happens actually is that you unwrap the first layer and get back an Optional:
Optional("Ghh")
which is what your function signature
(value: Any?) -> Bool
needs.
About why it's wrapped twice:
with
formValuesAreValid(values: [String: Any?])
the values in the values dictionary are Optionals, and then when you access the dictionary:
let userValue = values[rowWrap.tag]
you get yet another Optional - because accessing a dictionary always returns an Optional, so in this case values[rowWrap.tag] returns an "Optional Optional".
Then somewhere else you unwrap once thinking you'll get the value if let thing = value as? String but you get the inner Optional instead and your next check fails.
Found a way to make it work by force unwrapping the "userValue":
let entryIsValid = rowWrap.verification(value: userValue!)
I still don't understand why this works and why it doesn't work with the argument as wrapped optional.

Swift 2.0 Tuple pattern element label must be '_'

I have been trying to fix all my code since swift 2.0 update. I have a problem that seems to be the way tuples work now:
public func generate() -> AnyGenerator <(String, JSON)> {
switch self.type {
case .Array:
let array_ = object as! [AnyObject]
var generate_ = array_.generate()
var index_: Int = 0
return anyGenerator{
if let element_: AnyObject = generate_.next() {
return ("\(index_++)", JSON(element_))
} else {
return nil
}
}
case .Dictionary:
let dictionary_ = object as! [String : AnyObject]
var generate_ = dictionary_.generate()
return anyGenerator{
if let (key_: String, value_: AnyObject) = generate_.next() {
return (key_, JSON(value_))
} else {
return nil
}
}
default:
return anyGenerator{
return nil
}
}
}
Specifically the line:
if let (key_: String, value_: AnyObject) = generate_.next()
Is throwing the error: Tuple pattern element label 'key' must be '_'
I tried to make that change already, but I didnt work...
Any ideas?
The problem is: We cannot use type annotation inside of tuple patterns anymore.
In the release notes:
Type annotations are no longer allowed in patterns and are considered part of the outlying declaration. This means that code previously written as:
var (a : Int, b : Float) = foo()
needs to be written as:
var (a,b) : (Int, Float) = foo()
if an explicit type annotation is needed. The former syntax was ambiguous with tuple element labels. (20167393)
So, you can:
if let (key_, value_): (String, AnyObject) = generate_.next() {
But in this case, you could omit : (String, AnyObject):
if let (key_, value_) = generate_.next() {

How to check if NSDictionary is not nil in Swift 2

I'm getting NSDictionary as parameter in my function but having problem because don't know how to check if that parameter is not nil.
My function looks like this:
func doSmth(val : NSDictionary)
Inside my function I'm trying to get some values:
let action = val["action"] as! String
But getting error "fatal error: unexpectedly found nil while unwrapping an Optional value" when receive parameter val as nil.
The error is due to assuming (force casting) a value that can sometimes be nil. Swift is awesome, because it allows conditional unwraps and conditional casts in very concise statements. I recommend the following (for Swift 1-3):
Use "if let" to conditionally check for "action" in the dictionary.
Use as? to conditionally cast the value to a String
if let actionString = val["action"] as? String {
// action is not nil, is a String type, and is now stored in actionString
} else {
// action was either nil, or not a String type
}
You can also access the allKeys or alternatively allValues property and check if the array contains any elements like so:
let dic = NSDictionary()
let total = dic.allKeys.count
if total > 0 {
// Something's in there
}
else {
// Nothing in there
}
EDIT
Here is how you can detect if the NSDictionary is nil, if they key you are looking for exists, and if it does attempt to access it's value:
let yourKey = "yourKey"
if let dic = response.someDictionary as? NSDictionary {
// We've got a live one. NSDictionary is valid.
// Check the existence of key - OR check dic.allKeys.containsObject(yourKey).
let keyExists: Bool = false;
for var key as String in dic.allKeys {
if key == yourKey {
keyExists = true;
}
}
// If yourKey exists, access it's possible value.
if keyExists == true {
// Access your value
if let value = dic[yourKey] as? AnyObject {
// We're in business. We have the value!
}
else {
// yourKey does not contain a value.
}
}
else {
// yourKey does not exist in NSDictionary.
}
}
else {
// Call an ambulance. NSDictionary is nil.
}
That's not particularly related to Swift 2.
If the dictionary could be nil declare it as optional
func doSmth(val : NSDictionary?)
Then use optional bindings to check
if let valIsNonOptional = val {
let action = valIsNonOptional["action"] as! String
}
The code assumes that there is a key action containing a String value anyway if the dictionary is not nil
Your dictionary parameter is probably not nil. The problem is probably that your dictionary doesn't contain a value for the key "action".
When you say val["action"], the dictionary (being an NSDictionary) returns an Optional<AnyObject>. If val contains the key "action", it returns Some(value). If val doesn't contain the key "action", it returns None, which is the same as nil.
You can unwrap the Optional in your cast, and choose a course of action based on whether it was nil, using an if-let statement:
if let action = val["action"] as? String {
// action is a String, not an Optional<String>
} else {
// The dictionary doesn't contain the key "action", and
// action isn't declared in this scope.
}
If you really think val itself might be nil, you need to declare your function that way, and you can unwrap val without renaming it using a somewhat confusing guard statement:
func doSmth(val: NSDictionary?) {
guard let val = val else {
// If val vas passed in as nil, I get here.
return
}
// val is now an NSDictionary, not an Optional<NSDictionary>.
...
}

Resources