Nikola Brežnjak blog - Tackling software development with a dose of humor
  • Home
  • Daily Thoughts
  • Ionic
  • Stack Overflow
  • Books
  • About me
Home
Daily Thoughts
Ionic
Stack Overflow
Books
About me
  • Home
  • Daily Thoughts
  • Ionic
  • Stack Overflow
  • Books
  • About me
Nikola Brežnjak blog - Tackling software development with a dose of humor
iOS

How to run your app on the iPhone with the newest OS version via an older version of Xcode?

In this short post, I’ll explain how to run your app via an older version of Xcode (9.2 in my particular case) on your iPhone that has the newest OS (11.4.1 in my case at the time of this writing).

Say you update your iPhone OS to the newest version, and you open the project in an older version of Xcode and try to run it. You will most probably get an error similar to this (version numbers may vary):

Could not locate device support files on iPhone X (11.1) with Xcode (9.2 9C32c)

The problem has been addressed in these two StackOverflow posts:

  • Xcode not supported for iOS 11.3 by Xcode 9.2 needed 9.3
  • How to fix Xcode error: Could not locate device support files on iPhone X (11.1) with Xcode Beta (9.2 9C32c)

The solution is basically to go on this Github project and download the device support files and place them in the DeviceSupport folder of your Xcode app.

You can get to the DeviceSupport folder by going into your Applications, find the Xcode app, right-click on it and select Show Package Contents. Then navigate to Contents > Developer > Platforms > iPhoneOS.platform > DeviceSupport and paste the contents of the downloaded file there.

All you have to do now is to restart Xcode and run the app again.

⚠️ Now here’s the potentially tricky party; you may realize that even though you’ve downloaded the correct support files from Github (and restarted Xcode) the Xcode is still not happy with it. Say for example you’ve downloaded 11.4 (15F79) but after restarting and running again you get an error mentioning something like 11.4.1 (15G77). The solution there is to just rename the folder to that, restart Xcode and voila, it works!

Hope this helps someone ?

iOS

How to create a native iOS app that can read Search Ads Attribution API information

TL;DR

In this post, I’ll show you how easy it is to create a native iOS app that can read Search Ads Attribution API information.

You can check out the full working code on Github.

What is Search Ads App Attribution?

From Apple’s documentation:

Search Ads App Attribution enables developers to track and attribute app downloads that originate from Search Ads campaigns. With Search Ads App Attribution, iOS developers have the ability to accurately measure the lifetime value of newly acquired users and the effectiveness of their advertising campaigns.

Steps to recreate this yourself

  • Create a new blank project in Xcode
  • Add a label on the Main.storyboard:

  • Create an Interface Builder Outlet for this label and name it searchAdsInfoLabel. For those not familiar with iOS development, this is done by pressing Ctrl and dragging the label into the ViewController.swift file.

  • Once done, you should see this in your ViewController.swift file:
import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var searchAdsInfoLabel: UILabel!

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

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
  • Next, import iAd to your project:

  • import iAd in your ViewController.swift file: import iAd.

Add the following function to the ViewController.swift file:

func getSearchAdsInfo() {
    ADClient.shared().requestAttributionDetails({ (attributionDetails, error) in
        if error == nil {
            for (type, adDictionary) in attributionDetails! {
                print(type);
                print(adDictionary);

                let attribution = adDictionary as? Dictionary<AnyHashable, Any>;

                let iadAdgroupId = attribution?["iad-adgroup-id"] as? String
                let iadAdgroupName = attribution?["iad-adgroup-name"] as? String
                let iadAttribution = attribution?["iad-attribution"] as? String
                let iadCampaignId = attribution?["iad-campaign-id"] as? String
                let iadCampaignName = attribution?["iad-campaign-name"] as? String
                let iadClickDate = attribution?["iad-click-date"] as? String
                let iadConversionDate = attribution?["iad-conversion-date"] as? String
                let iadCreativeId = attribution?["iad-creative-id"] as? String
                let iadCreativeName = attribution?["iad-creative-name"] as? String
                let iadKeyword = attribution?["iad-keyword"] as? String
                let iadLineitemId = attribution?["iad-lineitem-id"] as? String
                let iadLineitemName = attribution?["iad-lineitem-name"] as? String
                let iadOrgName = attribution?["iad-org-name"] as? String

                self.searchAdsInfoLabel.text = "iad-adgroup-id: \(iadAdgroupId ?? "")\niad-adgroup-name: \(iadAdgroupName ?? "")\niad-attribution: \(iadAttribution ?? "")\niad-campaign-id: \(iadCampaignId ?? "")\niad-campaign-name: \(iadCampaignName ?? "")\niad-click-date: \(iadClickDate ?? "")\niad-conversion-date: \(iadConversionDate ?? "")\niad-creative-id: \(iadCreativeId ?? "")\niad-creative-name: \(iadCreativeName ?? "")\niad-keyword: \(iadKeyword ?? "")\niad-lineitem-id: \(iadLineitemId ?? "")\niad-lineitem-name: \(iadLineitemName ?? "")\niad-org-name: \(iadOrgName ?? "")"
            }
        }
    })
}

Call this function in the viewDidLoad() method.

The full code listing, of the ViewController.swift file should now look like this:

import UIKit
import iAd

class ViewController: UIViewController {
    @IBOutlet weak var searchAdsInfoLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        getSearchAdsInfo();
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func getSearchAdsInfo() {
        ADClient.shared().requestAttributionDetails({ (attributionDetails, error) in
            if error == nil {
                for (type, adDictionary) in attributionDetails! {
                    print(type);
                    print(adDictionary);

                    let attribution = adDictionary as? Dictionary<AnyHashable, Any>;

                    let iadAdgroupId = attribution?["iad-adgroup-id"] as? String
                    let iadAdgroupName = attribution?["iad-adgroup-name"] as? String
                    let iadAttribution = attribution?["iad-attribution"] as? String
                    let iadCampaignId = attribution?["iad-campaign-id"] as? String
                    let iadCampaignName = attribution?["iad-campaign-name"] as? String
                    let iadClickDate = attribution?["iad-click-date"] as? String
                    let iadConversionDate = attribution?["iad-conversion-date"] as? String
                    let iadCreativeId = attribution?["iad-creative-id"] as? String
                    let iadCreativeName = attribution?["iad-creative-name"] as? String
                    let iadKeyword = attribution?["iad-keyword"] as? String
                    let iadLineitemId = attribution?["iad-lineitem-id"] as? String
                    let iadLineitemName = attribution?["iad-lineitem-name"] as? String
                    let iadOrgName = attribution?["iad-org-name"] as? String

                    self.searchAdsInfoLabel.text = "iad-adgroup-id: \(iadAdgroupId ?? "")\niad-adgroup-name: \(iadAdgroupName ?? "")\niad-attribution: \(iadAttribution ?? "")\niad-campaign-id: \(iadCampaignId ?? "")\niad-campaign-name: \(iadCampaignName ?? "")\niad-click-date: \(iadClickDate ?? "")\niad-conversion-date: \(iadConversionDate ?? "")\niad-creative-id: \(iadCreativeId ?? "")\niad-creative-name: \(iadCreativeName ?? "")\niad-keyword: \(iadKeyword ?? "")\niad-lineitem-id: \(iadLineitemId ?? "")\niad-lineitem-name: \(iadLineitemName ?? "")\niad-org-name: \(iadOrgName ?? "")"
                }
            }
        })
    }
}

After you run the project (make sure you run it on your device, as you won’t get any data back on the simulator) you should get some stub data that Apple returns:

Conclusion

If you’re utilizing Apple’s Search Ads, then I hope this helped you see how easy it is to fetch Search Ads App Attribution information in your app.

