Understand Closures

You’ll find closures throughout the API’s, but you may not know how to use them properly. Let’s take a basic tour of closures. 

Download the exercise file, and you’ll find a project with a playground. In the playground you’ll find a function  to compute a pizza volume.

func roundPizzaVolume(height:Double, diameter:Double) -> Double{
    let pi = Double.pi
    let z = diameter / 2.0
    let a = height
    let v = pi * z * z * a //My favorite pun.
    return v
}
roundPizzaVolume(height: 2, diameter: 10) 

That’s for a round pan pizza, for a rectangle it won’t work. 

But if I make a function that has a formula for the area, then I can do any area. In the anyPizzaVolume, I’ll put one at the end of the argument list. 

anyPizzaVolume(height:Double, length:Double, width:Double, area:(Double,Double)->Double)-> Double{

That declares a closure, which is not much more than a function that you pass as a parameter.

In my code I’ll multiply height by area, then return the volume.  

func anyPizzaVolume(height:Double, length:Double, width:Double, area:(Double,Double)->Double)-> Double{
    let volume = height * area(length,width)
    return volume
}

I’ll call anyPizzaVolume.In the autocomplete for anyPizzaVolume, you’ll see the closure.

anyPizzaVolume(height: , length: , width: , area:  Double>)

  Add a height of 2, a length of 10 and a width of 10. Hit tab when you get to area, and you’ll get a stubbed closure. I usually use this, but I’m going to do it manually so you get the idea. You stick the closure as the last parameter, so you can put the closure outside the function. Otherwise you need to put it inside, and that can get confusing to read. 

anyPizzaVolume(height: 2, length: 10, width: 10) {
    
}

You start a closure identifying the declared variables, then add the keyword in 

anyPizzaVolume(height: 2, length: 10, width: 10) {
    (l,w) in
}

Now you write code based on those two values. 

anyPizzaVolume(height: 2, length: 10, width: 10) {
    (l,w) in
    let r = l / 2.0
    return r * r * Double.pi
}

Run this and you get the answer you got before.  I can copy this, paste it, and change the code to a rectangle area. 

anyPizzaVolume(height: 2, length: 10, width: 10) {
    (l,w) in
    return l * w
}

Run again, and you get a different answer. There are better ways of doing this method, but I wanted to illustrate the basic use of closures. Where they get used is Asynchronous processing. That’s when you don’t control when the code will execute like completion handlers, timers,  and actions. For example, in the roundPizzaVolume I could add a completion handler that handles errors. 

completionHandler:(Bool,Double)-> Void

For this closure, I’m not returning anything to the function. Closures require a return type, so I useVoid to indicate there is noting returned. I’ll add the completionHandler`function to to the code

completionHandler(v>0,v)

The bool indicates if the area is a meaningful answer, and I pass the volume to the closure. Now I can change the roundPizzaVolume code to use the closure by adding this: 

roundPizzaVolume(height: -2, diameter: 10){
    (success,volume) in
    if !success{
        print("Invalid Volume \(volume)")
    }
}

Change the height to -2 and Run. Many closures, especially file handling, will do this and give you a success bool which you can then handle after it does it function.

Take a look at the ViewController.  You’ll find examples of  API’s using closures.   Action handlers are often closures. For example I have an alert in this code. UIAlertActions use closures to respond to the selection of the action.

let actionOne = UIAlertAction(title: “First Action”, style: .default) { (action) in
self.alertStatus.text = “First Action”
}

 Notice that as asynchronous blocks, you need self to use properties and methods of the class.  Also you can make closures optional, so if you do not want to use them, they can be set to nil, as in this action.

 let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

There’s a lot you can do with closures. As you can see here, you will find them in code frequently.  

The Whole Code

You’ll find the completed code on Github here. Below you’l find the playground and view controller for this project.

import UIKit
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//


func roundPizzaVolume(height:Double, diameter:Double, completionHandler:(Bool,Double)->Void) -> Double{
    let pi = Double.pi
    let z = diameter / 2.0
    let a = height
    let v = pi * z * z * a //My favorite pun.
    completionHandler(v>0,v)
    return v
}
roundPizzaVolume(height: -2, diameter: 10){
    (success,volume) in
    if !success{
        print("Invalid Volume \(volume)")
    }
}

func anyPizzaVolume(height:Double, length:Double, width:Double, area:(Double,Double)->Double)-> Double{
    let volume = height * area(length,width)
    return volume
}

anyPizzaVolume(height: 2, length: 10, width: 10) {
    (l,w) in
    let r = l / 2.0
    return r * r * Double.pi
}

anyPizzaVolume(height: 2, length: 10, width: 10) {
    (l,w) in
    return l * w
}

//
//  ViewController.swift
//  ClosureExercise
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var presentStatus: UILabel!
    @IBOutlet weak var alertStatus: UILabel!
    @IBOutlet weak var alertButton: UIButton!
    @IBAction func alertButton(_ sender: UIButton) {
        let alert = UIAlertController(title: "Demo Alert", message: "Some Closure examples", preferredStyle: .alert)
        //Actions for methods like alerts often use closures to do the action
        let actionOne = UIAlertAction(title: "First Action", style: .default) { (action) in
             //This is on a seperate thread so use self to access the elclosing class' methods and properties.
            self.alertStatus.text = "First Action"
            //this is on a seperate thread so use self to access the elclosing class' methods and properties.
        }
        let actionTwo = UIAlertAction(title: "Second Action", style: .default) { (action) in
            //This is on a separate thread so use self to access the enclosing class' methods and properties.
            self.alertStatus.text = "Second Action"
        }
        // Closures can be optional, and then you can set the value to nil if you don't plan to use it. Make sure you handle the nil in the method though.
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        
        
        alert.addAction(actionOne)
        alert.addAction(actionTwo)
        alert.addAction(cancelAction)
        
        // The present function has a closure we usually leave nil, but if you have clean up​ after you launch the method, this is where you do it. Here I print a status message.
        present(alert, animated: true) {
            self.presentStatus.text = "Presented Alert"
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        alertButton.layer.cornerRadius = 20
        // Do any additional setup after loading the view, typically from a nib.
    }


}

Ducking Sound in AVAudioSession

title shot

Ducking has nothing to do with waterfowl. Sometimes you’ll want to add sound to your app, but the user will be using the music or others app along with your app.  For that, you’ll want to slightly lower the volume of the background music so you can put your sounds in the foreground. That;’s ducking, and Its not very hard to do.  

 Download the exercise file which is Speech synthesizer app from an earlier tip.  I’m running this on my iPad mini , but before I do, I’ll start a bit of music.  Now run the app.  Tap the button on the app, and the music disappears. Stop the app. 

There’s a class called AVAudioSession which controls the audio output. While part of AVFoundation, it has a default setting without AVFoundation which plays only one channel of sound. To combine channels, you have to configure the current AVAudioSession to do so. Usually, you’ll do this once in the AppDelegate. Head over there and add AVFoundation

import AVFoundation

In didFinishLaunchingWithOptions, make an identifier for AVAudioSession’s singleton sharedInstance.

let session = AVAudioSession.sharedInstance()

AVAudioSession has a category property. You set the property with a setCategory method, which throws errors, So set up a do…try…catch

do{
    try
} catch {
    print ("Unable to set audio category")
}

After the try, add the setCategory method. For the category, use a AVAudioSession.Category.playback

try session.setCategory(AVAudioSession.Category.playback 

The second parameter is mode. I’ll set this to default.

try session.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default, 

The with is an array of options. I’ll add two options. The first is a bit redundant, but doesn’t hurt to add. mixWiithOthers assures that the foreground and background channels mix. Alone you’ll have them at the same volume.  The second one duckOthers,  softens the background sound` a bit.

try session.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default, options: [.mixWithOthers,.duckOthers])

Our last step is to activate the session directly under the setCategory. Setting the category supposedly activates it, but in some circumstances, it won’t. If you need it, it’s here, but I’ll comment it out this time.

//try session.setActive(true, options: [])

        Build and run the project.  I’ll start the music again, and then I’ll start the speech synthesizer. The music ducks under the speech synthesizer.

You can use this in a lot of places where you want your sound effects and you’re user might want their own music or media. 

The Whole Code

Here’s the code in the app delegate for ducking. See the GitHub download for the ducking and speech synthesizer.

//
//  AppDelegate.swift
//  SpeechSynthesizer
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit
import AVFoundation
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        let session = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default, options: [.mixWithOthers,.duckOthers])
            //try session.setActive(true, options: [])
        } catch {
            print ("Unable to set audio category")
        }
        return true
    }
}

Actions in Table Views

Sometimes table views could use a few buttons. There’s two delegates which create swipe buttons on table view cells. Let’s learn how you can implement these buttons and an interesting hidden feature you can do with them. 

There is two delegate methods, one for the leading swipe configuration and one for the trailing Swipe configuration. They work identically, so I’ll start with the trailing one. 

In the TableViewController.swift code, under the titleForHeaderInsection delegate method,  add the following which you can find in the auto complete: 

override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
}

}

This method returns  a UISwipeActionsConfiguration, and I’ll add the returned object to my code: 

return UISwipeActionsConfiguration(actions: [greenAction,blueAction])

You’ll see the initializer takes an array of actions of type UIContextualAction. That’s the core of this method.  It sets the title on the cell for your action and handles the code when tapped. I’ll add an action called greenAction

let greenAction = UIContextualAction(style: .normal, title: "Green") { (action, view, actionPerformed) in
         
        }

You’ve got normal and destructive styles for the buttons since you can place code to delete the cell here. I’m just going to leave it .normal then set the title to Green and then add the handler.  

The handler has three parameters: the action itself if you want to change the action, the view in which the action is displayed, and a bool indicating if the action was performed or not.

I made a method for use within the action to change the cell:

//A simple example of what you can do in the cell
    func setCell(color:UIColor, at indexPath: IndexPath){
        
        // I can change external things
        self.view.backgroundColor = color
        // Or more likely change something related to this cell specifically.
        let cell = tableView.cellForRow(at: indexPath )
        cell?.backgroundColor = color
    }

Inside the closure for the action,  I’ll add the setCell method to change the background and cell color. 

let greenAction = UIContextualAction(style: .normal, title: "Green") { (action, view, actionPerformed) in
            self.setCell(color: .green, at: indexPath)
        }

I can change properties of the action as well, including its title and background color. I’ll change the background color to green. 

greenAction.backgroundColor = .green

I’ll copy then paste all this. to make another button, changing all the greens to blues. 

 let blueAction = UIContextualAction(style: .normal, title: "Blue") { (action, view, actionPerformed) in
            self.setCell(color: .blue, at: indexPath)
        }
        blueAction.backgroundColor = .blue

I can take both these actions and place them in the actions array of UISwipeActionsConfiguration

return UISwipeActionsConfiguration(actions: [greenAction,blueAction])

Since the leading version is identical to the trailing version, I’ll copy all of this then  paste it below the first. Change trailing to leading.

override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let greenAction = UIContextualAction(style: .normal, title: "Green") { (action, view, actionPerformed) in
            self.setCell(color: .green, at: indexPath)
        }
        greenAction.backgroundColor = .green
        
        let blueAction = UIContextualAction(style: .normal, title: "Blue") { (action, view, actionPerformed) in
            self.setCell(color: .blue, at: indexPath)
        }
        blueAction.backgroundColor = .blue
        
        return UISwipeActionsConfiguration(actions: [greenAction,blueAction])
    }

Build and run using an iPhone XR simulator. If you swipe a cell slowly about 1/4 to 1/2 across right to left, you’ll get the blue and the green trailing button. 

If you swipe gently on another row left to right, you get the green and blue button. 

The first button is the one closest to the leading or trailing edge. Click the green button and the background turns green,  tap the blue button and the background turns blue. 

Now swipe from right to left across the entire button.

The background goes green. The first action of the UISwipeActionsConfiguration will fire on a big swipe. So If I want blue on the leading swipe and green on the trailing ‘Ill change my leading array to have the blue as th first element of the array: 

return UISwipeActionsConfiguration(actions: [blueAction,greenAction])

Run again, and you get the long swipe of blue from the leading and green from the trailing.

This is a powerful, simple,  and useful way to get actions into individual table view cells.  

The Whole Code

Here’s the code for this lesson. You can also download it from Github Here.

//
//  TableViewController.swift
//  SwipeActions
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit

class TableViewController: UITableViewController {

    let rowCount = 12
    
    override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let greenAction = UIContextualAction(style: .normal, title: "Green") { (action, view, actionPerformed) in
            self.setCell(color: .green, at: indexPath)
        }
        greenAction.backgroundColor = .green
        
        let blueAction = UIContextualAction(style: .normal, title: "Blue") { (action, view, actionPerformed) in
            self.setCell(color: .blue, at: indexPath)
        }
        blueAction.backgroundColor = .blue
        
        return UISwipeActionsConfiguration(actions: [greenAction,blueAction])
    }
    
    override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let greenAction = UIContextualAction(style: .normal, title: "Green") { (action, view, actionPerformed) in
            self.setCell(color: .green, at: indexPath)
        }
        greenAction.backgroundColor = .green
        
        let blueAction = UIContextualAction(style: .normal, title: "Blue") { (action, view, actionPerformed) in
            self.setCell(color: .blue, at: indexPath)
        }
        blueAction.backgroundColor = .blue
        
        return UISwipeActionsConfiguration(actions: [blueAction,greenAction])
    }
    
    //A simple example of what you can do in the cell
    func setCell(color:UIColor, at indexPath: IndexPath){
        
        // I can change external things
        self.view.backgroundColor = color
        // Or more likely change something related to this cell specifically.
        let cell = tableView.cellForRow(at: indexPath )
        cell?.backgroundColor = color
    }
    
    
    //Your standard Table Delegate and Data Source methods
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return rowCount
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = String(format:"Row #%i",indexPath.row + 1)
        cell.textLabel?.font = UIFont(name: "GillSans-Bold", size: 26)
        return cell
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "Sample Action Table"
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
}


Thrown Errors

There’s many ways to handle errors in Swift. For some errors, using throws is a great way to handle errors without crashing the system.

Download the exercise file. You’ll find a project with an embedded playground. 

let coffees = ["Sumatra","Colombia","Dark Energy","Sarabanda Dark","Kona"]
let ratings = [2,2,-1,5]

func coffee(_ name:String) -> String{
    let index = coffees.firstIndex{ (coffee) -> Bool in
        coffee == name
    }
    let ratingIndex  = index!
    let rating = ratings[ratingIndex]
    return String(repeating:"☕️", count: rating)
}

var myCoffee = "Colombia"
print(myCoffee + ":" + coffee(myCoffee))

While there’s a lot better ways to do this, I’ll use an example of a function coffee(name:) that finds the rating of a type of coffee using the two arrays coffees and ratings. You’ll notice there’s a nil error and some index out of range errors possible here in the rating and the search. 

I can run for Sumatra as it is set for and it gives me a two cup rating. Try Kona and it crashes since I have four instead of five ratings, so I have an array out fo bounds error. It also crashes on Java, whihc is not one of my coffees, so index is nil and can’t be force unwrapped.

For internal fatal errors of a function, we can use throws. To use throws, add it to the function name before the return type

