iOS10 Swift 3 pList

Using Xcode 8.0 for Swift 3 / iOS 10.

In this tutorial, we’re going to load some preset data from a pList and display it in a ViewController containing a mapView.  We’re going to do this entirely in code without changing the storyboard.

The ViewController will something look like this:

screenshot-2016-10-12-22-29-58

As you see we display the flag , the name of the country , the name of the capital, set the map’s position to the capital and create an annotation there.

So let’s get started: 

In xcode, create a new project, choose iOS, choose Single View Application,  give it a Product Name of pList, choose Universal for Devices and store it somewhere.

Since we’re including maps, we need to turn Maps Capability on. On the left pane of xcode select the Project Navigator, select your project, then in the centre pane select Capabilities, scroll down to Maps and switch Maps to on:

Please access the first version of this project at GitHub and copy the flags and countrydata.plist file. Add the files to the project:

screenshot-2016-10-12-23-00-22

As can see the countrydata.plist file contains an array of 7 items, each item contains dictionaries with information about a country and it’s capital that we will be using later.

Now select ViewController.swift after line import UIKit, add the line “import MapKit”.

We’re going to write 2 functions for MapView Annotations so we want to comply to the MapView delegate. Change class line to “class ViewController: UIViewController, MKMapViewDelegate” , add line “var mapView  = MKMapView()”  before function viewDidLoad() and add line “mapView.delegate = self” to function viewDidLoad()

Add the following declarations before func viewDidLoad:

var mainStackView = UIStackView()
var countryNameLabel = UILabel()
var countryCapitalLabel = UILabel()
var countryFlagButton = UIButton(type: .roundedRect)
// plist file name
let plistFileName = “countrydata”
let fileType = “plist”
// Dictionary names
let countryCodeID = “landCode”
let countryNameID = “landName”
let countryCapitalID = “landCapital”
let countryFlagID = “landFlagImage”
let countryCapLatID = “landCapLat”
let countryCapLonID = “landCapLon”
var plistArray = [Dictionary<String, String>]()
var countryNumber = 0
var countryDict: Dictionary<String, String>!

Add the following empty functions after function viewDidLoad():