Of course, once you get the data, you should send this to your server for saving and further analysis.

How to create a #native #iOS app that can read Search Ads Attribution API information #SearchAdsAttribution https://t.co/6HDQBtzmM7

— Nikola Brežnjak (@HitmanHR) September 10, 2017

iOS

How to use fastlane pem to automatically generate and renew your VoIP push notification profiles

In the previous tutorial, I showed you how easy it is to use fastlane pem to automatically generate and renew your push notification profiles.

In this tutorial, I’ll do the same thing, but for VoIP notifications. I found the solution in this Github issue.

In your project’s root folder create a new folder called fastlane.

Then, create a file called Fastfile in the fastlane folder.

Put the following content into Fastfile:

lane :voip_cert do
    app_identifier = "com.nikola.mygreatapp"
    apple_id = "[email protected]"
    output_path = "."
    p12_password = "password"

    UI.important "Creating a new push certificate for app '#{app_identifier}'."

    Spaceship.login(apple_id)
    Spaceship.client.select_team
    csr, pkey = Spaceship::Certificate.create_certificate_signing_request
    cert = Spaceship::Certificate::VoipPush.create!(csr:csr,bundle_id:app_identifier)

    x509_certificate = cert.download
    certificate_type = "voippush"
    filename_base = "#{certificate_type}_#{app_identifier}"

    p12_cert_path = File.join(output_path, "#{filename_base}.p12")
    p12 = OpenSSL::PKCS12.create(p12_password, certificate_type, pkey, x509_certificate)
    File.write(p12_cert_path, p12.to_der)
    UI.message("p12 certificate: ".green + Pathname.new(p12_cert_path).realpath.to_s)

    x509_cert_path = File.join(output_path, "#{filename_base}.pem")
    File.write(x509_cert_path, x509_certificate.to_pem + pkey.to_pem)
    UI.message("PEM: ".green + Pathname.new(x509_cert_path).realpath.to_s)
end

Make sure you enter the appropriate app_identifier and apple_id and password (if you’re using it).

Execute the following command from your app’s root directory:

fastlane voip_cert

You should see something like this in the output:

nikola in ~/DEV/TheOnlyAppThatMatters on git:feature/make-app-great-again ✖︎ [06:06:06]
→ fastlane voip_cert
[06:06:06]: Get started using a Gemfile for fastlane https://docs.fastlane.tools/getting-started/ios/setup/#use-a-gemfile
[06:06:06]: Driving the lane 'voip_cert' ?
[06:06:06]: Creating a new push certificate for app 'com.nikola.mygreatapp'.
[06:06:06]: p12 certificate: /Users/nikola/DEV/TheOnlyAppThatMatters/fastlane/voippush_com.nikola.mygreatapp.p12
[06:06:06]: PEM: /Users/nikola/DEV/TheOnlyAppThatMatters/fastlane/voippush_com.nikola.mygreatapp.pem
[06:06:06]: fastlane.tools finished successfully ?

And that’s it! Hope this proves to be helpful for you! ?

How to use fastlane pem to automatically generate and renew your #VoIP push notification profiles https://t.co/AvbKkbhRWD

— Nikola Brežnjak (@HitmanHR) September 7, 2017

iOS

How to use fastlane pem to automatically generate and renew your push notification profiles

This is a short tip which will save you a lot of time!

Ever gone through the tedious task of manually creating and maintaining your push notification profiles for your iOS apps? Tutorials like this one were very useful, but nowadays thanks to fastlane pem, you can do it with a single command.

The pem tool creates new .pem, .cer, and .p12 files to be uploaded to your push server if a valid push notification profile is needed.

To install pem run:

sudo gem install fastlane

Also, make sure you have the latest version of the Xcode command line tools installed:

xcode-select --install

Then, just run fastlane pem and it will ask you information about the app for which you wish to renew the certificates.

