Sunday, October 25, 2015

Using Google Maps SDK for iOS in Swift

Update: 
December 14, 2015 - Modify step 13 and add step 14 for the V1.11.1 SDK release.
January 7, 2016 - Update step 6 images for the new Google Developers Console interface.
January 13, 2016 - Add more information about CocoaPods at the bottom of this post.
May 27, 2016 - Update Step 3 for CocoaPods 1.0.

This tutorial shows how to add a mapView using Google Maps SDK for iOS and add a segmented control to switch between normal, satellite, and hybrid types. This tutorial is created with Xcode 6.4 and successfully tested with Xcode 7.1.

1. Install CocoaPods. (More information about CocoaPods is written at the bottom of this post.)

Type this command in the terminal:

sudo gem install cocoapods

2. Create an Xcode project and close it.

3. In the Xcode project directory, create a Podfile as below:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

pod 'GoogleMaps'


(You may open the Podfile with this command: open -a Xcode Podfile)

Update May 27, 2016:

The above code may not work with the new CocoaPods 1.0. If you see error like this:


The dependency `GoogleMaps` is not used in any concrete target.

Try modify your Podfile as below:

target "ProjectName" do
    pod 'GoogleMaps'

end

4. Run the terminal and type the command below in the project directory:

pod install

You may see something like this in the terminal:





5. Open the projectName.xcworkspace file just automatically created. (Don't open the original .xcodeproj file)




You may see the project navigator like this with a new Pods folder:


6. Go to Google Developers Console. Create a new project.  Select the "Products & services" button at the Google Developers Console title.



Select API Manager.


Select Google APIs and enable Google Maps SDK for iOS.

Select Credentials.


Select Add credentials -> API Key. (The image below is the previous interface.)



Select "iOS key" and then name the key.

You may leave the "Accept requests from an iOS application with one of these bundle identifiers" field as blank since it is optional, or fill in your app bundle identifier.

Now you should be able to see your iOS API key.

7. Add a temporary Objective-C file to your project. You may give it any name you like.


Select Yes to configure an Objective-C bridging header. (Update: Select Create Bridging Header.)


Delete the temporary Objective-C file you just created.

8. In the projectName-Bridging-Header.h file just created, add this line:


#import <GoogleMaps/GoogleMaps.h>


9. Edit the AppDelegate.swift file:

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        GMSServices.provideAPIKey("AIza....") //iOS API key
        
        return true

    }

10. Modify ViewController.swift as below:

import UIKit

class ViewController: UIViewController, GMSMapViewDelegate {
    
    var mapView : GMSMapView!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        mapView = GMSMapView(frame: CGRectMake(0, 100, view.bounds.width, view.bounds.height - 100))
        
        //Default position at Chungli (Zhongli) Railway Station, Taoyuan, Taiwan.
        mapView.camera = GMSCameraPosition.cameraWithLatitude(24.953232, longitude: 121.225353, zoom: 15.0)
        
        mapView.mapType = kGMSTypeNormal
        mapView.delegate = self
        
        view.addSubview(mapView)
        
