Archive for the ‘iOS’ Category

Learning Swift

Tuesday, January 17th, 2017

Now that Swift 3 is out and the language is stable, I thought I’d write my next app using it. I watched the first few videos of Paul Hegarty’s CS193P class and then started to read The Swift Programming Language (Swift 3). My first inclination, since this is the replacement for Objective C, was to relate the language features of Swift to those of Objective C. Both languages will have to handle strings and arrays so I started there. It didn’t go as well as I thought it would. An immutable string in Swift is initialized as


let mySwiftString:String = "This is a string."

whereas an immutable string in Objective C is initialized as

NSString *myObjCString = @[This is a string.];

So far so good, but the similarity breaks down quickly.

mySwiftString = "The string now has new content." //  Cannot assign to value: 'myString' is a let constant
myObjCString = "The string now has new content." //  No problem

Change the ‘let’ to ‘var’ for the Swift string and it works fine but then you no longer have an immutable string.

In Objective C, ‘myObjCString’ is an object created by the String Class. Swift strings are represented by the String type. In Swift, strings, arrays, dictionaries, sets, and numbers are value types.
When they are passed around, their value is passed around, not a reference to an object. Not so with Objective C where some of these are passed by value and some by reference. And numbers can be either ‘primitives’ or NSNumbers.

Another big difference is type safety. Swift is a type-safe language so you must explicitly define the type of everything or else the complier will object. Objective C puts the onus on you to make sure that the type of your object is what the code expects.

I quickly decided that trying to fit Swift into my mental model of Objective C wasn’t going to work. Sure they both handle operations, collection types, control flow, and classes, but their basic approach is so different that trying to draw analogies between the two is counter-productive.

Xcode Error: Finder information, or similar detritus not allowed

Wednesday, September 14th, 2016

I haven’t updated my apps lately and I thought I’d see how much work it would be to update to Xcode 8. Well, there is a lot of deprecated code in my apps, since I am still compatible with iOS 5.1.1. I fixed the easy ones but there are still 48 issues left. However, the apps won’t compile now because of this error:
Finder information, or similar detritus not allowed

The error message indicated that the file in question is:
/Users/username/Library/Developer/Xcode/DerivedData/Words-boikejhzvtrlltffksnstzlbjcpf/Build/Products/Debug-iphonesimulator/AppName.app

But that is a package, so the problem must be in a file somewhere in there.

Fortunately, this is an error that others have encountered and there is a solution.

First cd to the above named directory and run the following code to find all of the files in the director that have extended attributes.
ls -al@ *

When I ran it I had a bunch of files that have an @ at the end file permissions section. In my case they are all images—in fact all of them are app icon or launch images. My other images are fine.


-rw-r--r--@ 1 username  staff   153369 Jan 12  2013 AppName LaunchImage-Landscape~ipad.png
  com.flyingmeat.Acorn.lastScale        8
        com.flyingmeat.Acorn.selectedLayerId       36
-rw-r--r--@ 1 username  staff    11122 Mar  9  2013 AppName AppIcon72x72@2x~ipad.png
  com.apple.FinderInfo       32
  com.apple.quarantine       60
-rw-r--r--@ 1 username  staff     3958 Sep  4  2013 ShowMe AppIcon40x40@2x.png
  com.apple.metadata:kMDItemWhereFroms      169

The files for this project are all in the Words directory so I recursively removed the attributes from all of the files. Thanks to cwd at StackOverflow for the info on how to do it. I have tens of thousands of files, so it took a while.

xattr -rc /Words

Deprecated iOS Methods: Rotation Setup

Saturday, June 25th, 2016

I haven’t updated my code for a while because I wanted to continue to support iOS5 on first generation iPads. Since Apple allows older devices to download older versions of the software, and my code in the App Store appears to be stable, I decided to update the existing code and get rid of deprecated code warnings. (I have 56 deprecated method warnings that I need to get rid of.)

First up is shouldAutorotateToInterfaceOrientation. This method, along with shouldAutorotate is sprinkled throughout my code because it was required for each view.

The old code, in the AppDelegate.m file, supported iOS 5-9 and looked like this:


#pragma mark - Handle rotation for different versions of the OS
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return YES;
}

// iOS 6 and above
- (BOOL)shouldAutorotate {
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations {
    
    #ifdef UIInterfaceOrientationMaskAll
        return UIInterfaceOrientationMaskAll;  // iOS6+ method.
    #else
        return 0;
    #endif
}

I replaced it in the AppDelegate.m with this line:


- (NSUInteger)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAll;
}

and removed lots of occurrences of shouldAutorotateToInterfaceOrientation.

“In iOS 6 and iOS 7, your app supports the interface orientations defined in your app’s Info.plist file.” Source

You don’t need to, but because I like to have clean code, I removed these lines in my .plist files:


<key>UISupportedInterfaceOrientations</key>
<array>
    <string>UIInterfaceOrientationPortrait</string>
    <string>UIInterfaceOrientationLandscapeLeft</string>
    <string>UIInterfaceOrientationLandscapeRight</string>
    <string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
    <string>UIInterfaceOrientationPortrait</string>
    <string>UIInterfaceOrientationPortraitUpsideDown</string>
    <string>UIInterfaceOrientationLandscapeLeft</string>
    <string>UIInterfaceOrientationLandscapeRight</string>
