Swift - Changing constant doesn't show error - swift

I'm reading a tutorial from a book. I can't attach the book here. In a chapter an UIImage constant is declared and its value is assigned in next lines. It is not a var neither optional. It runs successfully. How does it work?
extension ViewController: MKMapViewDelegate {
private func addAnnotations() {
for business in businesses {
guard let yelpCoordinate = business.location.coordinate else {
continue
}
let coordinate = CLLocationCoordinate2D(latitude: yelpCoordinate.latitude,
longitude: yelpCoordinate.longitude)
let name = business.name
let rating = business.rating
let image:UIImage //Constant non-optional
switch rating {
case 0.0..<3.5:
image = UIImage(named: "bad")!
case 3.5..<4.0:
image = UIImage(named: "meh")!
case 4.0..<4.75:
image = UIImage(named: "good")!
case 4.75..<5.0:
image = UIImage(named: "great")!
default:
image = UIImage(named: "bad")!
}
let annotation = BusinessMapViewModel(coordinate: coordinate,
name: name,
rating: rating, image: image)
mapView.addAnnotation(annotation)
}
}
}

First, you should know that it is perfectly fine in Swift to declare a variable and assign it a value on the next line, as long as you don't refer to that variable before it is assigned.
let a: Int
... // you can't use "a" here
a = 10 // OK!
Look at the switch statement after the variable declaration. A switch statement must be exhaustive, meaning that at least one case of it will be run. In this switch statement, every case has a single statement that assigns to image, and there are no fallthroughs. From these observations, both us and the compiler can conclude that image will be assigned (and assigned only once) after the switch statement, hence you can use it in the line:
let annotation = BusinessMapViewModel(coordinate: coordinate,
name: name,
rating: rating, image: image)

From the Swift 5.1 reference, here
When a constant declaration occurs in the context of a function or method, it can be initialized later, as long as it is guaranteed to have a value set before the first time its value is read.

Related

Numpy's argmax() in Swift

I'm working on a project which converts user's face to emoji. I use Apple's ARKit in this purpose.
I need to get the most probable option. I wrote this code:
func renderer(for anchor: ARAnchor) {
guard let faceAnchor = anchor as? ARFaceAnchor else {
return
}
let shapes = faceAnchor.blendShapes
let browDownLeft = shapes[.browDownLeft]!.doubleValue
let browInnerUp = shapes[.browInnerUp]!.doubleValue
let browOuterUpLeft = shapes[.browOuterUpLeft]!.doubleValue
let leftBrowMax = max(browDownLeft, browInnerUp, browOuterUpLeft)
switch leftBrowMax {
case browDownLeft:
userFace.leftBrow = .browDown
case browInnerUp:
userFace.leftBrow = .browInnerUp
case browOuterUpLeft:
userFace.leftBrow = .browOuterUp
default:
userFace.leftBrow = .any
}
}
I need to duplicate function's body six time (for brows, eyes and mouth sides), so I want to write it in a more convenient way. Is there any options in Swift like numpy's argmax function? Also I need to specify arguments range, because arguments for mouth should not be compared with arguments for brows.
You can use something like this:
func maxBlendShape(for blendShapes: [ARFaceAnchor.BlendShapeLocation], in shape: [ARFaceAnchor.BlendShapeLocation: NSNumber]) -> Double? {
blendShapes
.compactMap { shape[$0] }
.map(\.doubleValue)
.max()
}
Usage would then be something like this:
maxBlendShape(for: [.browDownLeft, .browInnerUp, .browOuterUpLeft], in: faceAnchor.blendShapes)
Note: Nothing here is specific to ARKit, you just filter some keys from the dictionary and find their max value. A generic solution could look like this:
extension Dictionary where Value == NSNumber {
func maxDouble(for keys: [Key]) -> Double? {
keys
.compactMap({self[$0]})
.map(\.doubleValue)
.max()
}
}
faceAnchor.blendShapes.maxDouble(for: [.browInnerUp, .browDownLeft, .browOuterUpLeft])

