Tuesday, May 31, 2016

Basic TableView With Sections Template

Hook up the outlet and change the class name if needed.

Swift 3

 
class ViewController: UIViewController, UITableViewDataSource, 
        UITableViewDelegate {
    
    @IBOutlet weak var tableView: UITableView!
    let sections: [String] = ["Section 1", "Section 2", "Section 3"]
    let s1Data: [String] = ["Row 1", "Row 2", "Row 3"]
    let s2Data: [String] = ["Row 1", "Row 2", "Row 3"]
    let s3Data: [String] = ["Row 1", "Row 2", "Row 3"]
    
    var sectionData: [Int: [String]] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        sectionData = [0:s1Data, 1:s2Data, 2:s3Data]
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) 
            -> Int {
        return (sectionData[section]?.count)!
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) 
            -> String? {
        return sections[section]
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) 
            -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "cell")
        
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "cell");
        }
        
        cell!.textLabel?.text = sectionData[indexPath.section]![indexPath.row]
        
        return cell!
    }
}
 

Swift 2

 
class SectionExampleVC: UIViewController, UITableViewDataSource, 
        UITableViewDelegate {
    
    @IBOutlet weak var tableView: UITableView!
    let sections: [String] = ["Section 1", "Section 2", "Section 3"]
    let s1Data: [String] = ["Row 1", "Row 2", "Row 3"]
    let s2Data: [String] = ["Row 1", "Row 2", "Row 3"]
    let s3Data: [String] = ["Row 1", "Row 2", "Row 3"]
    
    var sectionData: [Int: [String]] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        sectionData = [0:s1Data, 1:s2Data, 2:s3Data]
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) 
            -> Int {
        return (sectionData[section]?.count)!
    }
    
    func tableView(tableView: UITableView, titleForHeaderInSection section: Int) 
            -> String? {
        return sections[section]
    }
    
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return sections.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) 
            -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("cell")
        
        if cell == nil {
            cell = UITableViewCell(style: .Default, reuseIdentifier: "cell");
        }
        
        cell!.textLabel?.text = sectionData[indexPath.section]![indexPath.row]
        
        return cell!
    }
}
 

(Swift 2.2)

Monday, May 30, 2016

Basic TableView Template

Hook up the outlet and change the class name if needed.

Swift 2

class ViewController: UIViewController, UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    
    var tableData: [String] = ["Row 1", "Row 2", "Row 3"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) 
                                                                 -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("cell")
        
        if cell == nil {
            cell = UITableViewCell(style: .Default, reuseIdentifier: "cell");
        }
        cell!.textLabel?.text = tableData[indexPath.row]
        return cell!
    }
}

Swift 3

class ViewController: UIViewController, UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    
    var tableData: [String] = ["Row 1", "Row 2", "Row 3"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
            var cell = tableView.dequeueReusableCell(withIdentifier: "cell")
            
            if cell == nil {
                cell = UITableViewCell(style: .default, reuseIdentifier: "cell");
            }
            cell!.textLabel?.text = tableData[indexPath.row]
            return cell!
    }
}

Completion Handler on Background Thread

Example

 
// Function with a completion handler parameter
func doWork(completion: @escaping (String) -> () ) {
    DispatchQueue.global(qos: .userInitiated).async {
        completion("Work is all done!")
    }
}

// Function that calls your completion handler function
func startWork() {
    print("Starting work")
    
    doWork { (result) -> () in
        print(result)
    }
    
    print("Waiting for doWork to finish.")
}
 

Console printout

Starting work
Waiting for doWork to finish.
Work is all done!

Quality of Service Parameter Enum

  • userInteractive - Need it done almost instantaneously. Highest priority.
  • userInitiated - Need it done in a few seconds or less.
  • utility - Can wait a few seconds to a few minutes.
  • background - Work will take significant time. Like minutes or hours. Lowest priority.

Notes

Closures all follow the same format:
() -> ()
For example, you see this format here:
doWork { (result) -> () in
But there are a few more ways you can write this:
doWork { (result) -> Void in
doWork { (result) in
doWork { (result: String) in
doWork() { result in
doWork { result in
These all work the same. The last one is probably the bare minimum you can get away with.

(Swift 3)

Sunday, May 29, 2016

Get a Random Number

 
print(arc4random()) // Random number up to 4,294,967,296
print(arc4random_uniform(10)) // Random number from 0 to 9
print(drand48()) // Random double between 0.0 and 1.0. Example: 0.396464773760275
 

(Swift 2.2)

Saturday, May 28, 2016

Parsing JSON

When parsing you will be turning the results into a dictionary of objects.
To see how to get the JSON see this post.

Example JSON
{
    "name": "Luke Skywalker", 
    "height": "172", 
    "mass": "77", 
    "hair_color": "blond", 
    "skin_color": "fair", 
    "eye_color": "blue", 
    "birth_year": "19BBY", 
    "gender": "male", 
    "homeworld": "http://swapi.co/api/planets/1/", 
    "films": [
        "http://swapi.co/api/films/6/", 
        "http://swapi.co/api/films/3/", 
        "http://swapi.co/api/films/2/", 
        "http://swapi.co/api/films/1/", 
        "http://swapi.co/api/films/7/"
    ], 
    "species": [
        "http://swapi.co/api/species/1/"
    ], 
    "vehicles": [
        "http://swapi.co/api/vehicles/14/", 
        "http://swapi.co/api/vehicles/30/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/12/", 
        "http://swapi.co/api/starships/22/"
    ], 
    "created": "2014-12-09T13:50:51.644000Z", 
    "edited": "2014-12-20T21:17:56.891000Z", 
    "url": "http://swapi.co/api/people/1/"
}
Then you convert it into a dictionary:
let dict = json as! Dictionary<String, AnyObject>
So you are really dividing up the JSON like this:
Key Value
String AnyObject
{
    "name":   "Luke Skywalker",  String
    "height":   "172",  String
    "mass":   "77",  String
    "hair_color":   "blond",  String
    "skin_color":   "fair",  String
    "eye_color":   "blue",  String
    "birth_year":   "19BBY",  String
    "gender":  "male",  String
    "homeworld":  "http://swapi.co/api/planets/1/",  String
    "films": [ Array<String>
          "http://swapi.co/api/films/6/", 
          "http://swapi.co/api/films/3/", 
          "http://swapi.co/api/films/2/", 
          "http://swapi.co/api/films/1/", 
          "http://swapi.co/api/films/7/"
  ], 
    "species": [ Array<String>
          "http://swapi.co/api/species/1/"
  ], 
    "vehicles":  [ Array<String>
          "http://swapi.co/api/vehicles/14/", 
          "http://swapi.co/api/vehicles/30/"
  ], 
    "starships": [ Array<String>
          "http://swapi.co/api/starships/12/", 
          "http://swapi.co/api/starships/22/"
  ], 
    "created":  "2014-12-09T13:50:51.644000Z",  String
    "edited":  "2014-12-20T21:17:56.891000Z",  String
    "url":  "http://swapi.co/api/people/1/" String
}

To get data out of this dictionary you do something like this:
 
let dict = json as! Dictionary<String, AnyObject>
let name = dict["name"]
let height = dict["height"]

let films: Array = dict["films"] as! Array<String>
let film1 = films[0]
 

(Swift 2.2)

Getting JSON from a Web API

let session = NSURLSession.sharedSession()
let url = NSURL(string: "http://swapi.co/api/people/1/")!

session.dataTaskWithURL(url) {(data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in

    guard let theResponse = response as? NSHTTPURLResponse 
        where theResponse.statusCode == 200 else {
        print("Error in response.")
        return
    }
    
    if let responseData = data {
        do {
            let json = try NSJSONSerialization.JSONObjectWithData(responseData, 
                                   options: NSJSONReadingOptions.AllowFragments)
            
            print(json)
        
        } catch {
            print("Could not serialize into json")
        }
    }
}.resume()

Very important, don't forget the .resume() at the end of the closure!

Now if you want to parse this JSON you can see this post on how to do it.
(Swift 2.2)

Friday, May 27, 2016

Simple Animations

UIView.animate(withDuration: 1) { 
    self.view.backgroundColor = UIColor.darkGray
}

And when you need a delay:
UIView.animate(withDuration: 4, delay: 5, options: [], animations: {
    self.view.backgroundColor = UIColor.darkGray
}, completion: nil)

What can be animated?

Here are some common properties (but not all):
  • UIView.alpha - Use this to fade objects in or out. (Adjusts transparency)
  • UIView.bounds - Defines the X,Y position of self (0,0) and the width, height of the object. Use to change the object's size or even position.
  • UIView.center - Use to change the position of the object on the screen.
  • UIView.frame - Defines the X,Y position inside its parent and the width, height of the object. Reposition or resize within a parent view.
  • UIView.transform - Use this to rotate, scale, translate and skew objects.
  • UIColor - Such as text color or background color. You can animate changing color such as in the example above.
  • view.layoutIfNeeded() - Make changes to an object's constants and then call this function inside the animations block to see those changes animate.

(Swift 3)

UIStackView - A Visual Guide To Settings

This post visually shows the stack view's Alignment and Distribution property settings. There is also a section on resolving auto layout warnings caused by stack views.

Horizontal StackView Alignment Property

Top

Center

Bottom

Fill


Vertical StackView Alignment Property

Leading

Center

Trailing

Fill


Horizontal StackView Distribution Property

I gave the label and buttons background colors so you can see how they stretch.

Fill

Fill Equally

Fill Proportionally

Equal Spacing

Equal Centering


Vertical Distribution Property


Resolving Layout Warnings

If you put these three controls into a horizontal UIStackView you are going to see 2 errors. The default Alignment and Distribution is set to "Fill".
There are no constraints on the stack view yet so let's add some and have this stack view on the top of our view.
OK, now that the stack view has a vertical position (top space constraint of 8) the "Vertical position is ambiguous..." warnings.

Now we have some new warnings.
Your first thoughts might be to want to add constraints to the controls. And for that second warning, you might think to just do an "Update Frames" on it. But after you try you realize this does not work.
Here are some options:
  • Change the Distribution to "Fill Proportionally"
  • Change the Distribution to "Equal Spacing"
  • Change the Distribution to "Equal Centering"
If you do not like those options and want to use "Fill" or "Fill Equally" then you have to click one of the controls and lower its horizontal Content Hugging Priority.

Content Hugging Priority?

I had to study up on this too because I had no idea what it meant. So I will attempt to explain it to you as simply as possible. Let's break down what this means:
  • Content - This is what is in the control. For a label the content is the text. For an image view the content the image. For a button the content is the button text. For a text field I think the content is considered the text in it or even the placeholder text. I think you get the idea.
  • Hugging - It is how close the outside of the control is to the content. The outside of the control is "hugging" its content.
  • Priority - When your UI is rendered which control should keep hugging its content and which one should not? The priority informs auto layout which to render first. The last one is the one whose hugging will change. The lower number is the last to get rendered. Lowest priority.
Examples:
Now that you are an expert on hugging, take another look at that stack view and ask yourself, "Which control's hugging do I want to change (get bigger or smaller) when the UI changes sizes? Let's see what happens if you lower the priority for each control individually.

Label with lower Content Hugging Priority

TextField with lower Content Hugging Priority

Button with lower Content Hugging Priority

So you are basically giving priority to which control will be expanding when the UI size changes. We only looked at horizontal content hugging but I think you can understand how vertical content hugging works.

I hope this helped you understand stack views and content hugging a little more!

For more info, this article really helped me understand content hugging.
(Xcode 7.3)

Wednesday, May 25, 2016

Add 3D Touch to App Icon

Note: As of this writing the Simulator does not support 3D Touch.

Step 1: Define Shortcut Menu Items

Do this in the Info.plist

I defined the icon type (UIApplicationShortcutItemTypeShare) to show the share icon. There are 29 different icons you can choose from as of this writing. Find them here.

Step 2: Handle Action for Shortcut Item

func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
    if let tabVC = self.window?.rootViewController as? UITabBarController {
        if shortcutItem.type == "Search" {
            tabVC.selectedIndex = 0
        } else if shortcutItem.type == "Add New" {
            tabVC.selectedIndex = 1
        }
    }
}

Want to create a dynamic shortcut?

Maybe you want to show/not show shortcuts or show shortcuts with dynamic info like a count of items.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // DYNAMIC SHORTCUT
    // Download new data to add it in the localizedSubtitle.
    // Like localizedSubtitle: "You have \(number) new ____"
    if let shortcutItems = application.shortcutItems where shortcutItems.isEmpty {
        let dynamicShortcut =
            UIMutableApplicationShortcutItem(type: "Add New",
                                             localizedTitle: "Add New",
                                             localizedSubtitle: "Create a new lead",
                                             icon: UIApplicationShortcutIcon(templateImageName: "addNewIcon"),
                                             userInfo: nil)
        application.shortcutItems = [dynamicShortcut]
    }
    return true
}

(Swift 2.2)

Wednesday, May 18, 2016

Passing Data through Segue

The first thing you need to do is attach a segue from one view controller to another. Do NOT attach a segue from a button to a view controller or this will not work.
 
class FirstVC: UIViewController {
    
    @IBAction func ToSecondVC(sender: AnyObject) {
        let data = "Pass this to next view controller"
        performSegueWithIdentifier("ToSecondVCSegue", sender: data)
    }
    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        let secondVc = segue.destinationViewController as! SecondVC
        secondVc.data = sender as? String
    }
}

class SecondVC: UIViewController {

    var data: String?
    
    @IBOutlet weak var dataLabel: UILabel!
    
    override func viewDidLoad() {
        dataLabel.text = data
    }
}
 

(Swift 2.2)

Filtering Arrays

The main thing to remember is that "$0" represents each item in the array.
let array = ["Mark", "Matt", "Matthew", "Bob"]
let filtered = array.filter({$0.containsString("Matt")})
print(filtered.count)


class Person {
    var name: String!
    var age: Int!
    
    init(_ name: String, _ age: Int) {
        self.name = name
        self.age = age
    }
}

var people = [Person]()
people.append(Person("Mark", 45))
people.append(Person("Matt", 46))
people.append(Person("Julie", 50))
people.append(Person("Patty", 51))
people.append(Person("Bob", 52))

let filteredPeople = people.filter({$0.age >= 50})
print(filteredPeople.count)

(Swift 2.2)

Tuesday, May 17, 2016

String Functions

var str = "hello, playground"
print(str.capitalizedString) // Hello, Playground
let upperCase = str.uppercaseString // HELLO, PLAYGROUND
print(upperCase.lowercaseString) // hello, playground

let charSet = NSCharacterSet(charactersInString: ",")
let strArray = str.componentsSeparatedByCharactersInSet(charSet)
print(strArray[0]) // "hello"
print(strArray[1]) // " playground"

if str.containsString("hello") {
    print("hello found in string!")
}

print("Length of string: \(str.characters.count)")

var name = "   Mark    "
let trimThis = NSCharacterSet(charactersInString: " ")
name.stringByTrimmingCharactersInSet(trimThis) // "Mark"

let index = str.startIndex.advancedBy(7)
str.substringFromIndex(index) // "playground"
str.substringToIndex(index) // "hello, "

let norwayLike = str.stringByReplacingOccurrencesOfString("o", withString: "ø")
print(norwayLike) // "hellø, playgrøund"

let append = str.stringByAppendingString("!") // "hello, playground!"
let easier = str + "!" // "hello, playground!"

let age: Int = 25
let stringAge: String = String(age)
let stringAge2: String = "\(age)"

As you can see it still takes multiple lines to do in Swift that you can do in one line in most other languages. But I guess that is what extension classes are for!
(Swift 2.2)

Alerts

let alert = UIAlertController(title: "Alert Title", message: "This is an alert message.", preferredStyle: UIAlertControllerStyle.Alert)
// Buttons for the alert
let okButton = UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)
// Add button to alert
alert.addAction(okButton)
// Show alert
presentViewController(alert, animated: true, completion: nil)

let alert = UIAlertController(title: "Delete User", message: "Are you sure you want to delete this user?", preferredStyle: UIAlertControllerStyle.Alert)
let cancelButton = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)
let deleteButton = UIAlertAction(title: "Delete", style: UIAlertActionStyle.Destructive) { (UIAlertAction) in
    // Delete code here.
}
alert.addAction(cancelButton)
alert.addAction(deleteButton)
presentViewController(alert, animated: true, completion: nil)

(Swift 2.2)

Monday, May 16, 2016

CollectionView

Data

Start with what you want in your cells. I will create an array of this class and show it in my CollectionView.
class Data {
    var colorName: String!
    var color: UIColor!
    
    init (colorName: String, color: UIColor) {
        self.colorName = colorName
        self.color = color
    }
}

Cell

You have to be able to access the controls on the cell (besides the view of the cell).
class CollectionCell: UICollectionViewCell {
    @IBOutlet weak var colorNameLabel: UILabel!
}

View Controller

Tie it all together.
class ViewController: UIViewController,
    UICollectionViewDataSource {
    
    @IBOutlet weak var collectionView: UICollectionView!

    var data: [Data] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.dataSource = self
        
        data.append(Data(colorName: "light gray", color: UIColor.lightGray))
        data.append(Data(colorName: "gray", color: UIColor.gray))
        data.append(Data(colorName: "dark gray", color: UIColor.darkGray))
        data.append(Data(colorName: "black", color: UIColor.black))
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return data.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell
        cell.backgroundColor = data[indexPath.row].color
        cell.colorNameLabel.text = data[indexPath.row].colorName
        return cell
    }
}

(Swift 3)

Sunday, May 15, 2016

Get Data from Core Data

Assuming you already added an entity to your *.xcdatamodeld file, this is how you would get all entities.
func getRecipes() {
    let app = UIApplication.sharedApplication().delegate as! AppDelegate
    let context = app.managedObjectContext
    let fetchRequest  = NSFetchRequest(entityName: "MyEntity")
    
    do {
        let results = try context.executeFetchRequest(fetchRequest)
        recipes = results as! [Recipe]
    } catch let err as NSError {
        print(err.debugDescription)
    }
}

Possible Errors and Resolutions


EXC_BAD_INSTRUCTION

Execution breaks in the AppDelegate.managedObjectModel property. This happens when you are not including (targeting) the *.xcdatamodeld for your app. Fix by clicking on your *.xcdatamodeld file and go to File Inspector pane on the right and put a checkmark next to your app name under "Target Membership".

Unable to load class

Full Error: CoreData: warning: Unable to load class named '______' for entity '______'. Class not found, using default NSManagedObject instead.
When you auto-generated your NSManagedObject Subclass, you should have had a "@objc(MyEntity)" above it. Do not delete this, put it back in. Or just copy your code, regenerate the managed object class and paste your code back in.
(Swift 2.2)

Saturday, May 14, 2016

Customizing Xcode with Designables

You ever find yourself subclassing a UI control to customize it or customizing the look of a UI control in your viewDidLoad? And then you have to keep running the simulator to see if your control looks right?

There is an easier way, my friends. Using @IBDesignables (Interface Builder Designables) you can update your UI control realtime in the Attributes Inspector when you are on your storyboard or xib and see your changes real-time.

I will use an example of adding rounded corners to my buttons.

1. Add a file to your project and paste in this code:
import UIKit

@IBDesignable class RoundedCornerButton: UIButton {

    override func drawRect(rect: CGRect) {
        self.clipsToBounds = true
    }
    
    @IBInspectable var cornerRadius: CGFloat = 0 {
        didSet {
            self.layer.cornerRadius = cornerRadius
        }
    }
}
2. Drop a button on to your xib or storyboard and give it a background color. Then go to the Identity Inspector and select RoundedCornerButton.

Take a close look here. Notice there is now a property called "Designables" and it says "Up to date".

3. Go to the Attributes Inspector and now you it shows new properties that can be set for your button:

This shows because you selected RoundedCornerButton in the Identity Inspector on the previous step.

4. Change the value for the Corner Radius. Your button is changing right in Interface Builder (on xib or storyboard)!
This allows you to better predict how your UI will look and save you time from having to constantly run your app to see how your controls render.

(Swift 2.2)

Thursday, May 12, 2016

Saving Files on iOS Device

It is possible to save files directly to your iOS device. Here is an example of a class that saves images as PNG to the Document Directory.
class ImageData {
    
    func saveImageAsPng(image: UIImage) -> String {
        let newImageName = "image\(NSUUID().UUIDString).png"
        let fullPath = getFullPath(newImageName)
        let imageData = UIImagePNGRepresentation(image)
        imageData?.writeToFile(fullPath, atomically: true)
        return newImageName
    }
    
    func getImage(imageName: String) -> UIImage? {
        let fullPath = getFullPath(imageName)
        return UIImage(named: fullPath)
    }
    
    func clearImagesFolder() {
        let fileManager = NSFileManager.defaultManager()
        let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, 
            .UserDomainMask, true)[0]
        
        do {
            try fileManager.removeItemAtPath(path)
        } catch {
            print("Could not clear image folder: \(error)")
        }
    }
    
    func getFullPath(imageName: String) -> String {
        let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
        return "\(path)\(imageName)"
    }
}

(Swift 2.2)

Persisting a Class with UserDefaults

In a previous post I showed how to persist single values. This post shows how to save objects/classes of data.
class SaveClassToUserDefaultsVC: UIViewController {
    
    @IBOutlet weak var NameTextField: UITextField!
    @IBOutlet weak var AgeTextField: UITextField!
    
    let defaults = UserDefaults.standard
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        GetPerson()
    }
    
    @IBAction func SavePerson(sender: AnyObject) {
        let person = Person(name: NameTextField.text!, age: AgeTextField.text!)
        let personData = NSKeyedArchiver.archivedData(withRootObject: person)
        defaults.set(personData, forKey: "personData")
    }
    
    func GetPerson() {
        if let personData = defaults.data(forKey: "personData") {
            if let person = NSKeyedUnarchiver.unarchiveObject(with: personData) as? Person {
                NameTextField.text = person.name
                AgeTextField.text = person.age
            }
        }
    }
}

What is NSKeyedArchiver and NSKeyedUnarchiver?

If you are familiar with other languages this is similar to serializing and deserializing.

You app needs to take data from memory and store it in a file. This is what the NSKeyedArchiver does. It "archives" the data or writes it for future use.

To restore the archived data (file) back into memory you use the NSKeyedUnarchiver. This will restore your data back into a class you can then use in your code.