func coffee(_ name:String) throws -> String{

You’ll need to identify the error when you throw it. You use an enum that adopts the Error protocol to do that. I’ll add three errors 

enum CoffeeError: Error{


I’ll add three errors. The first case will be a coffee not found

case coffeeNotFound

For the second, I can add an argument, which I can later use to give more information about the error in my code

case ratingNotFound(coffee:String)

    The last will be for an invalid rating, since I will only use ratings between one and five cups.

case badRating
}

Going back to my code, I’ll throw those errors in my method. The first error is an nil index, which I will use a guard for.Inside the guard’s else statement I’ll throw the error and send the enumeration’s value, in this case coffee not found. 

//let ratingIndex  = index!
guard let ratingIndex = index else{
    throw CoffeeError.coffeeNotFound
}

For my other two errors, where the rating index is out of range, I’ll use an if statement and throw the appropriate error. For the rating not found, I’ll.include in the parameter the name of the coffee

if ratingIndex >= ratings.count{
   throw CoffeeError.ratingNotFound(coffee: name)
}

If my rating is a nagative one, I’ll throw an error

if rating < 0 {
     throw CoffeeError.badRating
 }

If you make your own thrown errors, that’s how you would set them up. This is great way to detect errors you’ll get from situation beyond your control.

More likely, you will be handling the error. You might set up your own, but there are a lot of factory methods that throw errors. The standard way of handling thrown errors is the do…try…catch construct. It starts with  a do and a code block

do{
print(myCoffee + ":" + coffee(myCoffee))
}

Inside the code block, you try the thrown method, usually before the method. In a print method, it goes outside the print.

do{
    try print(myCoffee + ":" + coffee(myCoffee))
}

   

Add catch to handle any error.  In its simplest form, you can do this to catch and handle all errors. 

do{
    try print(myCoffee + ":" + coffee(myCoffee))
} catch {
    print("Error")
}

The real power of do…try…catch however is in specifying the error form your specified errors, so you can handle it properly.  I’ll set up a specific error handler for a coffee not found. I’ll place this above the gnereal catch. You can think of catch a lot like switch, where the catch without any errors is the default, and should go last.

catch CoffeeError.coffeeNotFound{
    print("Coffee not found")
}

The ratingNotFound error had a parameter, I can set the error parameter with a let in the parameter to silence a warning from string interpolation.

catch CoffeeError.ratingNotFound(let coffee){
    print ("Coffee \(coffee) does not have a rating" )
}

Try a few different cases of errors.  Set the coffee to Dark Energy, run, and you get a simple error from the -1 rating

Change the name to Dark Matter, which isn’t one of our coffees

Try Kona for the out of bounds error:

If yo dont need to hadle errors differently, an alternative is making the error a nil value using try?  and use an if Let construct. 

if let coffeeRating = try? coffee(myCoffee){
    print (myCoffee + " " + coffeeRating )
} else {
    print("Error on coffee")
}

There’s one other option: Disable the error thrown with try!. Use this only if you know this will work, such as 

myCoffee = "Sumatra"
try! print(myCoffee + ":" + coffee(myCoffee))

The best way to handle an error is not to make one. However, when creating methods and classes, you will find places that there are errors you can’t control. Throwing errors will notify any other classes and methods that there is trouble.

The Whole Code

You can find the completed project below, You can also download it from GitHub.

//
//  A Demo for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//
import UIKit

let coffees = ["Sumatra","Colombia","Dark Energy","Sarabanda Dark","Kona"]
let ratings = [2,2,-1,5]

enum CoffeeError: Error{
    case coffeeNotFound
    case ratingNotFound(coffee:String)
    case badRating
}

func coffee(_ name:String) throws -> String{
    let index = coffees.firstIndex{ (coffee) -> Bool in
        coffee == name
    }
    //let ratingIndex  = index!
    guard let ratingIndex = index else{
        throw CoffeeError.coffeeNotFound
    }
    if ratingIndex >= ratings.count{
        throw CoffeeError.ratingNotFound(coffee: name)
    }
    let rating = ratings[ratingIndex]
    if rating < 0 {
        throw CoffeeError.badRating
    }
    return String(repeating:"☕️", count: rating)
}

var myCoffee = "Sumatra"
do{
    try print(myCoffee + ":" + coffee(myCoffee))
} catch CoffeeError.coffeeNotFound{
    print("Coffee not found")
} catch CoffeeError.ratingNotFound(let coffee){
    print ("Coffee \(coffee) does not have a rating" )
} catch {
    print("Error")
}


if let coffeeRating = try? coffee(myCoffee){
    print (myCoffee + " " + coffeeRating )
} else {
    print("Error on coffee")
}

myCoffee = "Sumatra"
try! print(myCoffee + ":" + coffee(myCoffee))

Swift Strings Are Not C Strings or NSStrings

In many popular programming languages strings are little more than an array of characters, often referred to as C strings since C was one of the first languages to take this approach to strings. As we learned in the last post, with Swift’s use of Unicode characters in extentended grapheme clusters, this gets messed up, and you have a bit more work when working in Swift with the String and NSString Types.

Download the exercise file, and you’ll find a copy of the completed exercise file from the Unicode character tip. Run the app on an iPhone Simulator.

The app is supposed to count the characters in the string. It accurately counts 9 characters, but to do so it counts extended grapheme clusters, so the arrows don’t count in the character count. This makes sense if you are counting full characters, but it runs into a few problems.

Converting between String and NSString is one of those problems. I’ll Add an NSString above the label assignment:

let nsYummy = NSString(string: yummy)


NSString does not have a count but a length, I’ll add that to the label text with a new line to make it easier to read.

print (String(format:"\n %i %i",yummy.count,nsYummy.length))

Run this. The NSString‘s length reads all the Unicode characters separately, we get 12 instead of 9.

I’d expect 11, since I add the two arrows to the number of characters. I’ll come back to where that missing character is.

Neither of these are arrays of Character. You can’t do this:

let yummyChar = yummy[4]

Or this:

let nsYummyChar = nsYummy[4]

You’ll get an error.

That’s to keep track of all those clusters. NSString has a method character:at: which gives you the character as a 16-bit integer. I’ll chnge the assignment to

let nsYummyChar = nsYummy.character(at: 4)

Since I’ve been working in hex I’ll print to the console our label and the character

print(String(format:"%X %C",nsYummyChar, nsYummyChar))

Comment out yummy for now, Run and we get 67, which is the g.

For String, I have to use a relative index from the beginning or end of a string. There’s an internal type to String called String.Index that I can use with a subscript. It has a few properties that are useful. For the index of the first characte r there is the property startIndex. For the last index, endIndex. Remove the comment and change the subscript to

let yummyChar = yummy[yummy.startIndex]

That will get me the first character. For the fourth character, I can use the method index:offsetBy: I’ll just print that character to the console.

let yummyChar = yummy[yummy.index(yummy.startIndex,offsetBy:4)]
print(yummyChar)

Run this. In the console,

We get the h with the arrow because this is an offset from the index. It is the number of characters away from the starting character D. I subtract 1 to get 3 to get the fourth character.

let yummyChar = yummy[yummy.index(yummy.startIndex,offsetBy:3)]
print(yummyChar)

Iterations through characters are different too. For an NSString, you’ll have to iterate through an index and use character: At:

 
for index in 0..<nsYummy.length{
     let nsYummyChar = nsYummy.character(at: index)
    print(String(format:"%C",nsYummyChar) }
 

Swift Strings are sequenceable, so you can use for directly on the string. char here is of type character, and for comparison, I cast it to string for printing.

for char in yummy{
    print(String(char))
}

Run this. In the console you’ll find the results. First there is the NSString iteration, which iterates over all the characters, splitting up the grapheme clusters, and converting the doughnut emoji to question marks.

Then the Swift string returns grapheme clusters, with the doughnut intact.

If you noticed earlier, there were 12 characters in the NSString while ,String had 9. We expected that the arrows were another character for a total of 11. But looking at the console we can see that the emoji, here represented by those two question marks, is two characters. On the other hand, the char just prints 9 grapheme clusters for the Swift String and Character.

When working with strings, and especially when converting between String and NSString, be careful. Due to Unicode clusters, they might not be as simple as they look.

Some Common Functions for String

For most, that’s great theory, but how does it apply to strings, not characters? If you’re familiar with many languages that use strings as c-strings or character arrays, you’re familiar with a few simple string manipulation functions. In BASIC I knew them as right$, left$, and mid$. I’ve created a simple extension to String that will let you use three more common String functions to better understand strings.

First make the extension

extension String{

}

Since index is odd about numbering. I like keeping consistent with arrays, so I’ll write a function to return a valid position. This makes sure we are in range. I also set overflows to startIndex and endIndex. I did this for speed in coding the rest of this. This would be better returning Int! and checking for nil.

private func pos(position:Int)->Int{
    var pos = position
    if pos > 0 {pos -= 1} else {pos = 0}
    if pos >= count {pos = count - 1}
    return pos
}

I’ll need the position of the character I’m interested in. I made another function for that, using the offsetBy we already used. Since this is an extension of String, I use self for the object.

private func index(_ position:Int)->String.Index{
    return self.index(startIndex, offsetBy: pos(position:position))
}

All the characters to the left of the position I make an open range based on position to get a left string function.

func leftString(from position:Int)-> String{
     return  self[...index(position)]
}

In the extension, I use self to refer to my string. I get a substring on self by using a range for the subscripts on indices.

You’ll notice we get a weird error.

Substrings are not strings. Cast it to a string and the error disappears. .

return String(self[...index(position)])

For midString, a string starting at one character position and going for a length, I use a closed range of a start and end position. I calculate the end by adding the length, but still have to take 1 off.

func midString(from position:Int, length:Int)-> String{
    let endPosition = position + length - 1
    return String(self[index(position)...index(endPosition)])
}

Getting the rightmost characters is a little bit more difficult. rightIndex has to be from the trailing side. So I’ll find the position by subtracting position from endIndex

func rightString(from position:Int)-> String{
    let rightIndex = self.index(endIndex, offsetBy: -pos(position:position + 1))

}

Now to test all this, I’ll add print statements to viewDidLoad in the class I’ve been working in.

print(yummy)
print(yummy.leftString(from: 3))
print(yummy.midString(from: 3, length: 3))
print(yummy.rightString(from: 3))

Run this and you get in the console:

I printed the full string first for comparison. The third from the left character is u. For the leftString, I print the first, second and third characters. For midString I print the third character and the next two characters for a total of three characters. The u happens to be the third from the right character too, so the rightString prints from the second u to the beginning of the string.

You can tweak this to your preferences. I just wanted to show you how to manipulate the string using indices. These three functions I added in the extension are not in Swift because there are a lot more powerful things you can do with Swift strings. I’ll be covering those in upcoming tips.

The Whole Code

You’ll find the code below for cut and paste. You can also find it on GitHub here, but in a slightly differnt format. The extension is in a playground file.

//
//  A Demo for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//🍩

import UIKit

class ViewController: UIViewController {
    var yummy = "D\u{1f369}ugh\u{20d7}n\u{20ed}uts"
    
    @IBOutlet weak var label: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //yummy = "\u{1f369}"
        //yummy = "Bun\u{0303}elos"
        let nsYummy = NSString(string: yummy)
        
        let yummyChar = yummy[yummy.index(yummy.startIndex,offsetBy: 3)]
        print(yummyChar)
        
        let nsYummyChar = nsYummy.character(at: 4)
        
        for index in 0..Int{
        var pos = position
        if pos > 0 {pos -= 1} else {pos = 0}
        if pos >= count {pos = count - 1}
        return pos
    }
    
    private func index(_ position:Int)->String.Index{
        return self.index(startIndex, offsetBy: pos(position:position))
    }
    
    func leftString(from position:Int)-> String{
        return String(self[...index(position)])
    }
    func midString(from position:Int, length:Int)-> String{
        let endPosition = position + length - 1
        return String(self[index(position)...index(endPosition)])
    }
    func rightString(from position:Int)-> String{
        let rightIndex = self.index(endIndex, offsetBy: -pos(position:position + 1))
        return String(self[...rightIndex])
    }
}


Unicode Characters in Strings

Special characters like emoji, accents, and symbols in your strings are easier to get than you think. This week, we’ll talk about how using Unicode characters in Swift Strings. Open the exercise file and you’ll find a project which we’ll use for this. I just hooked up a label to make a big display of what I want to show.

Hit Control-Command-Spacebar to get the character viewer. If you get a compact one like this, click the upper right corner icon to expand it.

When expanded, Click the settings gear in the corner . Select customize list.


There a big list of items. 

You can select languages, I’ll select the Enclosed Characters for example.

Go down to the bottom and open Code tables then add Unicode. Click Done.

Select Unicode in the character viewer.

The Unicode set gives you the power to add a lot to your strings. This table lists all the Unicode symbols, and activates Unicode labeling of symbols in the character viewer. In the search bar of the character viewer, find a doughnut. You see a Unicode identifier after it.

You can use these Unicode characters in your app. You’ll see the doughnut is U+1f369.

I can add the doughnut to my code as an escape sequence in the string of  \u{} 

yummy = "\u{1f369}"

run my app, And I get a doughnut. 

If I need an accented character, I can use a Unicode character. In the search window, I’ll search for n. In the related characters, I’ll find an ñ.

Click that

It has a unicode of U+00f1, so I can do this:

var yummy = "Bu\u{00f1}elos"

That’s not very flexible for multiple accent combinations. Instead of using the single character, you can use extended grapheme clusters, which combines two characters. 

Head to the Unicode section of the Character viewer. Find the n. which has a value of U+006e

There a special set for combining characters with diacritical marks starting at U+0300.

The combining tilde is at U+0303. I can change my string to this:

yummy = "Bu\u{006e}\u{0303}elos"

Run this.

The combining Unicode doesn’t need the Unicode base by the way, You can do this with an n too:

yummy = "Bun\u{0303}elos"

Also gets

Diacritical marks can be anywhere in the character space. I’ll head over to the Combining Diacritical Marks for Symbols.Click a few and you’ll see in the preview their location referenced by some guidelines.

Some are above, some below and some in the middle.  Comment out the yummy assignments  we have. I’ll demonstrate the positions with this string:

var yummy = "D\u{1f369}ugh\u{20d7}n\u{20ed}uts"

Run this and see all the fun. .  

There’s one thing you have to be careful of here. Extended grapheme clusters are counted as a single character in a character count. Change the code to 

label.text = yummy + String(format:" %i",yummy.count)

and run

Yields 9, ignoring the arrows. This makes sense if you are counting full characters, but it runs into problems for memory allocation. If you are bridging between NSString and Swift’s String type, the allocation is different due to the extended grapheme clusters. This is why Swift strings aren’t true arrays with an integer index like C-strings. The clusters make counts impossible. I can’t do this to get the third character.

let char = yummy[3] 

I’ll get an error

I can’t just use an integer subscript. Next week, I’ll show you how to handle character access and manipulation in strings. 

The Whole Code

Here’s teh code for this project. You can also download it here from GitHub

//
//  A Demo for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//🍩

import UIKit

// an extra not found in the video:
// it doesn't hurt to make the characters constants or enums for more readable code.
    let upperArrow = "\u{20d7}"
    let lowerArrow = "\u{20ed}"
    let doughnut = "\u{1f369}"

class ViewController: UIViewController {
    var yummy = "D\u{1f369}ugh\u{20d7}n\u{20ed}uts"
    
    @IBOutlet weak var label: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        yummy = "\u{1f369}"
        yummy = "Bun\u{0303}elos"
        yummy = "D" + doughnut + "ugh" + upperArrow + "n" + lowerArrow + "uts"
        label.text = yummy + String(format:" %i",yummy.count)
    }

}



Ranges in Swift

You’ve probably used ranges in loops, without knowing it, but ranges are really a type in Swift. Have you ever thought about ranges and all their power? I’ll show you a few things you might want to know about Swift ranges. I’ve put a playground into a Project for an exercise file. I added there an array to play with

Here’s the one place I’m sure you’ve seen a range: in a for loop. Commonly you’ll see them as a closed range using the ... operator, which include all values in the range. This code

Adds 0+1+2+3+4+5 to get 15

A half-open range takes one less than the number, by changing the last dot for a less than symbol(..<). This half-open example goes from 0 to 4. Which gives us a total of 10.

Open and close ranges are their own type Range, not to be confused with NSRange. I can assign a range like any other type. Note here the upper bound is outside the range. .

Then use that value as the range in my for loop.

You can also use a range directly in an array’s subscript. I can make a subarray of elements like this.

which makes a small array of the second, third, and fourth elements of the array.

You can use an half open range here too.   

You can stick a count for the array in the range, but there is a faster way. You can use fully open range. Open ranges don’t specify one bound of the range. For example to the end of the array like this: 

Or the beginning to iterate from the beginning to an end. 

You also can test for membership with the contains property. To see if my range contains 5 I’d use

Since I set range= 0..<5 this returns false as 5 is outside the range. If I look for 2

that returns true because 2 is between 1 and 4

the contains method become handy because you can use it to test if an index is within a range. For a simple example, I’ll write a snippet of code to check if an index is in a given array. I’ll check for element 4 and get Ice cream.

I check for 42, I’ll get nothing

I’ve stuck to Integers here, but ranges can be other types as well. Four example, you can add a range of Double, then check if a valur exists inthat range quickly.


One important caution I mentioned earlier: Unlike other Swift types, Range and NSRange is not the same thing.

NSRange is a location and a length , Range is upper and lower bounds. Closed ranges might be a bit easier to convert between the two, but open ranges make it very difficult to convert from one to another. Some API use Range and some NSRange. Make sure you know which one you are using.  

The Whole Code

You can also download this from Github here: If you copy and paste this into a Swift playground in iPad, the copy below will format itself.

import UIKit
//:# Range Demo
//
//:  A Demo for iOS Development Tips Weekly
//:  by Steven Lipton (C)2018, All rights reserved
//:  For videos go to http://bit.ly/TipsLinkedInLearning
//:  For code go to http://bit.ly/AppPieGithub
//:

//: A swift playground formatted for iPad playgrounds

let myDesserts = ["Cannoli","Mochi","Bunelos","Pecan Pie","Ice Cream"]

//:## Basic Ranges
//: Closed range
var total = 0
for number in 0...5{
    total += number
}
total


//: Half Open range
total = 0
for number in 0..<5{
    total += number
}
total


var doubleRange = 0.1...6.7
doubleRange.contains(Double.pi)




//: Assigning ranges
var range = 0..<5
//:Range is not NSRange
var nsRange = NSRange(location: 0, length: 4)






total = 0
for number in range{
    total += number
}
total




//: Use as array subscripts
var eating  = myDesserts[2...4]
eating = myDesserts[2..<4]
eating = myDesserts[2...]
eating = myDesserts[...3]
//: Membership
range.contains(5)
range.contains(2)


let a = 42
range = 0..<myDesserts.count
var eat = ""
if range.contains(a){
    eat = myDesserts[a]
} else {
    eat = "nothing"
}
eat


Replace Segmented Controls with Button Arrays

You’ve probably used the Segmented control before like this one

It’s great for some simple uses but lacks flexibility. Besides using only text or single color icon, it doesn’t work in vertical or other arrangements. Let’s look at another solution: Using Button arrays. 

In the exercise files, I’ve set up for you a vertical stack view of buttons, though I could have placed these anywhere with Autolayout.

Open up the assistant editor. I’ve hooked up everything for you but the buttons.  Control-drag from the pizza button to the assistant editor.

While you could use the stack view’s arrangedSubviews array, a more flexible option is a collection of outlets with the outlet collection selection. Instaed of picking an outlet for the connection, select Outlet Collection

I’ll name the collection dessertSelection.

I’ll manually control drag the other buttons to this collection to keep the order correct.  

Control-drag again from the story board.  Make a action didSelectDessert with a sender of UIButton. Drag from the connector to the button so all buttons use the same action. 

Close up the assistant editor and head to ViewController. Each button now will have two states: selected and deselected. We’ll need to deselect everything, and then select the one the user tapped. 

@IBAction func didSelectDessert(_ sender: UIButton) {
     allDeselected()
     selectDessert(button: sender)
}

The power of that array shows up when deselecting the button. You can iterate through the buttons to shut them all off, which in this case is adding a drop shadow I made for you in the shadowOn method: 

func allDeselected(){
        for button in dessertSelection{
           shadowOn(button: button)
        }
    }

Selecting a dessert is doing the opposite to the selected button:

func selectDessert(button:UIButton){
        shadowOff(button: button)    
 }

This gives the selected button a pressed-down look. 

To finish the display, add an allDeselected to viewDidLoad to start fresh. 

override func viewDidLoad() {
        super.viewDidLoad()
        allDeselected()
    }

Run this, which I’ll do on a iPhone XR and you’ll  get buttons that select. For example select mochi

then select Napolean. Mochi pops back up and Napolean depresses.

Of course, you want it to select something.  To do that, make a selectedIndex function.  It will look for a selected condition in the array, which I’ll use the shadow radius, then return the index. 

func selectedIndex()->Int?{
        return dessertSelection.index(where: {(button)-> Bool in
               button.layer.shadowRadius == 1.0})
    }

Back in didSelectDessert, I’ll change the label’s text.  I’ll start by unwrapping the button’s title and the selected index. 

if let selection = selectedIndex() , let text = sender.titleLabel?.text {

}

I’ll set the text, adding 1 since users don’t think to start at zero.  

selectionLabel.text = "#\(selection + 1) " + text

 And finish with an else in case our index or title are missing. 

} else {
            selectionLabel.text = "No Selection"
        }