receiving right type in function

I try to build some NestedView, which loads view inside view inside... depends of objects tree. So I made a short test in Playground:
class First {}
class Second:First {}
class Dodd {
func takeIt<Foo, Bar>(content:[Foo], type: Bar.Type) {
print (Foo.self, Bar.self)
print (content as? [Bar] ?? [])
}
}
let arrayOfFirst:[First] = [First()]
let arrayOfSecond:[Second] = [Second(), Second(), Second()]
let dodd = Dodd()
let firstType = First.self
let secondType = Second.self
dodd.takeIt(content: arrayOfSecond, type: firstType)
dodd.takeIt(content: arrayOfFirst, type: secondType)
It produces nice and predictable output:
Second First
[__lldb_expr_77.Second, __lldb_expr_77.Second, __lldb_expr_77.Second]
First Second
[]
Great.
But if I try tu use exactly same mechanism in a bit more complicated environment results are less satisfying.
This is a function of some ViewController, whatever:
func addSubviews<HeadType>(for content:[HeadType]) {
Swift.print("ADD SUBVIEW FOR \(HeadType.self)")
func takeNext <ParentType, ChildType>(
parentArray: [ParentType],
pathToChild: AnyKeyPath,
type: ChildType.Type?) -> [ChildType]
{
Swift.print ("\nInside takeNext Child Type <\(ChildType.self)>\n")
let result:[ChildType] = parentArray.flatMap { ( parentElement ) -> ChildType? in
parentElement [keyPath:pathToChild] as? ChildType}
return result
}
let interfaceHead = InterfaceElements(type: HeadType.self)
Swift.print ("\tParentArrayContent: \(HeadType.self) ")
for (label, path) in interfaceHead.paths {
if let firstObject = content.first, let objectAtKeyPath = firstObject[ keyPath:path ] {
Swift.print ("\t\tpath: \(path)")
Swift.print ("\t\ttype: \(InterfaceElements(element: objectAtKeyPath).type.self)")
Swift.print("\t\tLabel", label)
let childType = InterfaceElements(element: objectAtKeyPath).type
let elements = takeNext(
parentArray: content,
pathToChild: path,
type: childType)
let controller = NestedStackViewController(for: elements)
}
}
}
Output:
ADD SUBVIEW FOR Joint
ParentArrayContent: Joint
path: Swift.ReferenceWritableKeyPath<HyperGlyph.Joint, Swift.ImplicitlyUnwrappedOptional<HyperGlyph.Position>>
type: Optional(HyperGlyph.Position)
Label position
Inside takeNext Child Type <NSManagedObject>
ADD SUBVIEW FOR NSManagedObject
ParentArrayContent: NSManagedObject
inited NestedStack for NSManagedObject
inited NestedStack for Joint
First loop is nice, Joint is Joint, Position type was found, sent to takeNext, but inside takeNext function Position became NSManagedObject type. Where could be trick?
Only one way to do it was to explicitly pass a type.
let elements = takeNext(
parentArray: content,
pathToChild: path,
type: SomeType.self) // << Here.
It made a program a bit more complicated, more switches, but it works in nice strong-type way now.

Use Measurement for input in NSTextFieldCell

I want to be able to nicely use a Measurement and MeasurementFormatter for output and input with a NSTextFieldCell.
I am able to display the measurement correctly with...
let areaFormatter = MeasurementFormatter()
areaFormatter.unitStyle = .medium
areaFormatter.unitOptions = .providedUnit
let area = Measurement<UnitArea>( value: 123.43, unit: .squareInches)
let editInput = NSTextFieldCell
editInput.objectValue = area
editInput.formatter = areaFormatter
This displays something like
123.43 in^2
The problem starts when I want to read this back in with
var inputArea = editInput.objectValue as! Measurement<UnitArea>
I think because the get Object value of the Measurement Formatter is not defined.
open func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool
Is my understanding correct? Are there any examples in Swift where this function has been defined for measurements?
Right now the user can edit the entire string including the text in the units. Is there a good way to block the units in the NSTextFieldCell? I would like the user to be able to edit the number but not the units and then return the measurement with
var inputArea = editInput.objectValue as! Measurement<UnitArea>
so this gets displayed
123.43 in^2
but only the 123.43 can be edited.

