Wednesday, August 5, 2015

WWDC2014 Video 237 A Strategy for Great Work

This session is presented by Ken, who has more than 10 yrs experience working at Apple. Here are 10 Lessons he shared in this session.

  • Know a good idea when you see it 
  • Don't try to solve every problem at once 
  • Find smart friends and listen to them 
  • Work should explain itself
  • Choose the simplest thing which might work 
  • Only show your best work
  • Iterating quickly leads to better work
  • Be kind to people, but be honest about work 
  • Separate yourself from your work 
  • You’re never done

I specially present my affection on the following Lesson and Quote. Love this session.


Tuesday, May 19, 2015

How to Disable Default Popping Topmost UIViewController by Swipe from Left Edge

From iOS7, user could pop to parent UIViewController by swiping from screen’s left edge to some points in the middle. Also, UIScreenEdgePanGestureRecognizer is introduced since then, which could help develper add more ways to interact with the user.

Whlie, a question raised recently, how can I disable it?

The UIViewControllers are “Push” or “Pop” from the ViewController Stack, sometimes we call it UINavigationController. This is the entry where developer could use to access all the UIViewController in the Stack. Also, each UIViewController been pushed into the Stack will have an easy access to this UINavigationController.

Back to our question, how can we disable it?

After reading the iOS Developer Doc of UINavigationController, I found interactivePopGestureRecognizer Property, which is 

The gesture recognizer responsible for popping the top view controller off the navigation stack. (read-only)

then, if we can disable this property, we could disable the user popping to parent UIViewController by swiping.

Here is the Code:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.delegate = self;
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.delegate = nil;
    }
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    return NO;
}

Also, Add UIGestureRecognizerDelegate to the UIViewController.

Tuesday, April 28, 2015

WWDC2014 Video 221 Creating Custom iOS User Interface

In this video, four main topics are covered.

Sprint animations

Spring Animation is the base of almost every animation in the iOS8 system, which means almost all of the animations in iOS8 are using spring animation. It was introduced from iOS7 via one single function:

+ animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:

This function is very similar with ordinary animation function we used to using a lot. The only difference here is two additional parameters: Damping Ratio and Initial Spring Velocity. The damping ratio defines how much the oscillation oscillate at the end state of the animation, ranging from 0.0 to 1.0. 0.0 defines high frequency oscillation, while 1.0 defines no oscillation at all. This is place where can help us make a boucing effect. The initial spring velocity defines how fast the animation starts. The smaller the volecity is, the sooner the animation object will move to the end state, verse vice.

The following demo code imitate the Animation of Opening Folder on iOS Home Screen.

const static CGFloat BOXSIZE = 100.f;

- (void)tapped:(UITapGestureRecognizer *)gesture
{
    [self initBox];
    
    [UIView animateWithDuration:1.f
                          delay:0.f
         usingSpringWithDamping:.75f
          initialSpringVelocity:.5f
                        options:0
                     animations:^{
                         [self endBox];
                     } completion:^(BOOL finished) {
                         //
                     }];
}

- (void)initBox
{
    self.movingBox.frame = CGRectMake(0.f, self.view.bounds.size.height - BOXSIZE, BOXSIZE, BOXSIZE);
}

- (void)endBox
{
    self.movingBox.frame = CGRectMake(CGRectGetMidX(self.view.bounds) - BOXSIZE * 3.f / 2.f, CGRectGetMidY(self.view.bounds) - BOXSIZE * 3.f / 2.f, BOXSIZE * 3.f, BOXSIZE * 3.f);
}

Vibrancy and blur

In iOS8, UIBlurEffect and UIVibrancyEffect are introduced for developers have Dynamic Blur Effect more easily. In the talk, Apple Engineer still recommends using static blur effect code if dynamic blur effect views are not actually what you need, since dynamic blur effect takes a lot of computer power.

The UIBlurEffect gives blur effect to an associated UIView, while UIVibrancyEffect gives the clear part which is not blurred on the blurred view. So, this is a three layer structure. UIView at the bottom, then comes the UIBlurEffect, at last the UIVibrancyEffect comes. The UIVibrancyEffect consumes the computing power most. The UIBlurEffect gives three styles for the developer to use, UIBlurEffectStyleExtraLight, UIBlurEffectStyleLight, UIBlurEffectStyleDark.

The example could be found at Siri User Interface, where user could clearly see the app icons on the Home Screen of the iPhone.

Shape layers

In this section, the engineer talks about the CAShapeLayer. Several things should be mentioned here:

  • How to use CAShapeLayer back up the UIView as the base layer. In UIView subclass,
+ (Class)layerClass
{
    return [CAShapeLayer Class];
}

- (void)awakeFromNib
{
    CAShapeLayer *layer = (CAShapeLayer *)self.layer;
    // init layer properties here.
    // Also, everytime to update layer property, convert it to CAShaperLayer and use a local variable for it.
}

Dyanmic Core Animation behaviors

This is probably the most challenging part for me, so many sample code on CABasicAnimation. I think have a good use of CAAction delegate and CALayer Delegate method could greatly help your custom UIView subclass have great animation effect when UIView’s properties are changed. Also, developer could define customized property, which could be very powerful. By using this technique, developer could not only customize animation behavior, but also disable system’s default animation behaviors.

For more information, here is a link to the Apple Developer Official Page.

Thursday, March 19, 2015

How to use NSShadow

As Apple iOS Documents mentions:

An NSShadow object may be used in one of two ways. First, it may be set, like a color or a font, in which case its attributes are applied to all content drawn thereafter—or at least until another shadow is applied or a previous graphics state is restored. Second, it may be used as the value for the NSShadowAttributeName text attribute, in which case it is applied to the glyphs corresponding to the characters bearing this attribute.