func setupStackView()
{
/*
Set up StackViews containing
1. Label   with country name
2. Label   with country capital
3. Button  with country flag
4. MapView with capital position.
*/
}
func readpList()
{
/*
Read the pList, we expect an array containing
a set of dictionaries for each country.
*/
}
func chooseRandomCountry()
{
}
func updateUI()
{
/*
Update the labels with country name and capital
Update button with a image of the country flag
Centre the mapView to the capital’s position.
*/
}
func flagTapped(sender: UIButton!)
{
// switch to next country
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
return nil
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
{
}

The xcode project at this stage can be downloaded from GitHub

Adding the following code to function viewDidLoad:

setupStackView()
readpList()
chooseRandomCountry()
updateUI()

Adding the following code to function readplist:

if let path = Bundle.main.path(forResource: plistFileName, ofType: fileType)
{
if let array = NSArray(contentsOfFile: path) as? [[String: String]]
{
plistArray = array
print(plistArray)
}
}

Run the app and check that the output pane contains:screenshot-2016-10-14-22-05-22

If the output is missing, check that the project contains the countrydata.plist file  and that the following lines are correct:

let plistFileName = “countrydata”
let fileType = “plist”

Addng the following code to function setupStackView:

countryNameLabel.backgroundColor = UIColor.lightGray
countryNameLabel.textColor = UIColor.black
countryNameLabel.textAlignment = .center
countryNameLabel.text = “UK”
countryNameLabel.font = UIFont.preferredFont(forTextStyle: .title1)
countryNameLabel.numberOfLines = 1
countryNameLabel.frame = CGRect(x: 0, y: 0, width: 120, height: 60)

countryCapitalLabel.backgroundColor = UIColor.lightGray
countryCapitalLabel.textColor = UIColor.black
countryCapitalLabel.textAlignment = .center
countryCapitalLabel.text = “London”
countryCapitalLabel.font = UIFont.preferredFont(forTextStyle: .title1)
countryCapitalLabel.numberOfLines = 1
countryCapitalLabel.frame = CGRect(x: 0, y: 0, width: 120, height: 60)

let image = UIImage(named: “uk.png”)
countryFlagButton.setBackgroundImage(image, for: .normal)
countryFlagButton.addTarget(self, action: #selector(flagTapped), for: .touchUpInside)
countryFlagButton.frame = CGRect(x: 10, y: 0, width: 120, height: 60)

let labelStackView = UIStackView()
labelStackView.axis = .vertical
labelStackView.alignment = .center
labelStackView.distribution = .fill
labelStackView.spacing = 1
labelStackView.addArrangedSubview(countryNameLabel)
labelStackView.addArrangedSubview(countryCapitalLabel)
labelStackView.translatesAutoresizingMaskIntoConstraints = false

let flagStackView = UIStackView()
flagStackView.axis = .horizontal
flagStackView.alignment = .center
flagStackView.distribution = .equalSpacing
flagStackView.addArrangedSubview(countryFlagButton)
flagStackView.addArrangedSubview(countryNameLabel)
flagStackView.addArrangedSubview(countryCapitalLabel)
flagStackView.spacing = 10.0
flagStackView.translatesAutoresizingMaskIntoConstraints = true

mainStackView.axis = .vertical
mainStackView.alignment = .fill
mainStackView.distribution = .fill
mainStackView.spacing = 10.0

mainStackView.addArrangedSubview(mapView)
mainStackView.addArrangedSubview(flagStackView)
mainStackView.translatesAutoresizingMaskIntoConstraints = false

self.view.addSubview(mainStackView)

//autolayout the stack view
let viewsDictionary = [“stackView”:mainStackView]
let stackView_H = NSLayoutConstraint.constraints(withVisualFormat:
“H:|-20-[stackView]-20-|”,
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: viewsDictionary)
let stackView_V = NSLayoutConstraint.constraints(withVisualFormat:
“V:|-20-[stackView]-20-|”,
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: viewsDictionary)
self.view.addConstraints(stackView_H)
self.view.addConstraints(stackView_V)

Run the app again, the simulator should look something like this:

screenshot-2016-10-14-22-42-45

We’ll use a GameKit function to select a random country in function chooseRandomCountry, so insert line “import GameplayKit” at the top of ViewController.swift and add the following code to function chooseRandomCountry:

// select random country
countryNumber = GKRandomSource.sharedRandom().nextInt(upperBound: plistArray.count)
countryDict = plistArray [countryNumber]

We’re going to store all the information about the current country in a new class. We also want to use this class to display the annotations.

First select new iOS cocoa touch file, give the class the name “Country” and a subclass of NSObject, language of Swift, and press create.

Edit Country.swift , change the contents to:

import UIKit
import MapKit

class Country: NSObject, MKAnnotation {
var title: String? // landCapital
var coordinate: CLLocationCoordinate2D
var info: String
var landCode: String
var landName: String
var flagFileName: String

init(title: String, coordinate: CLLocationCoordinate2D, info: String, landCode: String, landName: String, flagFileName: String) {
self.title = title
self.coordinate = coordinate
self.info = info
self.landCode = landCode
self.landName = landName
self.flagFileName = flagFileName

super.init()
}
}

Now let’s add the following code to function chooseRandomCountry:

// Extract country details from dictionaries
var capLatitude: Float = 0.0
var capLongitude: Float = 0.0
if let latCode = countryDict[countryCapLatID]
{
capLatitude = (latCode as NSString).floatValue
}
if let lonCode = countryDict[countryCapLonID]
{
capLongitude = (lonCode as NSString).floatValue
}

let capCoordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(capLatitude),longitude: CLLocationDegrees(capLongitude))

let cTitle = countryDict[countryCapitalID]!
let codeString = countryDict[countryCodeID]!
let fileString = countryDict[countryFlagID]!

// make info string for the notation
let landString = countryDict[countryNameID]!
let capString = NSLocalizedString(“Thecapitalof”, comment: “The capital of ..”)
let infoString = “\(capString) \(landString)”

// store country details to Country class.
currentCountry = Country(title: cTitle, coordinate: capCoordinate, info: infoString, landCode: codeString, landName: landString, flagFileName: fileString)

Before function viewDidLoad add the following line:

var currentCountry: Country? = nil
let regionRadius: CLLocationDistance = 200000

Add the following code to function updateUI:

countryNameLabel.text = currentCountry?.landName
countryCapitalLabel.text = currentCountry?.title

if let imageName = currentCountry?.flagFileName
{
let image = UIImage(named: imageName)
countryFlagButton.setBackgroundImage(image, for: .normal)
}
centerMapOnLocation(location: (currentCountry?.coordinate)!)
mapView.addAnnotation(currentCountry!)

After function updateUI add the following function:

func centerMapOnLocation(location: CLLocationCoordinate2D)
{
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location, regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: false)
}