</array>

You can remove them with a text editor or using Xcode and deleting Supported interface orientations and Supported interface orientations (iPad).

Next up is willRotateToInterfaceOrientation and willAnimateRotationToInterfaceOrientation

When $30 Billion is a small number

Wednesday, June 17th, 2015

Tim Cook announced that Apple has paid out $30 Billion dollars to app developers. And over 100 Billion apps have been downloaded. For those of you who are not math challenged, I’ll do the division for you. $30,000,000,000/100,000,000,000 = $.3. So the average app generates 30 cents for each app download.

Don’t quit your day job.

NS_DESIGNATED_INITIALIZER warnings

Saturday, April 18th, 2015

I programmatically create several tables and the code has worked fine for years. It did not generate any warnings two weeks ago when I last ran it. I’ve since updated to Xcode 8.3 and 10.10.3 and now get three warnings for each UITableViewController.


Method override for the designated initializer of the superclass '-initWithStyle:' not found.
Method override for the designated initializer of the superclass '-initWithCoder:' not found.
Method override for the designated initializer of the superclass '-initWithNibName:bundle:' not found.

The code to initialize the table is similar for all of my tables:


- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context 
                 withScoreKeeper:(ScoreKeeper *)scorer 
                    withWordList:(WordList *)wordlist {

    self = [super initWithStyle:UITableViewStyleGrouped];

    if (self) {
        _mObjContext = context;
        _scoreKeeper = scorer;
        _wordList = wordlist;
    }
    return self;
}

and the .h looks like this:


@interface SettingsTableViewController : UITableViewController {
    UIPopoverController *popover;

}
    - (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context 
                     withScoreKeeper:(ScoreKeeper *)scorer 
                        withWordList:(WordList *)wordlist NS_DESIGNATED_INITIALIZER;

Here’s what I found out. I am not a programmer, so parts could be mistaken.

When I opened my project for the first time with Xcode 5 and Xcode 6, I was prompted to “Convert to Modern Objective-C Syntax”. (The latest version of Xcode puts it under Edit > Convert > Convert to Modern Objective-C Syntax.) I allowed Xcode to convert everything and understood most of the changes. I understood how NS_DESIGNATED_INITIALIZER works but not why it works. Since I had no compiler errors and everything worked as before, I promptly forgot about it. In the latest version of Xcode, they appear to have updated the compiler and that’s what triggered my warning message.

From Apple’s notes: Adopting Modern Objective C

Using this macro introduces a few restrictions:

The implementation of a designated initializer must chain to a superclass init method (with [super init…]) that is a designated initializer for the superclass.

The implementation of a convenience initializer (an initializer not marked as a designated initializer within a class that has at least one initializer marked as a designated initializer) must delegate to another initializer (with [self init…]).

If a class provides one or more designated initializers, it must implement all of the designated initializers of its superclass.

Here’s what I think happened. First, I got three warning messages because UITableViewController has three designated initializers.


- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

I violated the third restriction because I did not implement all of the designated initializers of the superclass.

Removing the NS_DESIGNATED_INITIALIZER macro from the .h files made the warnings go away.

The question then arises, Do I care that these classes have no designated initializers? Probably not.
First, there are no other initializers in these classes, so I won’t accidentally call the wrong one. Second, I’m not a programmer by training, so when I started writing apps, I used the procedural programming style that I was used to. Until recently, I had never subclassed a class. So I won’t be subclassing this one and there won’t be any subclasses to worry about. Now that I know a bit more about Objective C, it turns out that every class I wrote was a subclass of one of iOS’s classes and that actually explains a bit about why I was getting the errors.

I did not realize that I could create the table view object by calling a method on its superclass. For example, this call works:


SettingsTableViewController *stvc = [[SettingsTableViewController alloc] initWithStyle: UITableViewStyleGrouped];

It works even when I have NS_DESIGNATED_INITIALIZER set. Presumably no other warnings are sent because the compiler is already complaining about not calling a designated initializer of super.

And as long as the views called in the table view do not need any of the objects that were passed in, everything is fine. If a view that is linked to in the table does need one of the objects, then obviously the app crashes.

Since I never want to call anything but the designated initializer, it was suggested that I use NSAssert() to make sure that I don’t call the designated initializers of my superclass, and make all the warnings go away with this code:


- (instancetype)initWithStyle:(UITableViewStyle)style {
    NSAssert(NO, @"%@", @"Tried to implement with initWithStyle");
    self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
    return self;
}

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    NSAssert(NO, @"%@", @"Tried to implement with initWithNibName");
    self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
    return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    NSAssert(NO, @"%@", @"Tried to implement with initWithCoder");
    self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
    return self;
}

Now I get this error in the log when I try to call initWithStyle directly.

*** Assertion failure in -[SettingsTableViewController initWithStyle:], /Users/jscarry/Words/Words/Settings/SettingsTableViewController.m:37

This stackoverflow question has some useful info.

This article explains more about why it is implemented.

Update: I found this article (UITableViewController Designated Initializer Woes) where they have a more complete explanation of the issue and a more complete fix.

Well Golly


Atheism Plus

Buy from Amazon