Wednesday, December 30, 2015

Enable Wi-Fi AP list when starting an app

Note: The information below is for iOS 8. For iOS 9, refer to NEHotspotHelper.

Normally iOS developers cannot write some code to show Wi-Fi hotspots around an iPhone / iPad. The iDevice user has to select Setting -> Wi-Fi to access a list of nearby Wi-Fi networks.

Although there are a lot of programming restrictions with Wi-Fi functions, it is still possible to show a list of surrounding Wi-Fi APs by modifying the Info.plist file.

1. Create a new project and edit the Info.plist property list file as below:

Key: Application uses Wi-Fi
Type: Boolean
Value: YES



Another way of modification is to open Info.plist as source code and add the UIRequiresPersistentWiFi key as true:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIRequiresPersistentWiFi</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>

2. Select Setting -> Wi-Fi. Turn on both Wi-Fi and Ask to Join Networks.


3. Run the app. The Select a Wireless Network window is automatically popped up.

How to get UUID (Universally Unique Identifier), IDFV (Vendor Identifier) or IDFA (Advertising Identifier)

Update:
January 29, 2016
UUID (Universally Unique Identifier) may be generated with:


let uuid = NSUUID().UUIDString
print(uuid)

Result:


19B7xxxx-xxxx-xxxx-xxxx-xxxxxxxx0CFA

===============
Original post:
December 30, 2015

Some device identifiers are now impossible to be obtained from public APIs of iOS:
IMSI - International Mobile Subscriber Identity (SIM card number)
IMEI - International Mobile Equipment Identity (Device ID)
UDID - Unique Device Identifier for Apple iDevices
MAC address - Media Access Control Address (Network address)

IDFV and IDFA are the identifiers available for identification or for advertising:

IDFV (Vendor Identifier / Identifier for Vendor)
IDFV is the same for different apps provided by the same vendor running on the same device.

IDFA (Advertising Identifier / Identifier for Advertising)
IDFA of an iOS device remains the same unless the user make a reset in the Settings app.

IDFV and IDFA can be obtained easily in code. However, IDFA has to be used for advertising. Improper use of IDFA in an app will cause app rejection from the App Store.

How to obtain IDFV / IDFA
Modify ViewController.swift as:

import UIKit
import AdSupport

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton(frame: CGRectMake(70, 100, 200, 20))
        button.setTitle("Get IDs", forState: UIControlState.Normal)
        button.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        button.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        button.addTarget(self, action: "btnPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(button)
    }
    
    func btnPressed(sender: UIButton) {
        
        let strIDFV = UIDevice.currentDevice().identifierForVendor?.UUIDString
        
        print("Vendor = \(strIDFV!)")
        
        var strIDFA : String = "No IDFA"
        
        if ASIdentifierManager.sharedManager().advertisingTrackingEnabled {
            strIDFA = ASIdentifierManager.sharedManager().advertisingIdentifier.UUIDString
        }
        print("IDFA = \(strIDFA)")
    }

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

}

Results

An iPad and an iPhone are tested. The two devices are with different IDFVs and IDFAs.
The IDFV and IDFA are the same for different apps on the same iPhone.
If the "Limit Ad Tracking" option is turned on, there is no IDFA.
If the "Limit Ad Tracking" option is turned off, the IDFA is changed.
If the Advertising Identifier is reset, the IDFA is also changed.

Here are the results of print logs:

iPad

IDFV = C9A9xxxx-xxxx-xxxx-xxxx-xxxxxxF155E6
IDFA = 2544xxxx-xxxx-xxxx-xxxx-xxxxxxFE4B56

iPhone
App #1

IDFV = 912Dxxxx-xxxx-xxxx-xxxx-xxxxxx2332C6
IDFA = CEF5xxxx-xxxx-xxxx-xxxx-xxxxxx8D9C15

App #2

IDFV = 912Dxxxx-xxxx-xxxx-xxxx-xxxxxx2332C6
IDFA = CEF5xxxx-xxxx-xxxx-xxxx-xxxxxx8D9C15

Limit Ad Tracking
Select Settings -> Privacy -> Advertising -> Limit Ad Tracking -> On

IDFV = 912Dxxxx-xxxx-xxxx-xxxx-xxxxxx2332C6
IDFA = No IDFA

Select Settings -> Privacy -> Advertising -> Limit Ad Tracking -> Off

IDFV = 912Dxxxx-xxxx-xxxx-xxxx-xxxxxx2332C6
IDFA = C9ECxxxx-xxxx-xxxx-xxxx-xxxxxxF86A1B

