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. UIAlertAction
s 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.
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 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.
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 } }
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.
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. } }
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.
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))
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.
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.
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]) } }
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 String
s. 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.
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) } }
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.
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 notNSRange
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
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.
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.
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() } }