willAnimateRotationToInterfaceOrientation: duration: deprecated

After iOS 8 willAnimateRotationToInterfaceOrientation is no longer supported. After some experimenting I found that a drop-in replacement for:


- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {

     ... code

} 

is


- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
     {

     ... code

     } completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
     {

     }];
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}

In case it isn’t clear, replace the method and then after your existing code, paste in the completion code and call super.

statusBarOrientation deprecated

My apps use code to put objects on the screen and the layout of my apps changes depending on the orientation of the device. I had been using statusBarOrientation to determine the orientation, but it as been deprecated as of iOS 9. It still works in iOS 11 but since I have to update my apps anyway for iPhone X, I decided that now would be as good a time as any to update the code.

I believe the reason for the deprecation is that now apps can appear in a sidebar, so they don’t have a status bar. I have not enabled that behaviour in my apps, so it doesn’t affect me. The recommended way to get the orientation is to look at the screen size.

I had been using this line at the top of each file where I needed to do something depending on orientation.


 UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;

I use a utilities class for things that I use across classes, so I decided to put it there so I could change it easily if I needed to at some point in the future.


+ (NSString *) orientation  {

    CGSize screenSize = [UIScreen mainScreen].bounds.size;
    NSString *deviceOrientation = @"Portrait";
    if (screenSize.height < screenSize.width) {
        deviceOrientation = @"Landscape";
    }
    return deviceOrientation;
}

The replacement code in each class is:


NSString *orientation = [Utilities orientation];

The enum UIInterfaceOrientation has not been deprecated, so I could have returned one of the values and not had to change any other code, but the conditionals are messy and hard to read,


if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)

so I decided to return a string instead to make the code easier to read, e.g.


if ( [orientation isEqualToString:@"Portrait"] ) {

iOS 11 UIBarButtonItem images not sizing

The answer to my question was hinted at in this question on StackOverflow, so I think the answer is to disable autolayout for my UIToolbar view. But I was not sure how to do that with programmatically designed views. Fortunately, someone else knew how to do it.

The code that is said to work for views is


cButton.translatesAutoresizingMaskIntoConstraints = YES;

But I’m not sure if it applies to my code since UIToolbar doesn’t inherit from UIView.

I have lots of small images that I use in my games that are different sizes depending on the device and orientation. Rather than having lots of different images, and adding new ones when Apple introduces new devices, I decided to make one 160×160 image fore each and then resize it when it is used. This worked fine from iOS 4 – iOS 10 but fails in iOS 11.

The code is pretty straightforward:


// Get the image
NSString *pictFile = [[NSBundle mainBundle] pathForResource:@"Correct" ofType:@"png"];
UIImage *imageToDisplay = [UIImage imageWithContentsOfFile:pictFile];
UIImage *cImage  = [UIImage imageWithCGImage:imageToDisplay.CGImage scale:[UIScreen mainScreen].scale orientation:imageToDisplay.imageOrientation];

UIButton *cButton = [UIButton buttonWithType:UIButtonTypeCustom];
[cButton setImage:cImage forState:UIControlStateNormal];
[cButton setTitle:@"c" forState:UIControlStateNormal];

//set the frame of the button to the size of the image
cButton.frame = CGRectMake(0, 0, standardButtonSize.width, standardButtonSize.height);

//create a UIBarButtonItem with the button as a custom view
c = [[UIBarButtonItem alloc] initWithCustomView:cButton];

This is what it looks like pre11. The bar button items have been resized and fit nicely in the bottom bar. Note I reduced the size of the checkmark by 50% just to make sure I was looking at the correct code and that it behaves like I expect.

iOS 10 version of buttons

Here’s what they look like in the simulator for Xcode 9.0 GM and iOS 11. Note that the top row of buttons resize correctly but the bottom row expand to fill the space allocated for the tab bar. The same behaviour happens on iPads as well and various devices.

iOS 10 version of buttons

The answer provided by FallStreak is:
BarButtonItem (iOS11\xCode9) uses autolayout instead of frames. Try this (Swift):


if #available(iOS 9.0, *) {
    cButton.widthAnchor.constraint(equalToConstant: customViewButton.width).isActive = true
    cButton.heightAnchor.constraint(equalToConstant: customViewButton.height).isActive = true
}

The Objective C version that I worked out is:


if (@available(iOS 9, *)) {
     [cButton.widthAnchor constraintEqualToConstant: standardButtonSize.width].active = YES;
     [cButton.heightAnchor constraintEqualToConstant: standardButtonSize.height].active = YES;
}

Learning Swift

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

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