Run that, and try the buttons.

They now give the selection and the title of the button. This was a simple version of what you can do with an array of buttons. Unlike a segmented control you can arrange and format  them like buttons, which leads to a lot of flexibility in choice controls. 
 

Use Test Flight

One of the really cool features of the App development process is getting real users to beta test your app. iOS  has a great way to do this with the test flight app. This week, I’ll summarize the steps to set up Test Flight for your app to beta test. I’ll be using my own app MiFitnessTrails in Beta, so there’s no exercise files this time. 

Your first step is to make an App ID. You’ll need to have a developer license for this. Go to your accounts at developer.apple.com/account and select the Certificates, ID’s and profiles button,

Then click App ID’s You see your current App ID’s.

I tend to use a specific App ID for projects I’m producing, and you can make one by pressing the Plus at top.

Register the App Fill in the information requested, using the bundle identifier for your app, your Developer ID, and the services you need. I’m keeping to the default services for now.  That will set up a record like this:

Your second step is to set the app up in App Store Connect as you would an app you are publishing.

Go to My Apps and hit the plus button.

Select New App in the drop down that appears

You get the App registration screen.

Once completed and you press Create, Apple will process your request and set up an entry in App Store Connect. You’ll go into that entry. The first screen is for preparing for App Store permissions, and Its a good idea to fill out as much as you can here for final publishing.


You’ll see a tab for TestFlight. Click that.  On the sidebar, click Test information. Fill out a description, an email for responses from your testers and a Marketing URL. While marketing documents can be here, if you are still working on user documentation, this is the URL your testers can get them from. Also include a privacy policy URL if you are collecting or sharing information. 

App review works differently for Test Flight than for the App store. You are only reviewed once for each test group or version, with your initial build submission. Place your review information here. After your first build is reviewed , you can deploy new builds directly, without approval. 

Your next steps are in Xcode.  Under Targets, make sure you have all your versioning and build info correct for submission. Builds must be unique each time.  I need to change my build number to 14.

Set your simulator to Generic iOS Device. You need to archive the project and you can only do that wiht this setting or connected to a live device. Select Product>Archive and archive the app.  Go to the Archives, which might show up for you after archives or Select Window>Organzer(Option-Shift-Command-O). Hit the Archives tab at top.

Select the top archive for build 14. You can validate or distribute the app. I tend to use Validate first, so I can catch any errors I made before submitting. In actual workflows, it saves a lot of time, doing all the steps of a Distribute locally so you can correct your mistakes a lot faster. I know this one works, so I’ll click Distribute

Leave all the defaults of iOS AppStore and Upload, then I leave all these defaults checked

In the next question. I automatically manage signing. Hit next one last time. Wait as the archinve is processed. I’ll get a summary screen when done. When Processed, I’ll upload it.