Setup You Class To Be "Archivable"

There is some setup work you have to do to your class before it can be archived and unarchived.

class Person: NSObject, NSCoding {
    
    var name = ""
    var age = ""
    
    required init(name: String, age: String) {
        self.name = name
        self.age = age
    }
    
    required init(coder decoder: NSCoder) {
        self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
        self.age = decoder.decodeObject(forKey: "age") as? String ?? ""
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(self.name, forKey: "name")
        coder.encode(self.age, forKey: "age")
    }
}

(Swift 3)

Accessing the Camera Roll

To access the camera roll you want to use the Image Picker.

Swift 3.0

 
class ViewController: UIViewController, UIImagePickerControllerDelegate, 
UINavigationControllerDelegate {

    var imagePicker: UIImagePickerController!
    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        imagePicker = UIImagePickerController()
        imagePicker.delegate = self
    }

    @IBAction func SelectImage_TouchUpInside(sender: AnyObject) {
        present(imagePicker, animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        imagePicker.dismiss(animated: true, completion: nil)
        imageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
    }
}
 
In iOS 10 you also need to give a usage description in your Info.plist

Swift 2.2

 
class ViewController: UIViewController, UIImagePickerControllerDelegate, 
UINavigationControllerDelegate {

    var imagePicker: UIImagePickerController!
    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        imagePicker = UIImagePickerController()
        imagePicker.delegate = self
    }

    @IBAction func SelectImage_TouchUpInside(sender: AnyObject) {
        presentViewController(imagePicker, animated: true, completion: nil)
    }

    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
        imagePicker.dismissViewControllerAnimated(true, completion: nil)
        imageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
    }

    // Deprecated
//    func imagePickerController(picker: UIImagePickerController, 
//            didFinishPickingImage image: UIImage, 
//            editingInfo: [String : AnyObject]?) {
//        imagePicker.dismissViewControllerAnimated(true, completion: nil)
//        imageView.image = image
//    }
}
 

(Swift 2.2)

Tuesday, May 10, 2016

Easiest Way to Persist Data

If your needs are simple, consider this option before moving on to other storage methods.

Saving

 
@IBOutlet weak var nameTextField: UITextField!

...

UserDefaults.standard.setValue(nameTextField.text, forKey: "name")
 

Retrieving

 
if let name = UserDefaults.standard.value(forKey: "name") as? String {
    nameTextField.text = name
}
 

Tips

Save some typing and create a variable to access the defaults:
  • let defaults = UserDefaults.standard
You can persist classes too but there is more work to it. May write another post for it.

(Swift 3.0)

Monday, May 9, 2016

Download Image from URL

Synchronously

Swift 2.2

let url = NSURL(string: 
"http://www.awwwards.com/awards/images/2014/07/UX-design-resources-09.jpg")

if let data = NSData(contentsOfURL: url!) {
    WebImageView.image = UIImage(data: data)
}

Swift 3.0

let url = URL(string: 
"http://www.awwwards.com/awards/images/2014/07/UX-design-resources-09.jpg")

if let data = try? Data(contentsOf: url!) {
    WebImageView.image = UIImage(data: data)
}

Asynchronously

Swift 3.0

let url = URL(string: 
"http://www.awwwards.com/awards/images/2014/07/UX-design-resources-09.jpg")

let task = URLSession.shared.dataTask(with: url!) { data, response, error in
    guard let data = data, error == nil else { return }
    
    DispatchQueue.main.sync() {
        self.WebImageView.image = UIImage(data: data)
    }
}

task.resume()

Tips

You may get an error such as: "App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file."

To fix this open the Info.plist file in your project and follow these steps:
  1. Click the "+" button on the last item in the list to add a new item.
  2. Add "App Transport Security Settings" and then expand the setting and click the "+" button again to add "Allow Arbitrary Loads". Set this to "YES".

Open Web Page in iOS

Showing a web page in your iOS app is a two step process. Maybe even a one step process if you already have a web view setup.
// 1. Setup the web view
let webView: WKWebView = WKWebView()
view.addSubview(webView)
webView.frame = view.frame

// 2. Setup the request
let url = NSURL(string: "http://swiftquickstart.blogspot.com/")!
let request = NSURLRequest(URL: url)
webView.loadRequest(request)
Recommendations:
  • The webView variable should be global in your view controller
  • Set the webView.frame in viewDidLayoutSubviews so when you change orientation it gets reset

(Swift 2.2)

Saturday, May 7, 2016

Timers

In this example when startTimer function is called it creates a timer that fires every second repeatedly. Every time the timer fires it makes a call to the notifyUiToUpdate function.
class Timer {
    
    private var _timer: NSTimer!

    func startTimer() {
        _timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, 
            selector: #selector(notifyUiToUpdate), userInfo: nil, repeats: true)
    }
    
    func stopTimer() {
        _timer.invalidate()
    }
    
    @objc func notifyUiToUpdate() {
        NSNotificationCenter.defaultCenter().postNotification(
            NSNotification(name: "updateView", object: nil))
    }
}


(Swift 2.2)

Notifications

Using NotificationCenter you can communicate from one class to another. For example, while executing code in one class you can notify the UI to update something.

Basic Setup

In a separate class you might "post" a notification. In this example I named the post "updateView". Notifications is a 3 step process:

Step 1 - Setup Post Names

 
extension Notification.Name {
    static let updateView = NSNotification.Name("updateView")
}
 

Step 2 - Post Notification

To pass data, you can send a dictionary to the userInfo parameter.
 
var userInfo: [String: String] = ["extraData": "Some extra data"]
userInfo["moreData"] = "Some more data"
NotificationCenter.default.post(name: .updateView, object: self, 
    userInfo: userInfo)
 

Step 3 - Observe Notification

Then in your ViewController you might be "observing" for that particular post called "updateView" in the notification center:
 
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver
            (forName: .updateView, object: nil, 
             queue: OperationQueue.main) {
        (notification) in
        // Get values from the class that sent the notification
        let poster = notification.object as! ClassThatPostedNotification
        let propertyValue = poster.property

        // Get values from userInfo
        let extraData = notification.userInfo?["extraData"] as! String
        let moreData = notification.userInfo?["moreData"] as! String
    }
}
 

So the ViewController will be observing any post called "updateView" and when it observes a post it calls updateView. I usually name my posts and functions the same name because...well, I am a simple guy. :)

(Swift 4)

Touch and Drag Objects

If you want to touch and drag an object around the screen you can do this by setting the object's center property to the location of your touch.
In this example I have an image (UIImageView) that I want to touch and drag.

Create New Class

import UIKit

class DraggableImage: UIImageView {
    
    override func touchesMoved(touches: Set, withEvent event: UIEvent?) {
        if let touch = touches.first {
            let position = touch.locationInView(superview)
            center = CGPointMake(position.x, position.y)
        }
    }
}
I override touchesMoved and am getting the position of my touch and making the center of my image the same position as my touch.


Setup Image in Xcode

In this example I am using a UIImageView. I dropped an image on my storyboard and I have to make 2 changes.
  1. Set the Custom Class
    Set the class to the class that overrides touchesMoved:
  2. Set Interaction
    Make sure "User Interaction Enabled" is checked. This allows your touchesMoved override to receive touch event messages.
    I also unchecked "Multiple Touch" because I'm only using one finger to move my object.
That is it! You should be able to run and move your image. (Written for Xcode 7.3, Swift 2.2)

Friday, May 6, 2016

Private - Scope

I just discovered this and thought I would share it. Look at this code:
class Vehicle {
    private var color: String = "red"
}

var car = Vehicle()

print(car.color)

How am I able to access the color property even though it is private?


It turns out it is file based. If you are in the same file as the class you can access the private variables. But if you put your class in one file and then instantiate it another file then you cannot access the private variables.

(Swift 2.2)

Thursday, May 5, 2016

Add And Play Sound In Your iOS Project


Tutorial on adding sounds to iOS projects.
  1. Drag and drop your sound file into your project.
    In this example the name of my sound file is "ButtonClickSound.wav".
  2. Code
    import UIKit
    import AVFoundation
    
    class ViewController: UIViewController {
    
        var buttonClickSound: AVAudioPlayer!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let path = NSBundle.mainBundle().pathForResource("ButtonClickSound", 
                ofType: "wav")
            let url = NSURL(fileURLWithPath: path!)
            
            do {
                try buttonClickSound = AVAudioPlayer(contentsOfURL: url)
                buttonClickSound.prepareToPlay()
            } catch let err as NSError {
                print(err.debugDescription)
            }
        }
    
        @IBAction func PlaySound_TouchUpInside(sender: UIButton) {
            if buttonClickSound.playing {
                buttonClickSound.stop()
            }
    
            buttonClickSound.play()
        }
    }
    
    

(Swift 2.2)

Wednesday, May 4, 2016

Add Custom Fonts To Your iOS App

Tutorial on using a custom font
  1. Get your font
    Go to your favorite font website and download a font. I will go to http://www.dafont.com/ and download one. Unzip the download and you should see a *.ttf file.
  2. Add to your project
    Drag and drop the *.ttf file directly into your Xcode project. A dialog will appear. Make sure you add checkmarks to Destination and your target application.
  3. Add to your info.plist
    Select the last item in your info.plist and click the "+" button. Select "Fonts provided by application" (start typing in "Fonts" and it will show up). Expand this item and in the empty space next to "Item 0" type in the full name of your font file. (I just copy and paste the file name.)
  4. Change the font on your label
    Add a label to your UI and change the font. You have to select "Custom" first. Then you can change the font's family to your custom font's name.

SwiftUI Search & Filter with Combine - Part 3 (iOS, Xcode 13, SwiftUI, 2...

In part 3 of the Searchable video series, I show you how to use Combine in #SwiftUI for the search and filter logic connected to the searcha...