Run the app, and you should see something like this for a random country:

screenshot-2016-10-14-23-58-49

If you tap the annotation pin you should only see the annotation title, the capital name.

Let’s code the mapView Annotations and the flag button.

Add the following code to the function mapView viewFor annotation:

if annotation is Country
{
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier)
if annotationView == nil
{
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView!.canShowCallout = true

let button = UIButton(type: .detailDisclosure)
annotationView!.rightCalloutAccessoryView = button
}
else
{
annotationView!.annotation = annotation
}
return annotationView
}

Add the following code to the function mapView annotationView view:

let country = view.annotation as! Country
let placeName = country.title
let placeInfo = country.info
let ac = UIAlertController(title: placeName, message:
placeInfo, preferredStyle: .alert)
let okString = NSLocalizedString(“OK”, comment: “OK in Alert”)
ac.addAction(UIAlertAction(title: okString, style: .default))
present(ac, animated: true)

Then add the following line before viewDidLoad function:

let annotationIdentifier = “Country”

Add the following code to flagTapped function:

chooseRandomCountry()
updateUI()

Run the app and check that checking on the flag changes the view to a random country and that the annotation shows the information message:

screenshot-2016-10-15-00-32-48

So we’re very nearly finished.

The xcode project at this stage can be downloaded from GitHub

Localization

We haven’t changed our storyboard, however as I want to document some localization, let’s made a small change.

Select Main.storyboard, select the View Controller, and choose “Editor” menu item “Embed in” and then “Navigation Controller”.

Now select the Navigation Item in View Controller, select the Attributes Inspecter in right pane:

screenshot-2016-10-15-12-25-20

Give the Navigation Item a Title “Capitals”:

screenshot-2016-10-15-12-30-10

Select the Project at the top of the Project Navigator pane , select Editor menu item “Add Localization” and select French (fr):

screenshot-2016-10-15-12-37-00

then  de-select LaunchScreen.storyboard and select Finish :

screenshot-2016-10-15-12-37-59

Repeat for German (de) and Dutch (nl) :

screenshot-2016-10-15-12-39-10

 

Edit the 3 files Main.string (French), Main.string (German) and Main.strings (Dutch), replacing String “Capitals” :

screenshot-2016-10-15-13-00-45

screenshot-2016-10-15-13-01-16

screenshot-2016-10-15-13-01-51

 

Now we need to handle our strings in our code.

Create a new file, select iOS, Resource, Strings File, Next:

screenshot-2016-10-15-13-16-29

Give the file the name “Localizable”  and create it.:

screenshot-2016-10-15-13-20-55

Edit Localizable.strings, add the following code:

Thecapitalof=”Capital of”;
OK=”OK”;

In the File Inspector, in the right pane, select localize for English.

screenshot-2016-10-16-01-46-13

Then select Localization for languages French, German and Dutch.

screenshot-2016-10-16-02-07-37

 

Select and change the contents of Localizable.string (French) to:

screenshot-2016-10-16-01-48-45

 

Select and change the contents of Localizable.string (German) to:

screenshot-2016-10-16-01-49-59

 

Select and change the contents of Localizable.string (Dutch) to:

screenshot-2016-10-16-01-50-31

 

Now we need to localize countrydata.plist. Select countrydata.list and in the File Inspector, in the right pane, select localize for English:

screenshot-2016-10-16-02-15-49

Then select localization for French, German and Dutch languages:

screenshot-2016-10-16-02-16-26

Now select countrydata.plist (French) and change the landNames and landCapitals into french:

screenshot-2016-10-16-02-24-13

 

Now select countrydata.plist (German) and change the landNames and landCapitals into german:

screenshot-2016-10-16-02-28-11

Now select countrydata.plist (Dutch) and change the landNames and landCapitals into dutch:

screenshot-2016-10-16-02-32-05

 

Run the app, in the simulator,  select Hardware and Home , then select the Settings app and change the language to french. Run the app and check the output is french:

screenshot-2016-10-16-02-47-23

Select Hardware and Home , then select the Settings app and change the language to allemand. Run the app and check the output is german:

screenshot-2016-10-16-02-52-53

Select Hardware and Home , then select the Settings app and change the language to nederlands. Run the app and check the output is dutch:

screenshot-2016-10-16-02-58-27

 

The final xcode project  can be downloaded from GitHub

(publishing now, need to check and expand some words)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s