You can pass parameters like this:

fastlane pem -a com.mygreat.app -u username

If you want to generate a development certificate run:

fastlane pem --development

To get a list of available options run:

fastlane pem --help

?

If you’re looking to generate the same thing but for VoIP push notification, then check out this tutorial.

How to use fastlane pem to automatically generate and renew your #push notification profiles https://t.co/cutGxIDH6Y

— Nikola Brežnjak (@HitmanHR) September 7, 2017

iOS

How to create a native iOS app that can receive VoIP push notifications

TL;DR

In this tutorial, I’ll give you step by step instructions on how to create a native iOS app with Swift (ObjectiveC code also available in the Github repo) that can receive VoIP push notifications sent with Houston, custom PHP script or through Amazon SNS.

This tutorial is largely inspired by this one (one of the few that actually made sense and made the steps manageable). However, I’ve added some not so obvious steps and fixed the issues that I had with the newest versions. Also, I added the link to the finished project on Github for further reference.

VoIP notifications

The official documentation can be found here. Few of the advantages are:

  • app is automatically relaunched if it’s not running when a VoIP push is received
  • device is woken up only when VoIP push occurs (saves battery)
  • VoIP pushes go straight to your app for processing and are delivered without delay
  • app is automatically relaunched if it’s not running when a VoIP push is received

Prerequisite settings

Apple provides us with a framework called PushKit to support using this VoIP push feature. However, we need to configure some additional settings to get this working.

Creating an App ID

In case you don’t already have an app (and consequently an App ID), you need to create one.

First, login to your Apple developer account and access Certificates, Identifier & Profiles:

Next, go to Identifiers->App IDs and then click on the + button.

Two important things to fill out here are App ID Description and so-called Bundle ID (this will most likely be something like com.yourdomain.yourappname):

Although not seen in the screenshots above, I used nikolaVoipTest as Bundle ID. This will be important in the next step.

Generating a VoIP push certificate

To generate a VoIP push certificate you first need to click on the All button in the Certificates section on the left-hand side. Then, click the + button:

On the next page you need to select the VoIP Services Certificate:

and on the one after that you need to select the App ID for which you’re creating this VoIP certificate:

Next, you’ll be presented with instructions on how to create a so-called CSR (Certificate Signing Request) file:

Once you create that file, you’ll select it for upload on the next screen. If everything goes well you’ll be given the certificate which you have to download:

After you download the certificate, open it up, and this should open the Keychain Access application, and you should see the certificate under the My Certificates section:

Creating the app

With all the setting out of our way, we can now start Xcode and create a new Single view application project:

Take special care when setting the Product Name as the Bundle Identifier is set automatically from it. We need to set this to be the same as the Bundle identifier that we’ve set in the steps above.

Setting the appropriate capabilities

To use the VoIP push in the app, we need to turn ON the Background Modes for our app and check few of the checkboxes:

Make sure you select the following options:

  • Audio, Airplay, and Picture in Picture
  • Voice over IP
  • Background fetch
  • Remote notifications

Adding the code

Open AppDelegate.swift and at the top add the import PushKit statement.

Then, in the didFinishLaunchingWithOptions part of the application function make sure you register for notifications like this:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        //Enable all notification type. VoIP Notifications don't present a UI but we will use this to show local nofications later
        let notificationSettings = UIUserNotificationSettings(forTypes: [.Badge, .Sound, .Alert], categories: nil)

        //register the notification settings
        application.registerUserNotificationSettings(notificationSettings)

        //output what state the app is in. This will be used to see when the app is started in the background
        NSLog("app launched with state \(application.applicationState)")

        return true
}

Since we are using the registerUserNotificationSettings method we need to implement it’s delegate callback application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings):

func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {

    //register for voip notifications
    let voipRegistry = PKPushRegistry(queue: dispatch_get_main_queue())
    voipRegistry.desiredPushTypes = Set([PKPushTypeVoIP])
    voipRegistry.delegate = self;
}

In this callback, we register the VoIP notifications since we know that the user has agreed to receive notifications (since this function has been called). We enabled VoIP notifications by declaring the voipRegistry object.

At this point, you will get an error on the voipRegistry.delegate = self; line saying Cannot assign a value of type 'AppDelegate' to type 'PKPushRegistryDelegate!'.

The delegate for voipRegistry is of type PKPushRegistryDelegate which has three methods, two of which are required (didUpdatePushCredentials and didReceiveIncomingPushWithPayload). We have to define a so-called extension of the AppDelegate class. We do that by adding the following code after all the current code in the AppDelegate.swift file:

extension AppDelegate: PKPushRegistryDelegate {

    func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) {

        //print out the VoIP token. We will use this to test the notification.
        NSLog("voip token: \(credentials.token)")
    }

    func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) {

        let payloadDict = payload.dictionaryPayload["aps"] as? Dictionary<String, String>
        let message = payloadDict?["alert"]

        //present a local notifcation to visually see when we are recieving a VoIP Notification
        if UIApplication.sharedApplication().applicationState == UIApplicationState.Background {

            let localNotification = UILocalNotification();
            localNotification.alertBody = message
            localNotification.applicationIconBadgeNumber = 1;
            localNotification.soundName = UILocalNotificationDefaultSoundName;

            UIApplication.sharedApplication().presentLocalNotificationNow(localNotification);
        }

        else {

            dispatch_async(dispatch_get_main_queue(), { () -> Void in

                let alert = UIAlertView(title: "VoIP Notification", message: message, delegate: nil, cancelButtonTitle: "Ok");
                alert.show()
            })
        }

        NSLog("incoming voip notfication: \(payload.dictionaryPayload)")
    }

    func pushRegistry(registry: PKPushRegistry!, didInvalidatePushTokenForType type: String!) {

        NSLog("token invalidated")
    }
}

After adding this extension you will note that the previously mentioned error disappears.

In the first function, we merely output the device token. We will need this token in the next section when we’ll be testing our app with sending VoIP push notifications.

In the second one we ‘act’ on the received VoIP push notification. In this concrete case, we show a local notification if the app is in the background or an alert if we’re in the app. The third function (didInvalidatePushTokenForType) is used for handling when the token is invalidated.

Testing VoIP notifications

We have several options for testing our app. I’ll cover few of them in this section:

  • CLI program called Houston
  • PHP script called SimplePush
  • Amazon SNS

Getting the device token

Run your app in Xcode and make sure you run it on an actual device. The console output should look something like this:

2016-09-19 10:08:53.793
voipTestNikola[1876:2020432]
app launched with state UIApplicationState

2016-09-19 10:08:53.877
voipTestNikola[1876:2020432]
voip token: <40cc4209 d0f3ac25 95a7e937 3282897b 211231ef ba66764c 6fd2befa b42076cb>

Take note of the voip token from the output above (of course, the one from your Xcode debug window and not the one I’ve pasted above ;)), as we’ll need it in the next sections.

Preparing the certificate file

The VoIP certificate file that we’ve downloaded and added to the KeyChain has to be converted to a different file format so that we’ll be able to use it with the tools and services that I’ve listed above.

First, you need to open the KeyChain app on your Mac and then Export (Right click then select Export) the certificate:

You will get a .p12 file.

Now, navigate to the folder where you exported this file and execute the following command:

openssl pkcs12 -in YOUR_CERT.p12 -out VOIP.pem -nodes -clcerts

This will generate VOIP.pem file which we’ll use in the next sections.

Houston

Even though the docs say you can install it simply with gem install houston, you will most likely end up (after some StackOverflow searching) using this command to install it:

sudo gem install -n /usr/local/bin houston

This way you’ll install it to your local bin directory to which you have full rights.

Houston installed one more tool that will help us send the notifications like this:

With Terminal navigate to the folder where you have your certificate, copy the device id from above and execute a command like this:

apn push "<40cc4209 d0f3ac25 95a7e937 3282897b 211231ef ba66764c 6fd2befa b42076cb>" -c VOIP.pem -m "Testing VoIP notifications!"

Please note to change the VOIP.pem to whatever you named the file in the steps above.

You should get the following output in your terminal:

And, you should see this on your phone in case it was in the foreground:

In case the app was in the background you should see:

Also, I made a short video showcasing how the app behaves when it’s closed and receives a VoIP push (it’s woken up and run).

SimplePush

SimplePush (simplepush.php in the project root directory) is a PHP script that can be found on the net in few various forms and from few various authors. However, the code is less then 50 LOC, so it’s quite simple to understand. We need to set just a few of the config options:

// Put your device token here (without spaces):
$deviceToken = 'deviceToken';

// Put your private key's passphrase here:
$passphrase = 'passphrase';

// Put your alert message here:
$message = 'message';

// Put the full path to your .pem file
$pemFile = 'pemFile.pem';

and then run the script with php simplepush.php.

The only thing to note here is that we need to insert the device token without spaces and without < and > characters.

Amazon SNS

Amazon has really good documentation for preparing everything you need, and you can take a look at it here.

After you have all the prerequisites, you should follow these instructions to create the so-called ‘platform application’. Settings that I’ve used are on the image below:

After this you can add a platform endpoint by pasting your device id (again, as in the PHP example; without spaces and < and > characters).

Finally, you have to select this endpoint and click on the Publish to endpoint button:

Here you can enter some test data like shown below:

After you click on the Publish message you should get a notification on your phone.

Conclusion

In this tutorial you’ve learned how to create a native iOS app with Swift that can receive VoIP push notifications sent with Houston, custom PHP script or through Amazon SNS.

I bolded native for a reason in the sentence above. As you know, I’m a big fan of hybrid apps and Ionic framework in particular.

This post is a part of the recent task that I had to do to create the Cordova VoIP push plugin. To successfully complete that, I first made the native app and then the plugin, since (unfortunately) the plugin wasn’t available.

You can read all about how I created the Cordova plugin for receiving VoIP push notifications in this post.

How to create a #native #iOS app that can receive #VoIP push notifications https://t.co/PFGq7rqXZr

— Nikola Brežnjak (@HitmanHR) September 30, 2016

Recent posts

  • Discipline is also a talent
  • Play for the fun of it
  • The importance of failing
  • A fresh start
  • Perseverance

Categories

  • Android (3)
  • Books (114)
    • Programming (22)
  • CodeProject (35)
  • Daily Thoughts (77)
  • Go (3)
  • iOS (5)
  • JavaScript (127)
    • Angular (4)
    • Angular 2 (3)
    • Ionic (61)
    • Ionic2 (2)
    • Ionic3 (8)
    • MEAN (3)
    • NodeJS (27)
    • Phaser (1)
    • React (1)
    • Three.js (1)
    • Vue.js (2)
  • Leadership (1)
  • Meetups (8)
  • Miscellaneou$ (77)
    • Breaking News (8)
    • CodeSchool (2)
    • Hacker Games (3)
    • Pluralsight (7)
    • Projects (2)
    • Sublime Text (2)
  • PHP (6)
  • Quick tips (40)
  • Servers (8)
    • Heroku (1)
    • Linux (3)
  • Stack Overflow (81)
  • Unity3D (9)
  • Windows (8)
    • C# (2)
    • WPF (3)
  • Wordpress (2)

"There's no short-term solution for a long-term result." ~ Greg Plitt

"Everything around you that you call life was made up by people that were no smarter than you." ~ S. Jobs

"Hard work beats talent when talent doesn't work hard." ~ Tim Notke

© since 2016 - Nikola Brežnjak