Modifying struct instance using call to name in function parameter

I am attempting to use Parse to call up some variables and put them into a struct that is already initialized. The calling of the variables is happening smoothly and the data is available, but the inputing of the class into the function is not happening.
'unit' is a struct that has the name, hp, attack, etc. variables contained within it.
Is it not possible to pass along an instance of a struct and modify it's values like this? It would save me a lot of copy-pasting code to do it this way.
Thanks for your help!
func fetchStats(name: String, inout nameOfClass: unit) {
var unitStatArray = []
let query = PFQuery(className: "UnitStats")
query.whereKey("name", equalTo: name)
query.findObjectsInBackgroundWithBlock{(objects:[PFObject]?, error: NSError?)->Void in
if (error == nil && objects != nil){ unitStatArray = objects! }
nameOfClass.name = "\(unitStatArray[0].objectForKey("name")!)"
print("class name is \(nameOfClass.name)")
print("cannon name is \(cannon.name)")
nameOfClass.hitPoints = unitStatArray[0].objectForKey("hitPoints") as! Double
nameOfClass.hitPointsMax = unitStatArray[0].objectForKey("hitPointsMax") as! Double
nameOfClass.attack = unitStatArray[0].objectForKey("attack") as! Double
nameOfClass.defense = unitStatArray[0].objectForKey("defense") as! Double
nameOfClass.rangedAttack = unitStatArray[0].objectForKey("rangedAttack") as! Double
nameOfClass.rangedDefense = unitStatArray[0].objectForKey("rangedDefense") as! Double
nameOfClass.cost = unitStatArray[0].objectForKey("cost") as! Int
}
}
fetchStats("3-inch Ordnance Rifle", nameOfClass: &cannon)
This is an attempt to explain what I had in mind when writing my comment above.
Because there's an asynchronous call to findObjectsInBackgroundWithBlock, the inout won't help you here. The idea is to add a callback fetched like this:
func fetchStats(name: String, var nameOfClass: unit, fetched: unit -> ()) {
// your code as above
query.findObjectsInBackgroundWithBlock {
// your code as above plus the following statement:
fetched(nameOfClass)
}
}
This can be called with
fetchStats("3-inch Ordnance Rifle", nameOfClass: cannon) { newNameOfClass in
nameOfClass = newNameOfClass
}
(all of this code has not been tested)
The point is that you understand that your code is asynchronous (I know, I'm repeating myself). After you have called fetchStats you don't know when the callback (here: the assignment nameOfClass = newNameOfClass) will be executed. You cannot assume the assignment has been done after fetchStats has returned.
So whatever you need to do with the changed nameOfClass: the corresponding statements must go into the callback:
fetchStats("3-inch Ordnance Rifle", nameOfClass: cannon) { newNameOfClass in
// do whatever you want with the received newNameOfClass
}
Hope this helps.

How do i return coordinates after forward geocoding?

I am trying to see whether the user is within a certain distance of an address. I have successfully managed to get the users location, and convert the address with forward geocoding. I am left with two sets of coordinates. I am trying to make an if statement saying if they are within "a distance", print something!
Currently when i print the coordinates inside the placemark function i get the desired coordinates. When i call them to create eventLatitude and eventLongitude they become 0.0. I know this is a ascycronous problem, but i am unsure on who to resolve this. Can someone give me an example.
My code is below
before the viewdidload i have these variables
var placemarkLongitude = CLLocationDegrees()
var placemarkLatitude = CLLocationDegrees()
then inside the function i set these variables to the placemark coordinates
if let objects = objects {
for object in objects {
self.geocoder = CLGeocoder()
//get address from object
let COAddress = object.objectForKey("Address")as! String
let COCity = object.objectForKey("City")as! String
let COState = object.objectForKey("State")as! String
let COZipCode = object.objectForKey("ZipCode")as! String
let combinedAddress = "\(COAddress) \(COCity) \(COState) \(COZipCode)" //all parts of address
print(combinedAddress)
//make address a location
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {(placemarks, error) -> Void in
if(error != nil)
{
print("Error", error)
}
else if let placemark = placemarks?[0]
{
let placemark = placemarks![0]
self.placemarkLatitude = (placemark.location?.coordinate.latitude)! //THIS RETURNS A VALUE
self.placemarkLongitude = (placemark.location?.coordinate.longitude)! //THIS RETURNS A VALUE
print("Longitude: ", self.placemarkLongitude, " Latitude: ", self.placemarkLatitude)
}
})
// user location
let userLatitude = self.locationManager.location?.coordinate.latitude //THIS RETURNS A VALUE
let userLongitude = self.locationManager.location?.coordinate.longitude //THIS RETURNS A VALUE
print("User Location is ", userLatitude, ", " ,userLongitude)
let userLocation = CLLocation(latitude: userLatitude!, longitude: userLongitude!)
// event location
let eventLatitude = self.placemarkLatitude // THIS RETURNS 0.0
let eventLongitude = self.placemarkLatitude // THIS RETURNS 0.0
print("Event Location is ", eventLatitude, ", " ,eventLongitude)
let eventLocation = CLLocation(latitude: eventLatitude, longitude: eventLongitude)
//Measuring my distance to my buddy's (in km)
let distance = userLocation.distanceFromLocation(eventLocation) / 1000
//Display the result in km
print("The distance to event is ", distance)
if (distance < 100) {
print("yay")
}
}
}
You are correct about the asynchronous issue. Basically, you cannot do anything after this code:
// [A1]
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {
(placemarks, error) -> Void in
// [B] ... put everything _here_
})
// [A2] ... nothing _here_
The reason is that the stuff inside the curly braces (B) happens later than the stuff outside it (including the stuff afterward, A2). In other words, the code in my schematic above runs in the order A1, A2, B. But you are dependent on what happens inside the curly braces, so you need that dependent code to be inside the curly braces so that it executes in sequence with the results of the geocoding.
Of course this also means that the surrounding function cannot return a result, because it returns before the stuff in curly braces has even happened. The code in my schematic goes A1, A2, return! Only later does B happen. So clearly you cannot return anything that happens in B because it hasn't happened yet.
Just pass the coordinate values obtained from the completionHandler to any other method and do what you like to do.
{
self.placemarkLatitude = (placemark.location?.coordinate.latitude)! //THIS RETURNS A VALUE
self.placemarkLongitude = (placemark.location?.coordinate.longitude)! //THIS RETURNS A VALUE
// After this code pass the values like,
passingTheCoordinates(placemarkLatitude, placemarkLongitude)
}
func passingTheCoordinates(latitude:CLLocationDegrees, _ longitude:CLLocationDegrees){
}
Did not have enough reputation to reply your question but I also have this same problem today. I don't know much about your app design but for my case (which is stuck at the same place like you, same func, same problem, can't save to variable). My solution (maybe kinda temporally, does not good) is to save (placemark.location?.coordinate.latitude)! and (placemark.location?.coordinate.longitude)! to CoreData as Double.
This is how I implemented it. As I said before, since I don't know your app much so depend on your need, you might want something else.
LocationManager.sharedInstance.getReverseGeoCodedLocation(address: searchBar.text!, completionHandler: { (location:CLLocation?, placemark:CLPlacemark?, error:NSError?) in
if error != nil {
print((error?.localizedDescription)!)
return
}
if placemark == nil {
print("Location can't be fetched")
return
}
//Saving geo code to Core Data
newEntry.lat = (placemark?.location?.coordinate.latitude)!
newEntry.long = (placemark?.location?.coordinate.longitude)!
})
Credit to this repo for the LocationManager.swift file