Training and Instructional Design
One of the core elements of clean code is having code that is consistent with identifiers. Head to the App
D
elegate
. Take a look at this code from the tip from last week.
I have a few problems with it. I’m not being very consistent in some of those identifiers such as the HueColorTVC
and ColorTableViewController
. However, these identifiers are all over my code. There’s a great way to get consistency using refactoring.
Select the HueColorTVC
, then right click.
Click refactor… and then rename.
Xcode shows you all occurrences of this identifier, which in this case is only in the app delegate.
Change to hueColorTableViewController
. All the occurrences change at once.
Select and then right-click the ColorTableViewController
, and once again hit refactor.
Both cases of the class identifier, when it is instantiated in teh App delegate and the definition in HueColorTVC.swift are highlighted. Change to HueColorTableViewController
.
Let’s do this one more time. I’m going to change the navigationVC
to masterNavigationVC
.
Now I’d like my file name to match too. One way to change it is in attributes. Select HueColorTVC.swift.
Open the attributes panel. Change the name to HueColorTableViewController.swift.
and the file name changes.
One caution about all this renaming. Comments don’t refactor easily. You’ll see that at the top of the code. Select and refactor that. You can’t. Once you refactor, you might want to also search to find anything you didn’t get in your refactoring. You can replace all these cases by selecting replace, and adding the replacement in the text box.
Refactoring is a very useful and simple tool to get your code a lot more consistent in identifiers.
One of those difficult things for even experienced developers to understand is Automatic Reference Counting, or ARC. It is how Swift manages and conserves memory automatically.
Take a look at the Exercise file. I have a playground in this project with two classes. One is a pizza topping.
class PizzaTopping{ let name:String var onPizza: Pizza! init(name:String){ self.name = name } deinit { print("Deinitializing \(name)") } }
One is a Pizza
.
class Pizza{ let name:String var topping:PizzaTopping! init(name:String){ self.name = name } deinit{ print("Deinitializing \(name)") } }
Look at the PizzaTopping
class. It has two methods defined, an init
to initialize name, and deinint
.
deinit
is a method that executes just before the class is deallocated, and we’ll use it to see things disappear.
On the bottom, you’ll see I made a few toppings and pizzas in a do
block. I use a do block here to simulate what would happen inside a class or function for a playground without using one. I’ll explain why I did that shortly.
do{ // Initialize toppings let pepperoni:PizzaTopping = PizzaTopping(name: "Pepperoni") let mushroom:PizzaTopping = PizzaTopping(name:"Mushroom") // Initialize pizzas let pepperoniPizza:Pizza = Pizza(name: "Pepperoni Pizza") let mushroomPizza:Pizza = Pizza(name:"Mushroom Pizza") // Add toppings to pizzas mushroomPizza.topping = mushroom pepperoniPizza.topping = pepperoni }
If you run now, you’ll see in the console the chain of deinitializing.
Deinitializing Mushroom Pizza Deinitializing Pepperoni Pizza Deinitializing Mushroom Deinitializing Pepperoni
The Pizzas and then the toppings deinitialize. This is ARC in action. Each assignment is a reference to an area in memory. ARC counts the number of references. Where there are no more references, ARC assumes that the memory is unused and frees it up. We’ve got cases here where there is one reference. For example, pepperoniPizza
has one reference. At the end of the do
block, that value is discarded and I have zero references. When ARC finds zero references, it cleans out that memory, deallocating the space, and deinit
fires.
One place that changes is global variables. I used do
to keep all those variables as local. The default behavior in Playgrounds is a global variable. Let’s add a global above of the do
code.
var myPizza:Pizza! = Pizza(name: "Goat Cheese")
Run again.
Deinitializing Mushroom Pizza Deinitializing Pepperoni Pizza Deinitializing Mushroom Deinitializing Pepperoni
Goat Cheese doesn’t show as deallocated. Globals are not deallocated until the app is removed from memory.
Assign the mushroom pizza to my pizza.
myPizza = (mushroomPizza)!
Run again.
Deinitializing Goat Cheese Deinitializing Pepperoni Pizza Deinitializing Pepperoni
The Goat Cheese pizza deinitializes, the mushroom doesn’t, and the mushroom topping doesn’t. We changed the reference. Let’s go down the steps here carefully:
myPizza
PepperoniPizza
and pepperoni
have zero references. ARC deallocates these. myPizza
. mushroomPizza
and the value in its property topping
of mushroom
are assigned there, so they do not deallocate. ARC can be fooled into not deallocating memory through a strong reference cycle. lets suppose I track what pizza has a topping in the Topping class with the property onPizza, for example add
pepperoni.onPizza = pepperoniPizza
Run again
Deinitializing Goat Cheese
,Pepperoni
doesn’t deinitialize. It has two strong references, the Pizza
in onPizza
recording the pizza it is on and the pepperoni
topping in PepperonipPizza
. The topping can’t deallocate until the pizza does and the pizza can’t deallocate until the topping does. When you have a lot of instances of this happening you’ll have an expansion of wasted memory known as a memory leak.
You can break this reference cycle by changing one of the two properties to weak
. I’ll change onPizza
in this case,
weak var onPizza:Pizza!
For a weak variable, Arc changes its value to nil
when it tries to clean up at the end of the block and then cleans it up. That’s why `onPizza` is optional in this code.
The strong reference cycle is broken, and it deallocates the memory of the weak variable. Run now and the pepperoni deallocates.
Deinitializing Goat Cheese Deinitializing Pepperoni Pizza Deinitializing Pepperoni
Of course, the best way to avoid this is to use searches of pizzas for their toppings rather than onPizza
. ARC does much of the work to keep memory clean, but in large codebases, it is too easy to create a memory leak by a strong reference cycle. Knowing how to avoid them and how to use weak
when you can’t are important skills.
Here’s the playground code for this week’s tip. You’ll also find it on GitHub here
// // 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 PizzaTopping{ let name:String weak var onPizza: Pizza! init(name:String){ self.name = name } deinit { print("Deinitializing \(name)") } } class Pizza{ let name:String var topping:PizzaTopping! init(name:String){ self.name = name } deinit{ print("Deinitializing \(name)") } } var myPizza:Pizza = Pizza(name: "Goat Cheese") do{ // Initialize toppings let pepperoni:PizzaTopping = PizzaTopping(name: "Pepperoni") let mushroom:PizzaTopping = PizzaTopping(name:"Mushroom") // Initialize pizzas let pepperoniPizza:Pizza = Pizza(name: "Pepperoni Pizza") let mushroomPizza:Pizza = Pizza(name:"Mushroom Pizza") // Add toppings to pizzas mushroomPizza.topping = mushroom pepperoniPizza.topping = pepperoni myPizza = mushroomPizza pepperoni.onPizza = pepperoniPizza }
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