Getting your framework version in run-time

Xcode 13 introduced a new behavior that breaks framework versions: When your clients upload their app, Apple changes the CFBundleShortVersionString of all frameworks to match that of the app.
(See this StackOverflow thread for example)

One way to avoid that is to hard-code your framework version. But if you you want to keep using the MARKETING_VERSION build setting, you can use the following hack.

Create a version variable for your framework, for example:

public class MyFramwork: NSObject {
    @objc static var version: String = ""
}

Then include this Objective-C file in your framework:

#import <MyFramwork/MyFramwork-swift.h>

#define PROCESSOR_STRING(x) PRE_PROCESSOR_STRING_LITERAL(x)
#define PRE_PROCESSOR_STRING_LITERAL(x) @#x

@interface MyFramwork(version)
@property (class) NSString *version; // to access internal property
@end;

@interface MyFramworkLoader: NSObject
@end

@implementation MyFramworkLoader

#ifdef MARKETING_VERSION
+ (void)load {
    dispatch_async(dispatch_get_main_queue(), ^{
        MyFramwork.version = PROCESSOR_STRING(MARKETING_VERSION);
    });
}
#endif

@end

This will assign the MARKETING_VERSION value that you built you framework with, instead of an Apple-manipulated CFBundleShortVersionString value.

layoutSubviews Considered Reentrant

I had the weirdest bug the other day: rotating the iPhone worked fine in iOS 7, but not in iOS 8. Actually it was weirder: Some UIView instances adjusted themselves perfectly after the rotation, but some had the wrong size (even though they all had the exact same UIView subclass).

After some digging I discovered that layoutSubview was called several times during rotation, and each time the view had a different frame. Moreover, these calls were not happening sequentially! Instead, layoutSubviews was called with frame1, and before returning it was being called with frame2, and before returning form that it was called with frame3 — all for the same UIView.

There wasn’t a lot of code in my layoutSubviews, nothing that takes long. But it was apparently long enough to cause a bug in some instances.

My solution was to schedule a layout update for a split second later:

- (void)layoutSubviews
{
    [super layoutSubviews];
    [self performSelector:@selector(adjustLayout) withObject:nil afterDelay:0.01]; // avoid re-entering with different frame
}

- (void)adjustLayout
{
    if (CGRectEqualToRect(self.bounds, self.oldBounds)) return;
    // layout code here
    self.oldBounds = self.bounds;
}

How to Set The Image Name When Saving to The Camera Roll

In principle, you can’t. But in practice, there is a way… Read on to find out.

iOS names the image IMG_<num> to avoid file name clashes. There is no way to change that from your app.
However, if the user later imports an image to iPhoto, then the metadata of the image will determine its title, description, and keywords.
If the user then exports the image to file, iPhoto can use the title for the name of the exported file. (Instructions for doing that are down below, after the code.)

Here is the code to set the metadata and save the image to the camera roll. The code below gets the image from the camera, but this isn’t necessary – the image can come from anywhere.

#import 
#import 

- (void) imagePickerController: (UIImagePickerController *)picker didFinishPickingMediaWithInfo: (NSDictionary *)info
{
    [self dismissViewControllerAnimated:YES completion:nil];
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    NSMutableDictionary *metadata = info[UIImagePickerControllerMediaMetadata];

    // set image name and keywords in IPTC metadata
    NSString *iptcKey = (NSString *)kCGImagePropertyIPTCDictionary;
    NSMutableDictionary *iptcMetadata = metadata[iptcKey];
    iptcMetadata[(NSString *)kCGImagePropertyIPTCObjectName] = @"Image Title";
    iptcMetadata[(NSString *)kCGImagePropertyIPTCKeywords] = @"some keywords";
    metadata[iptcKey] = iptcMetadata;

    // set image description in TIFF metadata
    NSString *tiffKey = (NSString *)kCGImagePropertyTIFFDictionary;
    NSMutableDictionary *tiffMetadata = metadata[tiffKey];
    tiffMetadata[(NSString *)kCGImagePropertyTIFFImageDescription] = @"Description for image"; // only visible in iPhoto when IPTCObjectName is set
    metadata[tiffKey] = tiffMetadata;

    // save image to camera roll
    ALAssetsLibrary library = [[ALAssetsLibrary alloc] init];
    [library writeImageToSavedPhotosAlbum:image.CGImage metadata:metadata completionBlock:nil];
}

Now the user can import the images to iPhoto and get your programmed title, description, and keywords.

To export the images to files that have the same name as the image title, the user should choose File > Export and then change the File Name field to Use title.

export

Workaround for bug in [NSMutableIndexSet shiftIndexesStartingAtIndex:by:]

The Bug

Shifting an NSMutableIndexSet by a negative number will drop an index in some cases.

Example Code:

NSMutableIndexSet *set = [NSMutableIndexSet indexSetWithIndex:0];
[set addIndex:2];
[set shiftIndexesStartingAtIndex:1 by:-1];
NSLog(@"%@", set);

The set should contain 0-1 but instead contains only 1.

The Reason

NSIndexSet is a series of NSRange-s. If the shift method removes empty space between ranges, than they should become a single unified range. For example, if a set contains the range 1-2 and the range 5-6, and we do

[set shiftIndexesStartingAtIndex:3 by:-2];

then we should get a set with a single range 1-4.

However, the implementation of shiftIndexesStartingAtIndex:by: fails to unify ranges, and also assumes that separate ranges have at least one empty space between them. And so we get a set containing the ranges 1-1 and 3-4.

The Workaround

Luckily, the methods addIndex: and addIndexesInRange: do correctly unify ranges. And so the workaround is to first call one of these methods, and only then shift:

[set addIndexesInRange:NSMakeRange(3, 2)];
[set shiftIndexesStartingAtIndex:3 by:-2];