        //Add a segmented control for selecting the map type.
        let items = ["Normal", "Satellite", "Hybrid"]
        let segmentedControl = UISegmentedControl(items: items)
        segmentedControl.frame = CGRectMake(50, 60, 200, 20)
        segmentedControl.addTarget(self, action: "mapType:", forControlEvents: UIControlEvents.ValueChanged)
        view.addSubview(segmentedControl)
        
    }
    
    func mapType(sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0:
            mapView.mapType = kGMSTypeNormal
        case 1:
            mapView.mapType = kGMSTypeSatellite
        default:
            mapView.mapType = kGMSTypeHybrid
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

11. Edit the Info.plist file (Required for Xcode 7 and iOS 9):

Key: LSApplicationQueriesSchemes
Type: Array

Key: Item 0
Type: String
Value: googlechromes

Key: Item 1
Type: String
Value: comgooglemaps



Without modifying Info.plist, you'll get


Pressing the Google logo on the map in the iOS simulator shows:


canOpenURL: failed for URL: "comgooglemaps://" - error: "This app is not allowed to query for scheme comgooglemaps"


canOpenURL: failed for URL: "googlechromes://" - error: "This app is not allowed to query for scheme googlechromes"

This is because the iOS simulator does not include Google Maps and Chrome apps. So check this feature with a device.


12. Run the iOS simulator and get this:




Update:

December 14, 2015 - Step 13 is no longer required since Google has resolved this issue in SDK V1.11.0 - December 2015. 
Google Maps SDK for iOS Release notes Google Maps API Bug Reports: Bitcode Build

13. Xcode 7.1: If failing to run with iPhone/iPad with this error:


clang: error: linker command failed with exit code 1 (use -v to see invocation)


Try to disable the bitcode:

Targets -> Build Settings -> Build Options -> Enable Bitcode -> No



And build the code again. The build should succeed.

Update:
December 14, 2015 - Add Step 14.

14. The ITMS-90535 error happened during app submission has been resolved in  V1.11.1 SDK, which has updated the GoogleMaps.bundle info.plist file. Make sure that you have downloaded the latest SDK.

Google Maps SDK for iOS Release Notes
Google Maps API Bug Reports: Bug: unexpected CFBundleExecutable Key in GoogleMaps 1.10.1

=============
January 13, 2016

Additional Information

Some CocoaPods commands for reference:

1. Check the CocoaPods version installed:
pod --version

2. Check if CocoaPods framework of a project is outdated:

Go to the directory including the Podfile. Type:

pod outdated



The pod outdated command also checks if new CocoaPods version is available.

3. Update CocoaPods using the install command:

sudo gem install cocoapods

4. Update CocoaPods to a pre-release version:

sudo gem install cocoapods --pre

5. Update local repositories:

pod update



Related Information:

Google Sign-In for iOS - Create a GIDSignInButton programmatically in Swift
Marker Clustering with Google's Utility library for Maps SDK (Google-Maps-iOS-Utils)
Carthage Tutorial -  Reachability.swift

Saturday, October 24, 2015

CLLocationCoordinate2DIsValid - Check the validity of the default user location

This post shows how to:

1. Initialize the default user location with an invalid coordinate
2. Then check for the validity of the location.

let myLocation = CLLocation(latitude: kCLLocationCoordinate2DInvalid.latitude, longitude: kCLLocationCoordinate2DInvalid.longitude)

if CLLocationCoordinate2DIsValid(myLocation.coordinate) {
    println("myLocation is valid!!")
} else {
    println("myLocation is invalid!!")

}

If the user does not permit the device to access his/her location while using the app, the result is:

myLocation is invalid!!

Xcode shows "Build Succeeded" but iOS simulator does not run

If Xcode shows "build succeeded"



but iOS simulator is not run.

Select "Edit Scheme":

Select "Run" and check for the "Executable" option.



If "None" is selected, change it to projectName.app.

Monday, October 19, 2015

componentsSeparatedByCharactersInSet and join - Remove specific characters in a string

In this example, parenthesis "()" and white space " " are removed. For Xcode 6.4 (Swift 1.2):

let phoneOld = "(03)123 456 789" as NSString
let charSet = NSCharacterSet(charactersInString: "() ")

//Separate phoneOld by unwanted characters in charSet and form an array.
let phoneArray = phoneOld.componentsSeparatedByCharactersInSet(charSet) as! [String]


//Join all elements of phoneArray together without inserting any character between the elements.
let phoneNew = join("", phoneArray)


Results:



For Swift 2:

let phoneOld = "(03)123 456 789" as NSString
let charSet = NSCharacterSet(charactersInString: "() ")
let phoneArray = phoneOld.componentsSeparatedByCharactersInSet(charSet)

let phoneNew = phoneArray.joinWithSeparator("")

Related string function:

Wednesday, October 14, 2015

Open an internet image and create a button with underline title

Update: July 12, 2017 - Xcode 8.3.3 + Swift 3.1

Original Post: Xcode 6.4 (Swift 1.2)

Note: For Xcode 7 (Swift 2), refer to the information at the bottom of this page.


How to open an image file online with an iOS app?
Simply modify ViewController.swift as below:

Update: July 12, 2017 - Xcode 8.3.3 + Swift 3.1

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //--------------------- Image ----------------------
        // URL for the web image
        let url = URL(string: "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKqwWN6ru5rEMD7sT38QiCuFETD-65Hizt8k7KiS5QnWfRwydKcsX0rj6zgAezKFRm-GWGjJWBHWjoa7pKy5ubbBtAECVwmuohkBx4zL9eK6_XP-UbtXjCcpPl9cFKsScIOtzWoN0pxD4/s1600/IMG_4809.JPG")
        let imageView = UIImageView(frame: CGRect(x: 20, y: 100, width: view.bounds.width-40, height: view.bounds.height-200))
        
        //Image data for Swift 3
        var data : Data!
        do {
            data = try Data(contentsOf: url!)
        } catch {
            print(error.localizedDescription)
            return
        }
        
        imageView.image = UIImage(data: data)
        
        //Scale the image with the original aspect ratio
        imageView.contentMode = UIViewContentMode.scaleAspectFit
        
        view.addSubview(imageView)
        
        
        //--------------------- Link Button ----------------------
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 20))
        
        //Relocate the button with the center position.
        button.center = CGPoint(x: view.bounds.width/2, y: 50)
        
        //Underline the button and set the text as blue.
        let attributedString = NSAttributedString(string: "More Photos", attributes: [NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue, NSForegroundColorAttributeName: UIColor.blue])
        button.setAttributedTitle(attributedString, for: UIControlState.normal)
        
        //Set the highlight color as cyan.
        let attributedStringHighlight = NSAttributedString(string: "More Photos", attributes: [NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue, NSForegroundColorAttributeName: UIColor.cyan])
        button.setAttributedTitle(attributedStringHighlight, for: UIControlState.highlighted)
        
        button.addTarget(self, action: #selector(btnPressed), for: UIControlEvents.touchUpInside)
        view.addSubview(button)
        
    }
    
    func btnPressed() {
        let string = "http://cutecorners.blogspot.com/"
        UIApplication.shared.open(URL(string: string)!, options: [:], completionHandler: nil)//Open the URL in the browser.
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

Xcode 6.4 (Swift 1.2): Edit ViewController.swift as below:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //--------------------- Image ----------------------
        // URL for the web image
        let url = NSURL(string: "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKqwWN6ru5rEMD7sT38QiCuFETD-65Hizt8k7KiS5QnWfRwydKcsX0rj6zgAezKFRm-GWGjJWBHWjoa7pKy5ubbBtAECVwmuohkBx4zL9eK6_XP-UbtXjCcpPl9cFKsScIOtzWoN0pxD4/s1600/IMG_4809.JPG")
        
        let imageView = UIImageView(frame: CGRectMake(20, 100, view.bounds.width-40, view.bounds.height-200))
        imageView.image = UIImage(data: NSData(contentsOfURL: url!)!)
        
        //Scale the image with the original aspect ratio
        imageView.contentMode = UIViewContentMode.ScaleAspectFit
        
        view.addSubview(imageView)
        
        
        //--------------------- Link Button ----------------------
        let button = UIButton(frame: CGRectMake(0, 0, 200, 20))
        
        //Relocate the button with the center position.
        button.center = CGPoint(x: view.bounds.width/2, y: 50)
        
        //Underline the button and set the text as blue.
        let attributedString = NSAttributedString(string: "More Photos", attributes: [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue, NSForegroundColorAttributeName: UIColor.blueColor()])
        button.setAttributedTitle(attributedString, forState: UIControlState.Normal)
        
        //Set the highlight color as cyan.
        let attributedStringHighlight = NSAttributedString(string: "More Photos", attributes: [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue, NSForegroundColorAttributeName: UIColor.cyanColor()])
        button.setAttributedTitle(attributedStringHighlight, forState: UIControlState.Highlighted)

        button.addTarget(self, action: "btnPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(button)
        
    }
    
    func btnPressed(sender: UIButton) {
        let string = "http://cutecorners.blogspot.com/"
        UIApplication.sharedApplication().openURL(NSURL(string: string)!)//Open the URL in the browser.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

The result:





Updated Oct. 31, 2015:  Xcode 7 (Swift 2)


While building the above code, error happens:


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.

This is because Apple has introduced App Transport Security (ATS) in iOS9, but the image link used in this example is an insecure HTTP URL. To allow this network connection, modify Info.plist by adding:



If you need secure HTTPS connections, do not use this solution. More details regarding ATS is here:


Related Information:

Apple will require HTTPS connections for iOS apps by the end of 2016

Monday, October 12, 2015

Get the device type and iOS version

Update: December 10, 2016 / September 23, 2017
Xcode 8.1 (Swift 3.0.1) / Xcode 9.0 (Swift 4)

Swift 3/Swift 4 Code:

let systemVersion = UIDevice.current.systemVersion
print("iOS\(systemVersion)")
        
//iPhone or iPad
let model = UIDevice.current.model

print("device type=\(model)")

Result:

iOS10.1.1
device type=iPhone

Original post: October 12, 2015

Swift 1 Code:

let systemVersion = UIDevice.currentDevice().systemVersion
println("iOS\(systemVersion)")
        
//iPhone or iPad
let model = UIDevice.currentDevice().model
println("device type=\(model)")


Result:

iOS8.4.1
device type=iPad


To get the device model name string:

For Swift 2.0:

For Swift 1.2: