Tag Archives: ios

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.

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

Logging during development with Apple Watch hardware

Inserting breakpoints or logging statements (such as NSLog) is relatively straightforward with an iPhone-only iOS app. But how do you log from the WatchKit Extension (aka Watch app)?

The good news is that logging isn’t filled with many complicated steps. The bad news is that the logging works intermittently.

  1. Add your NSLog statements in your WatchKit Extension. This is probably your InterfaceController.m
  2. Run the watch app on iOS Device + watchOS Device (in the WatchKit App scheme)
  3. Select Debug > Attach to Process > (click on your watch app name)
  4. Profit! You should see your NSLogs when they are triggered in the app lifecycle in Xcode

debug

If logging isn’t working, try typical Xcode debug steps such as:

  • deleting the app on your phone & re-running it in Xcode
  • restarting the phone and/or watch devices
  • clean Xcode (cmd + shift + k) and re-run the app
  • quit & restart Xcode

Apple GameCenter Implementation Strategy

Having worked on my first app that implements GameCenter, I had a lot of questions about the process. The main question was how do I implement GameCenter into my app? Do I need to pick the right application template when creating my Xcode project? Do I need to use SpriteKit? Etc

The good news is that GameCenter can be added on after the rest of your application is ready. You should focus on your game mechanics, game engine, user friendliness, etc. After you are satisfied with the core experience, you can add GameCenter (leaderboard, achievements, and other features) afterwards. You can choose any application template and you don’t have to use SpriteKit.

In my app, I had both a phone app & a watch app. You will need to implement GameCenter using Apple’s GameKit Framework. GameKit is currently only for the iPhone, so GameCenter logic needs to be called in the Phone app.

I found great Apple documentation with the same advice:

Here’s a reasonable process to follow when designing a game that supports Game Center:

  1. Decide which Game Center features you plan to support.
  2. Define your game mechanics, keeping in mind the requirements and limitations of Game Center.
  3. Implement your game engine, ignoring Game Center for now. Focus on developing the other aspects of your game, such as your game engine and gameplay. You do this to avoid needing to create an iTunes Connect record earlier than you need to.
  4. [implement Game Center]

As with any app, focus on nailing the key user experience. Then worry about implementing/integrating GameCenter in your app.

Setup Apple Watch for Development Guide

While working in Xcode and running my watch app on my actual hardware watch for the first time, I ran into this error on my watch the first time: “Failed to install [app], error: Application Verification Failed.” This stack overflow answer provides the solution, but I wanted help illustrate the steps I took to fix the error. Disclaimer: this worked for me, but there are probably more optimal ways of fixing the error.

  1. In Xcode, get the UDID of your Apple Watch (WIndow > Devices). The UDID is labeled “Identifier” and you can double click on the Identifier device hash to select & copy it.
    1
  2. Visit the Apple Developer Portal at https://developer.apple.com/devcenter/ios/index.action and click on “Certificates, Identifiers & Profiles” in the right sidebar.
    2
  3. Click on Devices > All in the left sidebar.
    3
  4. Click on the Plus Sign (+) in the top right.
    4
  5. In Register Device, provide a Name (whatever you want) and your watch UDID (from step 1 above).
    5
  6. Submit the form to register your watch device.
  7. In “Certificates, Identifiers & Profiles”, locate your .watchkitextension Provisioning Profile for your app. Select & download this profile.
    7
  8. Locate your downloaded profile file on your computer & double click the Provisioning Profile.
  9. Restart Xcode.
  10. Build your project and you will encounter a iOS Development Certificate alert.
    10
  11. Warning, this step may be dangerous (proceed at your own risk). This worked for me. Click on “Revoke and Request”. This will revoke your current certificate and request a new one. You will probably get an email notifying you that “Your Certificate Has Been Revoked”.
  12. Run your Xcode project. Your watch app should now load the development build on your actual watch hardware.

About the author: Rex Feng enjoys iOS development and has released Pomodoro Pro for the iPhone & Apple Watch. You can follow him @rexfeng.