четверг, 13 ноября 2014 г.

JustToRemember: Preparing Interstitial Ads


1. Import iAd framework
#import <iAd/iAd.h>

2. Link iAd framework at Build Phases

3. Set policy for destination view controller
ViewController *viewController = [[ViewController alloc] init];
viewController.interstitialPresentationPolicy = ADInterstitialPresentationPolicyAutomatic;

4. Prepare ads at appdelegate
[UIViewController prepareInterstitialAds];

четверг, 6 ноября 2014 г.

Don't even think about setting frames at -viewDidLoad!


There is a common question: "Where should I initialize subviews (programmatically)?". Answer is quite complicated.

What do we need to create an object? Right: allocate memory and initialize object. What does this actually mean?
To initialize - to set starting values.

So, can you create subviews in one place? Oh, yes you can. But you should not.
The main factor is view's frame. It could be changed. And it should be set depending on current device orientation.

Q: Ok, where should I create subview and set initial parameters?
A: Let it be Rule #1: Create (allocate and init) subviews at -viewDidLoad. That method is called only once. Obviously, when your view controller was loaded from storyboard or xib.

Here is a simple example:
Firstly, we define our subviews at class extension (looks like a nameless category):
@interface ViewController ()
{
    UIView *subviewOne;
    UIImageView *imageView;
    UILabel *labelForBoldText;
}
@end

Secondly, we create subviews by allocating memory, defining attributes and adding them to main view:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // Init subviews //
    // view
    subviewOne = [[UIView alloc] init];
    subviewOne.backgroundColor = [UIColor redColor];
    subviewOne.layer.cornerRadius = 20.0f;
    [self.view addSubview:subviewOne];
    
    // label
    labelForBoldText = [[UILabel alloc] init];
    labelForBoldText.backgroundColor = [UIColor clearColor];
    labelForBoldText.textColor = [UIColor blackColor];
    labelForBoldText.font = [UIFont boldSystemFontOfSize:20.0f];
    [self.view addSubview:labelForBoldText];
}

Last goal is to set the frame. And here comes the main question:

Q: What place is the best to set the frame?
A: In my opinion, -viewWillLayoutSubviews.
Why do I prefer this method to -viewWillAppear? Apple documentation says: "-viewWillAppear - Called when the view is about to made visible." So, it's used to handle changes in screens. But what if user just flipped device on the other side? -viewWillAppear will not be called. You don't need to think about it if you are using autoLayout, because it will make frame changes depending on constraints.
On the other hand, -viewWillLayoutSubviews will be called when orientation changes will take place and the new view frame is already set. So, you can update your subviews frame just before their -layoutSubview method will be called.

Frame setter code will look like:
- (void)viewWillLayoutSubviews {
    // Define frame for view
    CGRect viewFrame = CGRectMake(0, 0, 100, 100);
    subviewOne.frame = viewFrame;
    
    // Define frame for label
    CGRect labelFrame = CGRectMake(0, 100, 100, 20);
    labelForBoldText.frame = labelFrame;
}

To conclude, I suggest following steps for creating subviews programmatically:

  1. Define subviews at class extension to make it accessible 
  2. Initialize subviews and add to view at -viewDidLoad
  3. Set frames at -viewWillLayoutSubviews

Dismissed.
Contact me at twitter (@ibvene) if you have any questions.

пятница, 24 октября 2014 г.

Использование ассоциированных объектов на примере UIAlertView и UIActionSheet

Существуют ситуации, в которых вам необходимо отображать более одного AlertView или ActionSheet и, более того, обрабатывать нажатие кнопки. При этом мы наблюдаем следующее - код вызова данных элементов отделен от кода обработки. В таком случае логично определить для каждого вызываемого объекта идентификатор (свойство tag) и направлять ответ пользователя на обработку тому или иному методу. В результате мы получим нечто вроде этого:
#pragma mark - Calling Alerts
- (IBAction)btn_callAlertOne:(id)sender {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"First Alert" message:@"Alert message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Boom",nil];
    alert.tag = 1;
    [alert show];
}

- (IBAction)btn_callAlertTwo:(id)sender {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Second Alert" message:@"Alert message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Boom",nil];
    alert.tag = 2;
    [alert show];
}

- (IBAction)btn_callAlertThree:(id)sender {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Third Alert" message:@"Alert message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Boom",nil];
    alert.tag = 3;
    [alert show];
}

- (IBAction)btn_callAlertFour:(id)sender {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Fourth Alert" message:@"Alert message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Boom",nil];
    alert.tag = 4;
    [alert show];
}

#pragma mark - UIAlertView Delegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    switch (alertView.tag) {
        case 1:
            [self alertView_firstAlertClickedButtonAtIndex:buttonIndex];
            break;
        case 2:
            [self alertView_secondAlertClickedButtonAtIndex:buttonIndex];
            break;
        case 3:
            [self alertView_thirdAlertClickedButtonAtIndex:buttonIndex];
            break;
        case 4:
            [self alertView_fourthAlertClickedButtonAtIndex:buttonIndex];
            break;
    }
}

- (void)alertView_firstAlertClickedButtonAtIndex:(NSInteger)buttonIndex {
    switch (buttonIndex) {
        case 0:
            break;
        case 1:
            break;
    }
}

- (void)alertView_secondAlertClickedButtonAtIndex:(NSInteger)buttonIndex {
    switch (buttonIndex) {
        case 0:
            break;
        case 1:
            break;
    }
}

- (void)alertView_thirdAlertClickedButtonAtIndex:(NSInteger)buttonIndex {
    switch (buttonIndex) {
        case 0:
            break;
        case 1:
            break;
    }
}

- (void)alertView_fourthAlertClickedButtonAtIndex:(NSInteger)buttonIndex {
    switch (buttonIndex) {
        case 0:
            break;
        case 1:
            break;
    }
}
Даже с отсутствующими алгоритмами обработки нажатий данный код выглядит массивно. Для каждого сообщения (alert или actionsheet) определяется метод вызова и метод обработки, плюс общий метод сортировки. Упростить данную структуру можно, если описать обработку нажатий при создании сигнала. Для этого необходимо ассоциировать блок обработки с сигналом. Сделать это можно с помощью функции objc_setAssociatedObject, доступной в runtime. Взглянем на описание функции:
/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
Воспользуемся ассоциированным объектом. Назначим блок обработки в качестве значения (value). Импортируем заголовок runtime и добавим ключ.
// Импорт
#import 
// Определим ключ
static void *ALMyAlertViewKey = "ALMyAlertViewKey";

- (IBAction)btn_callAlertOne:(id)sender {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"First Alert" message:@"Alert message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Boom",nil];
    
    objc_setAssociatedObject(alert, ALMyAlertViewKey, ^(NSInteger buttonIndex){
        switch (buttonIndex) {
            case 0:
                break;
            case 1:
                break;
        }
    }, OBJC_ASSOCIATION_COPY);
    
    [alert show];
}

- (IBAction)btn_callAlertTwo:(id)sender {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Second Alert" message:@"Alert message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Boom",nil];
    objc_setAssociatedObject(alert, ALMyAlertViewKey, ^(NSInteger buttonIndex){
        switch (buttonIndex) {
            case 0:
                break;
            case 1:
                break;
        }
    }, OBJC_ASSOCIATION_COPY);
    [alert show];
}

- (IBAction)btn_callAlertThree:(id)sender {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Third Alert" message:@"Alert message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Boom",nil];
    objc_setAssociatedObject(alert, ALMyAlertViewKey, ^(NSInteger buttonIndex){
        switch (buttonIndex) {
            case 0:
                break;
            case 1:
                break;
        }
    }, OBJC_ASSOCIATION_COPY);
    [alert show];
}

- (IBAction)btn_callAlertFour:(id)sender {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Fourth Alert" message:@"Alert message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Boom",nil];
    objc_setAssociatedObject(alert, ALMyAlertViewKey, ^(NSInteger buttonIndex){
        switch (buttonIndex) {
            case 0:
                break;
            case 1:
                break;
        }
    }, OBJC_ASSOCIATION_COPY);
    [alert show];
}

#pragma mark - UIAlertView Delegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    void (^associatedBlock)(NSInteger) = objc_getAssociatedObject(alertView, ALMyAlertViewKey);
    associatedBlock(buttonIndex);
}
Девять методов сократились до пяти. Теперь код создания сигнального окна и обработки результата находятся в одном месте. Главный обработчик -alertView:clickedButtonAtIndex: теперь содержит две строки: первая получает ассоциированный объект (value), а именно наш блок, а вторая передает индекс нажатой кнопки данному блоку. Для ActionSheet методы выглядят следующим образом:
// Импорт
#import 
// Определим ключ
static void *ALMyActionSheetKey = "ALMyActionSheetKey";

- (IBAction)btn_callActivitySheetOne:(id)sender {
    NSString *actionSheetTitle = @"1st Action Sheet title";
    NSString *destructiveButtonTitle = @"Red button";
    NSString *cancelTitle = @"Cancel";
    
    UIActionSheet *actionSheet = [[UIActionSheet alloc]
                                  initWithTitle:actionSheetTitle
                                  delegate:self
                                  cancelButtonTitle:cancelTitle
                                  destructiveButtonTitle:destructiveButtonTitle
                                  otherButtonTitles:nil];
    objc_setAssociatedObject(actionSheet, ALMyActionSheetKey, ^(NSInteger buttonIndex){
        switch (buttonIndex) {
            case 0:
                break;
            case 1:
                break;
        }
    }, OBJC_ASSOCIATION_COPY);
    [actionSheet showInView:self.view];
}

- (IBAction)btn_callActivitySheetTwo:(id)sender {
    NSString *actionSheetTitle = @"2nd Action Sheet title";
    NSString *destructiveButtonTitle = @"Red button";
    NSString *cancelTitle = @"Cancel";
        
    UIActionSheet *actionSheet = [[UIActionSheet alloc]
                                      initWithTitle:actionSheetTitle
                                      delegate:self
                                      cancelButtonTitle:cancelTitle
                                      destructiveButtonTitle:destructiveButtonTitle
                                      otherButtonTitles:nil];
    objc_setAssociatedObject(actionSheet, ALMyActionSheetKey, ^(NSInteger buttonIndex){
        switch (buttonIndex) {
            case 0:
                break;
            case 1:
                break;
        }
    }, OBJC_ASSOCIATION_COPY);
    [actionSheet showInView:self.view];
}

- (IBAction)btn_callActivitySheetThree:(id)sender {
    NSString *actionSheetTitle = @"3rd Action Sheet title";
    NSString *destructiveButtonTitle = @"Red button";
    NSString *cancelTitle = @"Cancel";
    
    UIActionSheet *actionSheet = [[UIActionSheet alloc]
                                  initWithTitle:actionSheetTitle
                                  delegate:self
                                  cancelButtonTitle:cancelTitle
                                  destructiveButtonTitle:destructiveButtonTitle
                                  otherButtonTitles:nil];
    objc_setAssociatedObject(actionSheet, ALMyActionSheetKey, ^(NSInteger buttonIndex){
        switch (buttonIndex) {
            case 0:
                break;
            case 1:
                break;
        }
    }, OBJC_ASSOCIATION_COPY);
    [actionSheet showInView:self.view];
}

- (IBAction)btn_callActivitySheetFour:(id)sender {
    NSString *actionSheetTitle = @"4th Action Sheet title";
    NSString *destructiveButtonTitle = @"Red button";
    NSString *cancelTitle = @"Cancel";
    
    UIActionSheet *actionSheet = [[UIActionSheet alloc]
                                  initWithTitle:actionSheetTitle
                                  delegate:self
                                  cancelButtonTitle:cancelTitle
                                  destructiveButtonTitle:destructiveButtonTitle
                                  otherButtonTitles:nil];
    objc_setAssociatedObject(actionSheet, ALMyActionSheetKey, ^(NSInteger buttonIndex){
        switch (buttonIndex) {
            case 0:
                break;
            case 1:
                break;
        }
    }, OBJC_ASSOCIATION_COPY);
    [actionSheet showInView:self.view];
}

#pragma mark - UIActionSheet Delegate
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
    void (^associatedBlock)(NSInteger) = objc_getAssociatedObject(actionSheet, ALMyActionSheetKey);
    associatedBlock(buttonIndex);
}