Thus, There TWO ways to implement NSShadow to your code.

1st, NSShadow could be set as attributes to all content drawn thereafter.

image
////  ShadowedRectangle.m//  NSShadowDemo////  Created by Antonio081014 on 3/18/15.//  Copyright (c) 2015 Antonio081014.com. All rights reserved.//

#import "ShadowedRectangle.h"

@implementation ShadowedRectangle

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor clearColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    UIFont *font = [UIFont boldSystemFontOfSize:30.f];
    
    NSShadow *shadow = [[NSShadow alloc] init];
    shadow.shadowColor = [UIColor redColor];
    shadow.shadowBlurRadius = 0.0;
    shadow.shadowOffset = CGSizeMake(0.0, 2.0);
    
    NSDictionary *attributes = @{NSShadowAttributeName          : shadow,
                                 NSForegroundColorAttributeName : [UIColor yellowColor],
                                 NSFontAttributeName            : font,
                                 NSBackgroundColorAttributeName : [UIColor greenColor]};
    
    NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:@"Text has shadows" attributes:attributes];
    
    [attributedText drawInRect:rect];
}

@end

2nd, NSShadow could be used as the value for the NSShadowAttributeName text attribute.

// Customize the UINavigationBar Title Font.
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8];
shadow.shadowOffset = CGSizeMake(0, 1);
[[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys:
                                                       [UIColor colorWithRed:245.0/255.0 green:245.0/255.0 blue:245.0/255.0 alpha:1.0], NSForegroundColorAttributeName,
                                                       shadow, NSShadowAttributeName,
                                                       [UIFont fontWithName:@"HelveticaNeue-CondensedBlack" size:21.0], NSFontAttributeName,
                                                       nil]];

While, as seen on above examples, NSShadow could be easily applied to the Text as attributes, it could be also applied to images(pixels). Example could be found here.

If you want to read more, please check out http://www.antonio081014.com.

Saturday, March 14, 2015

Customize UINavigationBar “Back Button”

Summary:

This article will try to dig a little bit about UINavigationController and UINavigationBar. The story starts with me trying customzie “Back Button” on UINavigationBar, which helps user to navigate back to precede ViewController.

Let’s start from scratch:

After creating a fresh new Xcode project with two ViewControllers(VC) on StoryBoard. User could easily navigate from one VC to another by clicking on the UIBarButtonItem on the UINavigationBar. The StoryBoard would look like the following:

image

When trying to run this simply application on iOS Simulator. User will realize the Title of Back Button on the second VC is not “First View Controller”, as it should be for most of the cases. Instead, it looks like:

image

“Back”, yes, the Title was changed by System automatically to “Back” because the Title of parent VC is too long.
If the Title of first VC is changed to “First”, then runnning application again, user would see:

image

So, when the Title of first VC is too long, the second VC will change the text of back button to “Back” automatically.
After a quick check with Reference of navigationItem of UIViewController. I could change the “back button” by using:
self.navigationItem.backBarButtonItem.title = @"Custom";
But doesn’t work at all. So another dig on this, found this:

image

So, basically it is saying the “Back Button” I wish to change is associated with Previous View Controller. Thus, the title of it should be changed in the “Parent View Controller”, not in Current View Controller. This blows my mind. Also, the reference of backBarButtonItem could be found by  “Command + Clicking” on backBarButtonItem:

image

Then, the above code was moved to the “FirstVC.m” file.
But still nothing changed. Keep digging…
Based on several answers on StackOverflow.com, I realized the property navigationItem in UIViewController is readonly. So, I use this piece of code instead:
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Custom" style:UIBarButtonItemStylePlain target:nil action:nil];
This code totally rocks.

Conclusion: 

Change backBarButtonItem in Parent ViewController. Not current ViewController.
Thanks for reading.

Friday, February 13, 2015

Location Services Immigration from iOS7 to iOS8

With iOS7, I developed an app tracking all the location with other information like date, time, etc. Yes, right, it's like the other apps like "Life360", which could share the location with other family members. The app is backed with a local server developed with NodeJS and Database by MongoDB. All the techs are pretty freshly popular in current days.

While, since iOS8 is pretty widely adapted by most of the users, I started to immigrate the app from iOS 7 to iOS 8. Here is what I did:

1st, I did nothing, simply run the source code on the iOS runned iPhone 5. The MapView shows exactly as before, which includes all the CLLocation fetched from the server. That means the history could be displayed fully on the Map. BUT, the current location annotation(That blue shining circle) doesn't show up. Also, my UIAlertView shows me the Location Service couldn't give me the location update even it tried to.

2nd, I read the Apple Reference, the most obviously differences I had found are the two newly coming functions which aim to request location service permission on the iDevice. I guess they must have the meaning to be here, I mean they are listed as the two functions in the first section on the page. So, I changed my code from - (void)startUpdatingLocation direct calling to having - (void)requestWhenInUseAuthorization first, then - (void)startUpdatingLocation in the delegate function - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status , when the status is kCLAuthorizationStatusAuthorized or kCLAuthorizationStatusAuthorizedWhenInUse, The result is the request function had been called but the status has been doomed to be kCLAuthorizationStatusNotDetermined .

3rd, After reading a post on StackOverflow, I found my answer. The reason I didn't have the NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription key in Info.plist with a message to be displayed in the prompt.

4th, Everything is good, just like the old time.

Summary:
To Immigrate Location Service from iOS 7 to iOS 8, do these:

  1. Call requestWhenInUseAuthorization or requestAlwaysAuthorization first.
  2. Implement Delegate Function - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
  3. When status indicates the Location Service has been granted, Call startUpdatingLocation
  4. Important: Add string message for NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription in your APPNAME-Info.plist under your support file folder.
Good Luck.