Author Archives: Rex

How to open phone app from iMessage extension and pass data

This weekend, I spent some time digging into iOS 10 iMessage app extensions.

Creating an iMessage app or sticker pack is relatively easy. Here are a couple of resources for getting started: tutsplus and medium. Apple also has their own example iMessage app.

For a new iMessage only app, you can choose File > New > Project and either ‘Sticker Pack Application’ or ‘iMessage Application’ in Xcode. To add an iMessage extension to your existing containing phone app, you can use File > New > Target and either ‘Sticker Pack Extension’ or ‘iMessage Extension’.

I wanted to understand the current state of user workflow between the iOS (main/phone) app and the iMessage extension app. Apple has it’s work cut out for them. The iMessage App store is an awkward modal triggered from an individual iMessage conversation. Users seem to have trouble locating the store and managing iMessage apps, so apps are getting bad reviews.

The good news is that you can launch your phone app from the iMessage app.

From the iMessage extension app, you can use this (with your own AppName):

guard let url: URL = URL(string: "AppName://?myParam=myValue") else { return }

self.extensionContext?.open(url, completionHandler: { (success: Bool) in
 
 })

Calling the above code will open the phone app. One issue I ran into (under the extension scheme) is that the phone app will crash in the simulator when opened this way. Xcode shows a SIGKILL since the iMessage app connected to Xcode is quit while the phone app is being opened. This appears to only be a simulator issue.

You can access the URL params in your phone app’s App Delegate:

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {

        return true

    }

The bad news is that you cannot launch your iMessage app from your phone app. Apple must have been short on time, as their usual modus operandi is to launch headline features that are incomplete and hopefully iterate later if their business still cares. Radar(s) have been filed.

When asked, “Is it possible for my app to open the Messages app with my iMessage extension activated?” An Apple Staff member says, “There currently isn’t a way to do this.”

I think opening your iMessage app from your phone app would be an excellent, natural use case. User does something in the containing app, then they want to share that app’s state with a friend using iMessage. Why rely on the user going to Messages, finding your iMessage app in the correct convo, and then recreating app state to send it over?

So to recap, iMessage apps have a lot of potential as they are living apps within iMessages. They can communicate with their containing app. But I have reservations about the UX, including discoverability.

How to transfer photos to Apple TV (4th gen) and use photos as screensaver

Here is a quick guide for saving photos on your 4th generation Apple TV so that you can use them as a screensaver on your Apple TV (without constantly have your computer on for Home Sharing).

  1. On your computer, open iTunes and turn on Home Sharing
    1. File > Home Sharing
    2. If applicable, select ‘Choose Photos to Share with Apple TV…’
  2. On your Apple TV, select the ‘Computers’ app icon from the home screen
    1. In your Library, select Photos & choose your album
    2. Select ‘Set as Screensaver’ in the top right & select ‘Yes’
  3. You’re done

That hopefully wasn’t too complicated to do. I wanted to post this since it wasn’t clear to me from googling if you could save photos to your Apple TV (or you had to always stream via Home Sharing).

As for the Apple TV, it feels like Apple Watch territory. Something that is nice to have, but nowhere near necessary. Their app stores are still early and widespread developer support is uncertain.

Use NSLocalizedString for user facing strings

Just a quick iOS tip I wish I knew earlier. Would’ve saved me some time & headache:

Always wrap user-facing strings with NSLocalizedString.

Even if you don’t plan to localize your app into any other languages, there is immense utility in being able to easily review all of the strings that a user will see. And if localization is in the cards, it’s significantly easier to NSLocalize your strings as you go along the first time, then try to find all of them after-the-fact.

via http://nshipster.com/nslocalizedstring/

The advice is sound. It won’t cost you any time to use NSLocalizedString the first time, but it will help save time if/when you localize.

Visual iOS guide on switching from Chinese to English

I wanted to document for my own future reference how to switch an iPhone (in the iOS Simulator) back to English from Chinese. My Chinese literacy is not very high, so I’m sure this guide will be handy.

Note: follow the red ovals and you will be able to change your iOS language back to English

1

2

3

4

5

6

7

8

Why would I need to do this? I’m working on language localization for different apps, and testing localization in the simulator is often easier than on actual hardware.

iOS Universal iPad Portrait Gotcha

I wanted to share a small tip that reinforces the necessity of on-device (non-simulator) testing.

While finalizing my latest iOS app (universal for both iPhone & iPad), I found an issue through manual QA on an actual iPad. I had only left Portrait checked in the Project > General section of Xcode, but my app was somehow running in landscape mode on the iPad.

general

Confused as to why it was rendering in both landscape & portrait mode on my iPad, I found a handy stack overflow post.

For one reason or another, you have to update your Info.plist to only specify portrait settings for iPads. Below is my Info.plist after I updated it to only target Portrait mode.

info_plist

It’s confusing as to why the Project General section’s Device Orientation is not sufficient to force only Portrait orientations and you have to also update the Info.plist.

Through simulator testing, it’s not likely that I would have caught onto this portrait vs landscape issue. I relied mostly on my primary iPhone and copious amounts of simulator testing for the other iOS universal devices.

For the highest level of quality control, you would need an iPhone 4s, iPhone 5, iPhone 6, iPhone 6+, iPad, and iPad Pro. That’s a lot of devices and I certainly don’t have all of those. Sidenote: if you do have all those devices, you would also be positioned to record App Preview videos for all devices natively (AKA lots of work).

Tip Solver launches

This week, my future-inspired tip calc, Tip Solver, launched on the App Store. Not only does it help you calculate you tip, it also helps you solve for how much you are really tipping.

3

As a tip calc, it’s easy to use and hopefully sleek/easy on the eyes. A key feature of this tip calculator is that it allows you to solve for your tip %. When you adjust the total or tip, you can see how much you actually tipped right away. This comes in handy when you pay $100 instead of $95, and so on.

While it’s customary to go from top to bottom when looking a receipt (for the items ordered, tax, tip, and total lines), I felt that it was better to invert the direction (to use a bottom to top approach). The reason being that your thumbs are often better able to reach the bottom (not the top) of the device. I wanted the most common action (setting the bill amount) to be in the easy to reach thumb zone.

Please check out my app if you have a moment. It’s free (with ads) and works on iPhones & iPads. If you have any feedback, you can let me know at rexfeng@gmail.com

https://itunes.apple.com/us/app/tip-solver-premier-gratuity/id1130814051

iOS Tip Calculators

I am in the midst of wrapping up a new iOS app. Wrapping up an app includes so many things that are oftentimes overlooked when it comes to developing a mobile application. From App Store screenshots, intro video, and description text, there is a lot of room to do it well (or poorly).

My new app is a tip calculator, which is by no means a new idea. The reasons I decided to build a tip calculator was that I wanted 1.) to implement modern usability improvements and 2.) have an app that is visually attractive.

Looking at the App Store, most calculator apps adopt a heavily skeuomorphic style. There is nothing wrong with skeuomorphism (in the context of aiding usability), but I wanted to build a tip calculator that is sleek and does not resemble a calculator.

Below are screenshots from the current top search results for “tip calculator” in the App Store. They are all probably decent apps that work, but I’m posting them as a reference of what the current state of tip calculator apps looks like.

Screen Shot 2016-06-22 at 7.26.17 PM Screen Shot 2016-06-22 at 7.26.41 PM Screen Shot 2016-06-22 at 7.26.56 PM Screen Shot 2016-06-22 at 7.27.05 PM Screen Shot 2016-06-22 at 7.27.20 PM Screen Shot 2016-06-22 at 7.27.49 PM Screen Shot 2016-06-22 at 7.28.03 PM Screen Shot 2016-06-22 at 7.28.09 PM

Freeing up disk space as an iOS Developer

As an iOS Developer, I often have trouble updating to the latest version of Xcode since my 128GB MacBook Air keeps filling up.

Common culprits include ~/Library/Developer/Xcode/DerivedData and ~/Library/Developer/Xcode/iOS DeviceSupport.

By clearing out those two folders, I’m able to free up 11GB at this time of writing. For more information on what these folders contain, this was helpful.

Life Perspective

This post takes a more serious, rhetorical tone.

As someone in his 30’s, I’m going to repeat a cliché that I say a lot: time flies. It’s amazing how fast the years go by. You can choose to be a grown up and do grown up things (family, career, etc.), or you can choose to have less responsibilities (more freedom?); but time flies regardless.

One topic that has been weighing heavily on me is my time with family. The family that I’ve taken for granted. You may know what I’m talking about: parents, sibling(s) – the people that you saw all the time when you were a kid. The people that you spent all your time with doing mundane things like watching TV or eating a meal.

As a person living in a different region from my immediate childhood family, it feels really weird for me to see so little of them. The worst part is that even if I do see them, there is no real way to “make the time count”. How do you make your time count anyways? Time flies by and that day or week with them is in the distant past.

I’ve read an interesting piece, The Tail End by Tim Urban, that shares some of my sentiments. By not living near my parents or childhood friends, I’ve got very little % of the time left with them. I’ve already spent most of the time I’ll ever have with them (in the context of humans on Earth).

Urban brings up great takeaways:

1) Living in the same place as the people you love matters. I probably have 10X the time left with the people who live in my city as I do with the people who live somewhere else.

2) Priorities matter. Your remaining face time with any person depends largely on where that person falls on your list of life priorities. Make sure this list is set by you—not by unconscious inertia.

3) Quality time matters. If you’re in your last 10% of time with someone you love, keep that fact in the front of your mind when you’re with them and treat that time as what it actually is: precious.

The more I think about this topic, the more paralyzed I feel. Not in a literal sense, but more in a existential sense. How do I make the most of my time here?

To use a shoddy example: when I travel to a new place, I want to experience “all the things” and feel like I’ve done it all. Which is obviously impossible for any place that’s not super, super tiny. What I end up doing is walking around streets arbitrarily, take a bunch of bad pictures. This is a brute force / high level strategy to see a little of everything, but without any depth. I feel like I am not experiencing everything to its potential, and this feels like FOMO (fear of missing out).

I don’t have any satisfying answers to making the best use of our precious time. There is always a sense of FOMO in a world with endless choices. All I can do is prioritize between what I need to do and what I want to do. Everything else will be left behind, but that’s alright because ain’t nobody got time for that.

Dynamic Infinite UIPickerView Scrolling Example

In iOS, I’ve seen a lot of examples of creating a UIPickerView with a hardcoded array of strings. This may be necessary if your data set is a list of strings (such as USA states). For numbers, there’s a much easier way to dynamically generate each row’s title.

To start off with, here’s a simple, programmatic UIPickerView implementation that includes the numbers (as strings) 0 through 9:

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

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

    var pickerView: UIPickerView = UIPickerView()
    var pickerDataSource = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
    
    func setupPicker() {
        pickerView.dataSource = self
        pickerView.delegate = self
        
        pickerView.frame = CGRectMake(0, 0, view.frame.width, 300)
        view.addSubview(pickerView)
    }
    
    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return pickerDataSource[row]
    }
    
    // columns count
    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
        return 1
    }
    
    // rows count
    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return pickerDataSource.count
    }
}

The key piece is the pickerDataSource that gives us our data source. It is an array literal, and in a worst case programming scenario, one can imagine a very, very long hardcoded array of looping “0” – “9”. A long hardcoded array works, but is not optimal for many reasons.

The code above looks like this in the simulator:

manual_ss

We can improve and change this in many ways. We are going to update the code to operate without using a hardcoded array literal. Also, the user has to scroll up (or down) for a very, very long time before they hit the end of the picker. When their scrolling stops, we will move the position of the selected row back to the middle, so that when they scroll again, there is a long way to go before they get to the top (or bottom).

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
    
    var pickerView: UIPickerView = UIPickerView()
    let pickerDataSize = 100_000
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupPicker()
    }
    
    func setupPicker() {
        pickerView.dataSource = self
        pickerView.delegate = self
        
        pickerView.frame = CGRectMake(0, 0, view.frame.width, 300)
        view.addSubview(pickerView)
        
        // set the picker to the middle of the long list
        pickerView.selectRow(pickerDataSize/2, inComponent: 0, animated: false)
    }
    
    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return String(row % 10)
    }
    
    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        // do something with the resulting selected row
        
        // reset the picker to the middle of the long list
        let position = pickerDataSize/2 + row
        pickerView.selectRow(position, inComponent: 0, animated: false)
    }
    
    // columns count
    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
        return 1
    }
    
    // rows count
    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return pickerDataSize
    }
}

In this 2nd code snippet for our ViewController, we have created a large number, pickerDataSize, that tells our device how many rows there are. The neat thing is that we don’t need to create an array of 100K items. When the UIPickerViewDelegate’s titleForRow method is called, it uses the row to dynamically return a string for that given row. Progress!

As a bonus, I went ahead and added a call to pickerView.selectRow in the didSelectRow function. When the user finishes selecting a row, this call will silently (animated: false) move the selected row back to the middle of the rows. This will maintain the illusion of infinite scrolling. Success!

Here we can see the updated, dynamic picker view:

dynamic_ss