Reset Advertising Identifier
Select Settings -> Privacy -> Advertising -> Reset Advertising Identifier -> Reset Identifier

IDFV = 912Dxxxx-xxxx-xxxx-xxxx-xxxxxx2332C6
IDFA = E8D2xxxx-xxxx-xxxx-xxxx-xxxxxx4A625C

References:
Does this app use the Advertising Identifier (IDFA)? - AdMob 6.8.0
Apple Developers Must Now Agree To Ad Identifier Rules Or Risk App Store Rejection
NSUUID / CFUUIDRef / UIDevice -unique​Identifier / -identifier​For​Vendor
The Developer’s Guide to Unique Identifiers
Using CoreTelephony framework to get IMEI and IMSI on iOS 7

Tuesday, December 29, 2015

Connect iOS device to MySQL database on a server (iOS Part)

This post shows how to create a PHP+MySQL service on Raspberry Pi for iPhone / iPad to access using the HTTP POST method.

Raspberry Pi Part


The server service is created on a Raspberry Pi. See this:

iOS Part

1. Create a project with Xcode. Edit ViewController.swift as:

import UIKit

class ViewController: UIViewController {
    
    let urlString = "http://192.168.xx.xx/iosmysql.php"
    
    var labelRead : UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton(frame: CGRectMake(70, 100, 200, 20))
        button.setTitle("Read Database", forState: UIControlState.Normal)
        button.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        button.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        button.addTarget(self, action: "btnPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(button)
        
        let label = UILabel(frame: CGRectMake(50,200,250,30))
        label.text = "Data from Server:"
        label.textAlignment = NSTextAlignment.Center //Align to center
        view.addSubview(label)
        
        labelRead = UILabel(frame: CGRectMake(50,250,300,300))
        labelRead.numberOfLines = 0 //Multi-lines
        
        //Use a font with fixed width for layout.
        labelRead.font = UIFont(name: "Courier", size: labelRead.font.pointSize-2)
        view.addSubview(labelRead)
    }
    
    func btnPressed(sender: UIButton) {
        
        let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
        request.HTTPMethod = "POST"
        
        let postString = "username=myusername&password=mypassword&tablename=fruit"
        request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
        
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
            if (error == nil) {
                
                let result = NSString(data: data!, encoding: NSUTF8StringEncoding)!
                
                //Execute UI code immediately
                dispatch_async(dispatch_get_main_queue(), {
                    self.labelRead.text = result as String
                })
            } else {
                print(error)
            }
        })
        task.resume()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

In this example, parameters sent to PHP using the HTTP POST method are username, password, and tablename of the MySQL database on Raspberry Pi.

2. Build the code. This 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 URL used here is an insecure HTTP URL instead of a secure HTTPS URL. To allow HTTP connections, modify Info.plist by adding:




3. Build and Run the code again after modifying Info.plist. Pressing the "Read Database" button gives the result:


This result is the same as the result obtained using MySQL monitor commands on Raspberry Pi:


References:
Connect iOS device to HTTP GET/POST PHP service (Raspberry Pi Part) (iOS Part)
Access and show content of a database in MySQL with PHP

Friday, December 25, 2015

Mac Technique: Change the language

1. Select the Apple logo -> System Preferences



2. Select Language & Region

3. Select + sign to add a new language.



4. Select a new language and click Add.


5. Choose to use English or the language just added.


6. Confirm that the new language you added is the primary language. Click the red close button.

7. Restart your computer.



The language should be changed after a reboot.

Thursday, December 24, 2015

Mac Technique: How to configure an IP address as a custom domain name for a browser

This post is about a Mac technique, not Swift programming.

If an Apache HTTP server is installed on a Raspberry Pi, entering its IP address in a Safari / Chrome browser on a Mac in the same local network of the Raspberry Pi gives this result:



Or the result of a PHP page:



However, is it possible to use a custom domain name to access the above 192.168.xx.xx IP address with a personal Mac?

Yes. The solution is easy. And the good news is: there's no need to pay for the custom URL!!

Just modify the hosts file of Mac using this terminal command:

sudo nano /private/etc/hosts

And add this line:

192.168.xx.xx     yourdomain.com



Save this hosts file and enter your custom domain name in the browser:


This solution also works with PHP.


You may give your server an awesome domain name you like and then you no long need to enter the numbers of your server's IP address in the browser!

Note the different hosts file path:
Mac:                 /private/etc/hosts
Raspberry Pi:   /etc/hosts

References:

App Submission with encryption:
Does my application “contain encryption”? (Stack Overflow)
Apple iTunes export restrictions on apps

Wednesday, December 23, 2015

Connect iOS device to HTTP GET/POST PHP service (iOS Part)

This post shows how to make an iPhone / iPad connect  a PHP service on Raspberry Pi using HTTP GET / POST methods.

Raspberry Pi Part

Follow the instructions in:

Connect iOS device to HTTP GET/POST PHP service (Raspberry Pi Part)

iOS Part

1. Modify ViewController.swift as below:


import UIKit

class ViewController: UIViewController {
    
    let urlString = "http://192.168.xx.xx/ios.php"
    
    var labelRead : UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let buttonGet = UIButton(frame: CGRectMake(70, 100, 200, 20))
        buttonGet.setTitle("GET", forState: UIControlState.Normal)
        buttonGet.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonGet.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonGet.addTarget(self, action: "getPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(buttonGet)
        
        let buttonPost = UIButton(frame: CGRectMake(70, 200, 200, 20))
        buttonPost.setTitle("POST", forState: UIControlState.Normal)
        buttonPost.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonPost.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonPost.addTarget(self, action: "postPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(buttonPost)
        
        let label = UILabel(frame: CGRectMake(50,300,250,30))
        label.text = "Message from Server:"
        label.textAlignment = NSTextAlignment.Center //Align to center
        view.addSubview(label)
        
        labelRead = UILabel(frame: CGRectMake(50,350,250,100))
        labelRead.textAlignment = NSTextAlignment.Center
        labelRead.numberOfLines = 0 //Multi-lines
        view.addSubview(labelRead)
    }
    
    func getPressed(sender: UIButton) {
        
        let component = NSURLComponents(string: urlString)
        component?.queryItems = [NSURLQueryItem(name: "language", value: "Swift"), NSURLQueryItem(name: "product", value: "iPhone")]

        let request = NSMutableURLRequest(URL: (component?.URL)!)
        request.HTTPMethod = "GET"
        
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
            if (error == nil) {
                
                let result = NSString(data: data!, encoding: NSUTF8StringEncoding)!
                
                //Execute UI code immediately
                dispatch_async(dispatch_get_main_queue(), {
                    self.labelRead.text = result as String
                })
                
            } else {
                print("Error")
            }
        })
        task.resume()
    }
    func postPressed(sender: UIButton) {
        
        let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
        request.HTTPMethod = "POST"

        let postString = "name=Peter&age=20"
        request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
        
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
            if (error == nil) {
                
                let result = NSString(data: data!, encoding: NSUTF8StringEncoding)!
                
                //Execute UI code immediately
                dispatch_async(dispatch_get_main_queue(), {
                    self.labelRead.text = result as String
                })
            } else {
                print(error)
            }
        })
        task.resume()
    }

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

}


In this example, parameters sent to PHP using the HTTP GET method are language and product, while parameters using the POST method are name and age.

2. Build the code. This 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 URL used here is an insecure HTTP URL instead of a secure HTTPS URL. To allow HTTP connections, modify Info.plist by adding:



More information about ATS:

Open an insecure internet image
Working with Apple's App Transport Security
Apple will require HTTPS connections for iOS apps by the end of 2016

3. Build and Run the code again after modifying Info.plist. The results:

GET:


POST:


Saturday, December 19, 2015

SLServiceTypeTwitter - Tweet from an iOS App

I have posted a tweet with the following code:

import UIKit
import Social

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton(frame: CGRectMake(70, 100, 200, 20))
        button.setTitle("twitter", forState: UIControlState.Normal)
        button.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        button.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        button.addTarget(self, action: "twitterButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(button)
        
    }
    
    func twitterButtonPressed(sender: UIButton) {
        if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter) {
            let myTwitterController = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
            myTwitterController.setInitialText("First tweet from my iOS app. http://studyswift.blogspot.com")
            presentViewController(myTwitterController, animated: true, completion: nil)
        } else {
            
            let alertController = UIAlertController(title: "", message: "Please login to Twitter.", preferredStyle: UIAlertControllerStyle.Alert)
            alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: { action in
                
                //Open Settings
                let urlSettings = NSURL(string: UIApplicationOpenSettingsURLString)
                UIApplication.sharedApplication().openURL(urlSettings!)
            }))
            
            self.presentViewController(alertController, animated:true, completion:nil)
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

Strangely, this code worked without the Twitter app installed on my iPad. After the Twitter app was installed, the tweet  was no longer posted successfully, but stored in the drafts folder of the Twitter app.