This will take some time, as much as ten minutes to an hour depending on the size of your files, so again do some waiting.  

While we are waiting, head back to App Store Connect. I’ll set up testers. You’ll see on the side a button New Group

If you Click that. It show another form for creating the group. I already set up a MakeAppPie list.   Since creating a new group would mean a two-day review, I’ll skip creating a new one and show you the created one. There are two tabs, a Testers and a Build tab.

In the Testers tab, you list your testers. All testers need a first name, last name and e-mail address. You can manually add testers, import a file or use the Public Link to get users to join. For example, If you wanted to join this beta test, you can go tohttps://testflight.apple.com/join/99XfjB8r . It would add you to this test group.

You’ll need a build for this, and you can and that in the next tab.  Before we do, Let’s check on the build.  go back to Xcode and You’ll find the upload was sucessful.

Press Done to dismiss the window. Head back to iTunes connect and in the sidebar select iOS Builds

You’ll see the new build is there. If not, refresh your browser.

In the MiFitness Trails App, you’ll see the build 14 on top of several other builds. Builds have expiration dates which are 90 days from the submission date.

On build 14, there are more questions about encoding. Click the trangle and you’ll get a link.  

Then You’ll get a question about changes to encoding. Answer the yes/no question and hit Start Internal Testing. Internal testers are people who have priviledges on your Apple developer account. If you have notifications on for Test flight on your phone, You’ll probably get a notification like I did on my watch:

Back at the builds tab in your list, click the add button

You’ll get a list of the available builds.

Select the build you want. Hit Next. You’ll get another question about testing information. If your app goes through App review before test flight distribution, and there are user sign-ons and passwords in the app, Apple would like access to a test account to make sure everything works correctly.

I don’t have sign-on information so I’ll leave that unchecked. At the bottom, I’ll make sure to inform my users by checking the Automatically notify testers button. Hit next. You’ll get the testing information form.

Add the improvements you’ve made to the app and more instructions to the beta testers for what you are looking for. The hit the Submit for review button.

If this is your first upload, you added a group,  or you changed versions, you’ll go through App review and the status of the build will change to Submitted for review. This has taken about one or two days in my experience. Once reviewed and accepted, or if you only changed the build number, this will be ready The build’s status changes to to Testing.

Your beta testers will get a notification of the new build:

When the beta testers go to the test flight App on their device, they can download and install the new version:

That’s the basics of Test Flight. This, of course, can change as Apple changes processes. Without long wait times for approval, with Test Flight you can get an app into a limited number of devices for real-life testing and gain a lot more insight into the effectiveness and improvements needed in your app. Having a large set of beta testers is great for marketing as well. You can start to find core customers for when the app goes live in the App Store.

Using Haptics

If you have an iPhone 7 or 8, you might think the home button is a physical button. If you completely shut off the phone you find it is a solid circle. What makes that click is a haptic. You can easily use haptics in your application to make buttons click or warn the user.  

I’d suggest downloading the exercise file. Haptics only work on a live phone. Take a look at the starter file. I have a simple button.

Right-click the document outline and you’ll see I made a Touch Up Inside and touch down event action for the button. 

Go to the ViewController.swift code. 

There are three types of haptics. The impact generator can be used for a collision of objects on the screen or as I use it a touch of a finger to a button for a click.  At the top of the code add this: 

let impactGenerator = UIImpactFeedbackGenerator(style: .medium)

The style gives one of three options of .light, .medium, or .heavy impact. I’ll use .medium. Impact generators need a small bit of time to get ready, so when I push down on the button, I’ll add the prepare method.

impactGenerator.prepare() //need enough time for this to get ready

Then I can use the impact occurred method to give the illusion I’m clicking down on something. 

impactGenerator.impactOccurred()
impactGenerator.impactOccurred()

When I lift up my finger I don’t feel anything, but to get the click that I’m releasing the button, I can add the impact occurred again. 

impactGenerator.impactOccurred()

To run and experience this code you’ll need a real phone. Simulators don’t do haptics, and neither do websites.  I suggest trying this yourself since this as a physical sensation or check out the video of this tip Run this and tap the switch. You get a feel like you are pushing down a button and then releasing it. 

Stop the app. At the top of the code, add a notification feedback generator. These give more complicated haptics to notify a user of an event. 

let notificationGenerator = UINotificationFeedbackGenerator()

Comment out the impactOccured in clickMeUp .

Add in clickMeUp 

notificationFeedbackGenerator.notificationOccurred(.success)

You have a few options for the type of notification:error success and warning. I’ll use a success. 

 Then run the app again. Press down and you’ll feel the impact click. Release and you’ll get a double tap for success. 

Try .error, run again.  You get a different pattern.

The third type of haptic is a selection feedback generator. It is so subtle it’s hard to demonstrate. It’s the haptic you get when spinning a picker view. 

Haptic can be used for gaming, and for physical feedback, such as alerting users not looking at their phone. Try to use them only when they are necessary, overuse confuses the user. Also, tie them to visual feedback whenever possible. With the proper uses, haptics can add to your applications a true feel. 

The Whole Code

Here’s the code for this project. You can find a download of this at GitHub here. You can also watch this lesson at LinkedIn Learning

//
//  ViewController.swift
//  Haptics demo
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit

class ViewController: UIViewController {
    let impactGenerator = UIImpactFeedbackGenerator(style: .medium)
    let notificationGenerator = UINotificationFeedbackGenerator()
    @IBAction func clickMeDown(_ sender: UIButton) {
        impactGenerator.prepare()
        impactGenerator.impactOccurred()
    }
    
   
    @IBAction func clickMeUp(_ sender: UIButton) {
        //impactGenerator.impactOccurred()
        notificationGenerator.notificationOccurred(.error)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }


}