Monday, 20 October 2014
Tuesday, 14 October 2014
Monday, 29 September 2014
Can multiple (two) persistent stores be used with one object model, while maintaining relations from one to the other?
The answer is yes. @Caleb points to the right resources, but getting it to work is still quite awkward. I thought I'd place a resumé here:
For two NSPersistentStore instances to share the same model, you have to add a configuration to your model, which is a string-named subset of the entities:
In the model, to an entity that belongs to the second store, you add a fetched property (NSFetchedPropertyDescription for googlability). This is somewhat of a very simple stored procedure, and it could look like this:
Then, when you add the stores to your persistent store coordinator, you use the strings for the configuration argument (more info about the options here):
[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:@"ModifyInBackground"
URL:storeURL1
options:options
error:&error]
[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:@"ModifyInMain"
URL:storeURL2
options:options
error:&error]
Finally, when you want to get from the entity in store B to the entity in store A, you trigger the fetched property like you would trigger a fault, just by accessing it.
Note: A fetched property always returns an NSArray, because the predicate you write to establish the link might have multiple results. If you want to get to just one entity, you could place something like this in a wrapper method of your NSManagedObject subclass:
Wallpaper *recordedWallpaper = [record.wallpaper lastObject];
NSPersistentStore instances to share the same model, you have to add a configuration to your model, which is a string-named subset of the entities:NSFetchedPropertyDescription for googlability). This is somewhat of a very simple stored procedure, and it could look like this:configuration argument (more info about the options here):[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:@"ModifyInBackground"
URL:storeURL1
options:options
error:&error]
[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:@"ModifyInMain"
URL:storeURL2
options:options
error:&error]NSManagedObject subclass:Wallpaper *recordedWallpaper = [record.wallpaper lastObject];Sunday, 21 September 2014
UIAlertController Changes in iOS 8
As part of the theme of iOS 8 to make interfaces adaptive there are some major changes to the presentation of view controllers. The new UIPresentationController does a lot of the hard work of animating view controller transitions and adapting to device size changes such as rotation. It also brings some big changes to some old UIKit favourites such as alert views, action sheets, popovers and search bar controllers. This post will be a gentle introduction to this new world by looking at the changes to alert views and action sheets.
UIAlertView - Alerting the Old Way
The last time I wrote about alert views was back in 2011 to describe the UIAlertView changes in iOS 5. The release of iOS 5 brought alert view styles but not much else has changed since then. The code snippet below is all it takes to setup and present an alert view with cancel and OK buttons:
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:@"DefaultStyle"
message:@"the default alert view style"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"OK", nil];
[alertView show];
The introduction of alert view styles in iOS 5 added a limited ability to create custom alerts by setting the alertViewStyle property. This extended the plain default button-only style to allow plain text input, secure text input or even a login and password input alert:
The UIAlertViewDelegate protocol has callback methods for the button actions and also a method (alertViewShouldEnableOtherButton:) called when a text field changes to allow buttons to be dynamically enabled/disabled.
UIAlertController - Adaptive Alerting
In the new adaptive world of iOS 8 the UIAlertController is a functionally identical, block-based replacement for both UIAlertView and UIActionSheet. Switching between an alert or action sheet is done by setting the preferred style when creating the controller.
A Simple Alert
It is interesting to compare the code required to setup a new style alert to the old UIAlertView. The creation of the basic UIAlertController is very similar to creating an UIAlertView (the alertTitle and alertMessage are both NSString’s):
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:alertTitle
message:alertMessage
preferredStyle:UIAlertControllerStyleAlert];
There is no delegate, nor do we initially specify the buttons. Note the third parameter which chooses between the alert and action sheet styles.
You add action buttons by creating an instance of UIAlertAction which you then add to the controller. The UIAlertAction consists of a title string, style and a block to execute when the user selects the action. The three possible choices for the UIAlertActionStyle cover default, cancel and destructive actions. To reproduce the classic cancel/ok action sheet we just need to create and add the two alert actions:
UIAlertAction *cancelAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel action")
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action)
{
NSLog(@"Cancel action");
}];
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"OK", @"OK action")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
NSLog(@"OK action");
}];
[alertController addAction:cancelAction];
[alertController addAction:okAction];
Finally we can present the alert view controller as with any other view controller:
[self presentViewController:alertController animated:YES completion:nil];
The display order for the buttons depends on the order they are added to the alert controller. If you follow the iOS Human Interface Guidelines you should make the default action the right button and the cancel button the left button for a two button alert. You can only have one cancel action, if you add a second you will get a runtime exception:
*** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘UIAlertController can only have one action with a style of UIAlertActionStyleCancel’
Destructive actions
Here is a quick example of the third alert action style for destructive actions. The code is the same as before except that we add a “reset” button instead of the “ok” button:
UIAlertAction *resetAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"Reset", @"Reset action")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action)
{
NSLog(@"Reset action");
}];
[alertController addAction:resetAction];
[alertController addAction:cancelAction];
[self presentViewController:alertController animated:YES completion:nil];
Note that this time the destructive action is added first to make it appear on the left.
Text Input Alerts
The greater flexibility of the UIAlertController means that you no longer need to be constrained by the built-in styles for plain text, secure text or login and password input alert views. We can add an arbitrary number of UITextField objects to the alert and use all of the standard UITextField configuration options. When you add the text field to the alert controller you specify a block that is used to configure the text field.
For example, to recreate the old login and password style alert we can add two text fields and configure them with the appropriate placeholder text and set the password field to use secure text entry:
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:alertTitle
message:alertMessage
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField)
{
textField.placeholder = NSLocalizedString(@"LoginPlaceholder", @"Login");
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField)
{
textField.placeholder = NSLocalizedString(@"PasswordPlaceholder", @"Password");
textField.secureTextEntry = YES;
}];
The values of the text field can be retrieved in the OK action handler:
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"OK", @"OK action")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
UITextField *login = alertController.textFields.firstObject;
UITextField *password = alertController.textFields.lastObject;
...
}];
Things get a little more complicated if we want to reproduce the behaviour of the old UIAlertView delegate method
alertViewShouldEnableOtherButton:. Assume we only want to enable the OK button if the user has entered at least 3 characters in the login field. There is no equivalent delegate method for UIAlertController so we need to add an observer to the login text field. We can do that with the following code snippet in the configuration block:[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField)
{
...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(alertTextFieldDidChange:)
name:UITextFieldTextDidChangeNotification
object:textField];
}];
We need to remove the observer when the view controller is dismissed by adding the appropriate code to the handler block for each of the actions (and anywhere else we may dismiss the alert controller). For example in the okAction block we saw earlier:
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"OK", @"OK action")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
...
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UITextFieldTextDidChangeNotification
object:nil];
}];
Before presenting the alert controller we can disable the OK action:
okAction.enabled = NO;
Then in the notification observer we can check the login text field for content before changing the state back to enabled:
- (void)alertTextFieldDidChange:(NSNotification *)notification
{
UIAlertController *alertController = (UIAlertController *)self.presentedViewController;
if (alertController)
{
UITextField *login = alertController.textFields.firstObject;
UIAlertAction *okAction = alertController.actions.lastObject;
okAction.enabled = login.text.length > 2;
}
}
The alert view is now presented with the OK button disabled unless there are at least three characters in the login text field:
Action Sheet
The action sheet is used when you need to present the user with a set of choices. Unlike the alert view which is always presented as a modal view the presentation of the action sheet depends on the device size. On an iPhone (compact width) the action sheet rises from the bottom of the screen. On an iPad (regular width) an action sheet is always shown in a popover.
The creation of an action sheet is almost identical to an alert, the only difference being the style:
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:alertTitle
message:alertMessage
preferredStyle:UIAlertControllerStyleActionSheet];
You add actions the same way as you do for alerts so I will abbreviate the code to add three actions:
UIAlertAction *cancelAction = ...; // UIAlertActionStyleCancel
UIAlertAction *deleteAction = ...; // UIAlertActionStyleDestructive
UIAlertAction *archiveAction = ...; // UIAlertActionStyleDefault
[alertController addAction:cancelAction];
[alertController addAction:deleteAction];
[alertController addAction:archiveAction];
You cannot add text fields to action sheets, if you try it you will get a runtime exception:
*** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Text fields can only be added to an alert controller of style UIAlertControllerStyleAlert’
If we do nothing more and present this on an iPhone/compact width device it works as expected:
[self presentViewController:alertController animated:YES completion:nil];
The cancel button, if present, is always shown as the bottom of the view regardless of the order it was added to the alert controller. The other actions are shown top to bottom in the order they were added. The iOS Human Interface Guidelines recommend that any destructive action is shown first.
There is a problem with this code when used on an iPad or regular width device it creates a runtime exception:
Terminating app due to uncaught exception ‘NSGenericException’, reason: ‘UIPopoverPresentationController (<_UIAlertControllerActionSheetRegularPresentationController: 0x7fc619588110>) should have a non-nil sourceView or barButtonItem set before the presentation occurs.’
At the time of writing the Apple UICatalog sample code crashes for the same reason when run on an iPad.
As I mentioned before for regular width presentations the action sheet is displayed in a popover. A popover always requires an anchor point which can be a source view or a bar button item. In this case I am using a standard UIButton to trigger the action sheet so I will use it as the anchor point.
A big difference in iOS 8 is that we no longer need to write code to test for the interface idiom. The UIAlertController takes care of adapting to the display environment so we can simply ask it for a popover controller. On an iPhone/compact width device this returns nil. The extra code we need to configure the popover is below:
UIPopoverPresentationController *popover = alertController.popoverPresentationController;
if (popover)
{
popover.sourceView = sender;
popover.sourceRect = sender.bounds;
popover.permittedArrowDirections = UIPopoverArrowDirectionAny;
}
The UIPopoverPresentationController class is also new in iOS 8 and replaces UIPopoverController and for our purposes is functionally equivalent. The action sheet now displays as a popover anchored to the source button:
Note that the UIAlertController is also smart enough to remove the cancel button when using a popover. A user cancels a popover by touching outside of the popover so it is not required.
Dismissing Alert Controllers
Typically the alert controller is dismissed automatically when the user selects an action. It can also be dismissed programmatically, if required, like any other view controller. One common reason can be to remove the alert or action sheet when the app moves to the background. Assuming we are listening for the
UIApplicationDidEnterBackgroundNotification notification we can dismiss any presented view controller in the observer (see the example code for the setup of the observer in viewDidLoad):- (void)didEnterBackground:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UITextFieldTextDidChangeNotification
object:nil];
[self.presentedViewController dismissViewControllerAnimated:NO completion:nil];
}
Note that to be safe we also make sure to remove any text field observers we may have added to the alert controller.
In Summary
A long post but hopefully it will be another three years before I need to write about alert and action sheets again. The old UIAlertView and UIActionSheet classes still work fine in iOS 8 so if you need to target iOS 7 there is no rush to migrate immediately. The AlertController sample Xcode project for this post can be found in my GitHub CodeExamples repository.
Friday, 29 August 2014
iOS UDID,UUID,Vendor identifier,Advertising identifier
Let's say you're making privacy software that also prevents piracy. I mean, it's an obvious idea—someone's going to do it. You're just trying to be that person.
Until recently, it was trivial to uniquely identify devices between application launches, and even across applications: a simple call to
UIDevice -uniqueIdentifier, and you were all set.
However,
UIDevice -uniqueIdentifier was deprecated in iOS 5 with the following notes:Use theidentifierForVendorproperty of [UIDevice] or theadvertisingIdentifierproperty of theASIdentifierManagerclass instead, as appropriate, or use theUUIDmethod of theNSUUIDclass to create aUUIDand write it to the user defaults database.
As of May 1st, Apple began enforcing this deprecation on all new app submissions, even for apps targeting earlier versions of iOS. Any use of
uniqueIdentifier is grounds for immediate rejection of new binaries.
Just as privacy and piracy have phonetic and conceptual similarities, device identifiers, whether UUID / GUID, UDID, or otherwise can be rather confusing:
- UUID (Universally Unique Identifier): A sequence of 128 bits that can guarantee uniqueness across space and time, defined by RFC 4122.
- GUID (Globally Unique Identifier): Microsoft's implementation of the UUID specification; often used interchangeably with UUID.
- UDID (Unique Device Identifier): A sequence of 40 hexadecimal characters that uniquely identify an iOS device (the device's Social Security Number, if you will). This value can be retrieved through iTunes, or found using
UIDevice -uniqueIdentifier. Derived from hardware details like MAC address.
Incidentally, all of the suggested replacements for
UIDevice -uniqueIdentifier listed in its deprecation notes return UUID, whether created automatically with UIDevice -identifierForVendor& ASIdentifierManager -advertisingIdentifier or manually with NSUUID (or CFUUIDCreate).Vendor Identifier
The value of this property is the same for apps that come from the same vendor running on the same device. A different value is returned for apps on the same device that come from different vendors, and for apps on different devices regardless of vendor.The value in this property remains the same while the app (or another app from the same vendor) is installed on the iOS device. The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them. Therefore, if your app stores the value of this property anywhere, you should gracefully handle situations where the identifier changes.
In many ways, this is what should have been used the whole time. App developers now have a way to identify devices uniquely—even across other apps by the same developer—without being entrusted with something as sensitive as the device's UDID.
For most applications, doing a find-and-replace of
uniqueIdentifier to identifierForVendor is enough.
However, for advertising networks, which require a consistent identifier across applications from many developers, a different approach is required:
Advertising Identifier
iOS 6 introduces the Advertising Identifier, a non-permanent, non-personal, device identifier, that advertising networks will use to give you more control over advertisers’ ability to use tracking methods. If you choose to limit ad tracking, advertising networks using the Advertising Identifier may no longer gather information to serve you targeted ads. In the future all advertising networks will be required to use the Advertising Identifier. However, until advertising networks transition to using the Advertising Identifier you may still receive targeted ads from other networks.
As the sole component of the Ad Support framework,
ASIdentifierManager's modus operandi is clear: provide a way for ad networks to track users between different applications in a way that doesn't compromise privacy.
Users can opt out of ad targeting in a Settings screen added in iOS 6.1, found at Settings > General > About > Advertising:
NSUUID & CFUUIDRef
NSUUID was added to Foundation in iOS 6 as a way to easily create UUIDs. How easy?NSString *UUID = [[NSUUID UUID] UUIDString];
If your app targets iOS 5 or earlier, however, you have to settle for Core Foundation functions on
CFUUIDRef:CFUUIDRef uuid = CFUUIDCreate(NULL);
NSString *UUID = CFUUIDCreateString(NULL, uuid);
For apps building against a base SDK without the vendor or advertising identifier APIs, a similar effect can be achieved—as recommended in the deprecation notes—by using
NSUserDefaults:- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *UUID = [[NSUserDefaults standardUserDefaults] objectForKey:kApplicationUUIDKey];
if (!UUID) {
CFUUIDRef uuid = CFUUIDCreate(NULL);
UUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
[[NSUserDefaults standardUserDefaults] setObject:UUID forKey:kApplicationUUIDKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
This way, a UUID will be generated once when the app is launched for the first time, and then stored in
NSUserDefaults to be retrieved on each subsequent app launch. Unlike advertising or vendor identifiers, these identifiers would not be shared across other apps, but for most intents and purposes, this is works just fine.
Of course, UUIDs have many other uses: primary identifiers for records in distributed systems, names for temporary files, or even a bulk color generator (chunk the hexadecimal representation into 5 groups of 6!). But on iOS, it's all about tracking, about finding what was lost in a sea of network traffic and possibilities. Knowing where you stand on uniqueness is the first step to understanding all of this.
Thursday, 28 August 2014
Websocket Programming Guide in iOS
iOS client
To build the iOS client you need a Mac and the Apple developer tools (Xcode) installed. To run it on an actual device (and not the simulator), an Apple developer program membership is required.
For iOS, we use the websocket client implementation SocketRocket. The easiest way to include the library is via Cocoapods. This is what our Podfile looks like:
platform :ios, "7.0"
pod "SocketRocket"
If you haven't got Cocoapods installed, run
gem install cocoapods and pod setup. Then run pod install from the iOS directory with the Podfile, and we're good to go.
The relevant code is in ViewController.m. To connect to the websocket server:
- (void)connectWebSocket {
webSocket.delegate = nil;
webSocket = nil;
NSString *urlString = @"ws://localhost:8080";
SRWebSocket *newWebSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:urlString]];
newWebSocket.delegate = self;
[newWebSocket open];
}
Replace localhost with the relevant hostname if not running the simulator on the same computer that runs the websocket server.
Then we implement the SRWebSocketDelegate protocol:
- (void)webSocketDidOpen:(SRWebSocket *)newWebSocket {
webSocket = newWebSocket;
[webSocket send:[NSString stringWithFormat:@"Hello from %@", [UIDevice currentDevice].name]];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
[self connectWebSocket];
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
[self connectWebSocket];
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
self.messagesTextView.text = [NSString stringWithFormat:@"%@\n%@", self.messagesTextView.text, message];
}
Note that this is very similar to the Websockets browser Javascript API.
Here is how to send a new message:
- (IBAction)sendMessage:(id)sender {
[webSocket send:self.messageTextField.text];
self.messageTextField.text = nil;
}
The remaining code in the iOS project is mainly for dealing with resizing views when showing or hiding the keyboard. The app targets iOS 7.0 and is not optimized for iPad.
Subscribe to:
Posts (Atom)
203 Favorite JavaScript Utilities
https://1loc.dev/
-
If you have ever developed an application using a user interface toolkit for a windowing based desktop system (be it Windows, Mac OS X, Li...
-
https://1loc.dev/
-
When to use unowned self or weak self The only time where you really want to use [unowned self] or [weak self] is when you would ...


