iOS 開發中你是否遇到這些經驗問題(二)

判若兩人丶發表於2016-08-22

前言:

之前在掘金社群是沒有原作許可權的, 所以這篇文章在之前是通過連結分享的, 現在有原作許可權所以為了方便重新上傳一下文章!在上一篇文章中我相信幫助了很多的小夥伴, 那麼在這篇文章希望還能幫助到你!

1.在Block中一起使用weakSelf與strongSelf的含義

我們都會宣告一個弱引用在block中使用, 目的就是防止迴圈引用, 那麼weakSelfstrongSelf一起使用目的是什麼呢? 首先先定義2個巨集:

#define LRWeakSelf(type)  __weak typeof(type) weak##type = type;
#define LRStrongSelf(type)  __strong typeof(type) type = weak##type;複製程式碼

我們建立一個shop並且在shop.myBlock程式碼塊中使用弱引用LRWeakSelf(shop);

    LRShop *shop = [[LRShop alloc]init];
    shop.string = @"welcome to our company";
    //弱引用
    LRWeakSelf(shop);
    shop.myBlock = ^{
        NSLog(@"%@",weakshop.string);
    };
    shop.myBlock();複製程式碼

LRWeakSelf(shop);LRStrongSelf(shop);一起使用

LRShop *shop = [[LRShop alloc]init];
    shop.string = @"welcome to our company";
    //弱引用
    LRWeakSelf(shop);
    shop.myBlock = ^{
        //強引用
        LRStrongSelf(shop)
        NSLog(@"%@",shop.string);
    };
    shop.myBlock();複製程式碼

這2個列印結果都是shop.string有值並且shop也銷燬了, 看起來是沒什麼區別:

僅僅使用LRWeakSelf(shop);並且在myBlock中增加一個延遲3秒在輸出就會出現問題, 雖然物件銷燬了, 輸出的值卻是null

//弱引用
 LRWeakSelf(shop);
    shop.myBlock = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakshop.string);
        });
    };
    shop.myBlock();複製程式碼

如果LRWeakSelf(shop);LRStrongSelf(shop);一起使用輸出的shop.string有值,物件也銷燬了, 我就不再截圖給大家看了!

//弱引用
 LRWeakSelf(shop);
    shop.myBlock = ^{
        //強引用
        LRStrongSelf(shop)
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",shop.string);
        });
    };
    shop.myBlock();複製程式碼

通過上面一堆的解釋, 我們明顯發現LRWeakSelf(shop);LRStrongSelf(shop);一起使用的好處, 不但能列印出我想要的值,而且也不會造成迴圈引用 , 在開發中這兩個方法可以根據實際情況進行使用!

2.使用UIAppearance注意的問題

如果不熟悉可以點選瞭解, UIAppearance它的目的就是設定全域性顯示樣式, 我們知道只要帶UI_APPEARANCE_SELECTOR這個巨集, 我們就可以使用UIAppearance比如這樣設定:

我們知道UIBarButtonItem它是有狀態的比如UIControlStateNormal或者是UIControlStateDisabled狀態 如果通過UIAppearance設定UIControlStateDisabled狀態下的顏色是不好使的, 因為使用appearance會有一些延遲, 導致在不同狀態下的顏色不好使, 我們只要強制重新整理一下就可以了:

// 重新整理
[self.navigationController.navigationBar layoutIfNeeded];複製程式碼

所以以後使用UIAppearance在某個狀態下設定顏色,字型等不好使, 只需要在對應的位置用layoutIfNeeded重新整理一下就可以了!

3. UITextField使用注意

先貼一個UITextField如何設定佔位文字的顏色, 如果不先設定佔位文字, 佔位文字的顏色是不管用的:

//先設定佔位文字
textField.placeholder = @"設定了佔位文字內容以後, 才能設定佔位文字的顏色";
//佔位文字顏色
[textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];複製程式碼

大家監聽UITextField文字的改變會用到代理:

#pragma mark - 
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
//這裡監聽文字改變
    return YES;
}複製程式碼

但是這個代理方法監聽會有問題如下圖:

所以我們要監聽UITextField的文字改變不建議使用代理, 我們用addTarget監聽文字

[textField addTarget:self action:@selector(textEditingChanged) forControlEvents:UIControlEventEditingChanged];複製程式碼

4.UITextView新增佔位文字的正確方法

UITextView的佔位文字屬於它內部的一個功能, 我們在控制器或者用代理來處理佔位文字一些功能是不合理的, 所以我們要自定義UITextView把相關內部的東西都封裝起來! (1)給外界提供佔位文字與佔位文字顏色:

/** 佔位文字 */
@property (nonatomic, copy)NSString *placeholder;
/** 佔位文字顏色 */
@property (nonatomic, strong)UIColor *placeholderColor;複製程式碼

(2)設定佔位文字的預設值, 如果不設定預設值,外界不用你提供的方法會有崩潰現象:

// 設定預設字型
self.font = [UIFont systemFontOfSize:17];
// 設定預設的佔位文字顏色
self.placeholderColor = [UIColor grayColor];複製程式碼

(3)內部新增佔位文字的label:

/** 佔位文字label */
@property (nonatomic, weak) UILabel *placeholderLabel;

//懶載入
- (UILabel *)placeholderLabel
{
    if (_placeholderLabel == nil) {
        UILabel *placeholderLabel = [[UILabel alloc] init];
        placeholderLabel.numberOfLines = 0;
        [self addSubview:placeholderLabel];
        _placeholderLabel = placeholderLabel;
    }
    return _placeholderLabel;
}複製程式碼

(4)通過監聽文字改變,來顯示或隱藏佔位文字:

 // 監聽文字
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChangeNotification) name:UITextViewTextDidChangeNotification object:nil];

//監聽的方法
- (void)textDidChangeNotification {
    // 有文字就隱藏佔位文字
    self.placeholderLabel.hidden = self.hasText;
}複製程式碼

(5)如果佔位文字被修改, 顏色被修改, 字型被修改, 我們在內部需要重寫set方法, 如果通過程式碼修改了textView文字(不是佔位文字)不會發通知也需要重寫set方法:

封裝好的自定義TextView可以直接使用:Demo下載

5.自定義控制元件裡如何拿到導航控制器進行頁面跳轉?

(1)如果有UITabBarController我們會這樣獲取導航控制器:

 UIViewController *viewC = [[UIViewController alloc]init];
 // 取出當前的導航控制器
 UITabBarController *tabBarVc = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController;
 //The view controller associated with the currently selected tab item
 //當前選擇的導航控制器
 UINavigationController *navC = (UINavigationController *)tabBarVc.selectedViewController;
 [navC pushViewController:viewC animated:YES];複製程式碼

(2)如果通過modal出來的控制器並且用UITabBarController不好使, 我們會這樣獲取導航控制器:

 UIViewController *viewC = [[UIViewController alloc]init];
 //獲取最終的根控制器
 UIViewController *rootC = [UIApplication sharedApplication].keyWindow.rootViewController;
 //如果是modal出來的控制器,它就會通過presentedViewController拿到上一個控制器
 UINavigationController *navC = (UINavigationController *)rootC.presentedViewController;
 [navC pushViewController:viewC animated:YES];複製程式碼

6.修改了leftBarButtonItem如何恢復系統側滑返回功能

在開發中系統的leftBarButtonItem不是我們想要的, 如果我們修改了leftBarButtonItem那麼系統自帶的側滑返回功能就不好使了!

//設定代理
self.interactivePopGestureRecognizer.delegate = self;
#pragma mark - 
//實現代理方法:return YES :手勢有效, NO :手勢無效
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    //當導航控制器的子控制器個數 大於1 手勢才有效
    return self.childViewControllers.count > 1;
}複製程式碼

7.重新認識Bounds

我們之前對Bounds理解就是以自己的左上角為座標原點, 也就是說Boundsxy值是0, 但是Boundsxy值有可能是正數也可能是負數, 不一定是0那麼Bounds真正是什麼意思呢 ?

  • Bounds: 是以自己內容的左上角為座標原點, 計算出自己的位置和大小
  • Frame: 是以父類內容的左上角為座標原點, 計算出自己的位置和大小 那什麼是內容呢 ? 首先內容是抽象的, 一個控制元件不僅僅只有一層矩形框的, 他有很多圖層的, 這個內容其實就可以抽象成一個控制元件的內部圖層

  • 內容:就是內部的東西, 它的子控制元件也屬於內容,也就是說修改了Buonds子控制元件的位置也會跟著改變

上圖藍色和綠色是屬於一個控制元件, 只不過藍色是控制元件本身, 綠色是控制元件的內容, 我們改變這個控制元件的Boundsxy值為-20, 內容位置改變, 控制元件本身位置不變!

8.跟列舉相關的一些符號的含義

上圖是一個蘋果官方的一個列舉, 我們主要是看<<的用處(它是C語言中的位運算左移), 如果在列舉中只要<<那它的含義就是可以通過|進行組合使用:

 //隨便新增一個UITextField
    UITextField *field = [UITextField new];
    //可以通過 | 組合使用UIControlEventEditingDidBegin, UIControlEventValueChanged,UIControlEventEditingDidEnd
    [field addTarget:self action:@selector(textFieldDidChanged) forControlEvents:UIControlEventEditingDidBegin | UIControlEventValueChanged | UIControlEventEditingDidEnd];
    [self.view addSubview:field];複製程式碼

如果列舉沒有<<就不能組合使用, 那它有什麼規律呢1 << n 代表:2的n次方:

//1 << 16 代表:2的16次方
 UIControlEventEditingDidBegin = 1 << 16,
//1 << 17 代表:2的17次方     
 UIControlEventEditingChanged  = 1 << 17,
//1 << 18 代表:2的18次方
 UIControlEventEditingDidEnd  = 1 << 18,
//1 << 19 代表:2的19次方
 UIControlEventEditingDidEndOnExit  = 1 << 19,複製程式碼

原來這樣的列舉可以組合使用, 那蘋果官方是怎麼知道我們多個條件組合使用了呢 ?

 NSUInteger controlEvents = UIControlEventEditingDidBegin | UIControlEventValueChanged | UIControlEventEditingDidEnd;
    /**
    //通過 & 符號來判斷是否包含:
    UIControlEventEditingDidBegin,
    UIControlEventValueChanged,
    UIControlEventEditingDidEnd
     */
    if (controlEvents & UIControlEventEditingDidBegin) {

        NSLog(@"UIControlEventEditingDidBegin");

    }else if (controlEvents & UIControlEventValueChanged) {

        NSLog(@"UIControlEventValueChanged");

    }else if (controlEvents & UIControlEventEditingDidEnd) {

        NSLog(@"UIControlEventEditingDidEnd");
    }複製程式碼

通過以上方法就能判斷組合的狀態, 在開發中這個<<意義很大的, 如果多個條件中, 任何一個條件滿足我們也可用帶<<的列舉給外界組合使用, 就像蘋果官方新增<<使用是一樣的!

9.Xib相關的一些問題

下圖我們可以看出來, 如果通過xib載入出來的view尺寸是不正確的, 在xib中這個view不管你怎麼設定都是治標不治本,我們會在layoutSubviews通過自己的寬度來計運算元控制元件的尺寸!

//在這裡拿出的寬度是不正確的
- (void)awakeFromNib {}
//對尺寸計算我們一般拿到這個方法中計算(拿到自己寬度計運算元控制元件的尺寸)
- (void)layoutSubviews {
    [super layoutSubviews];
    //在這裡拿到自己的寬度是正確的
}複製程式碼

那我們也會想到, 如果控制器的view也是xib建立的, 我們該怎麼辦 ? 其實不管控制器是在哪裡建立的, 我們只要只在viewDidLayoutSubviews方法中拿到控制器尺寸來計運算元控制元件尺寸都是正確的, 所以說建議大家以後在viewDidLayoutSubviews計算尺寸:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    //在這裡計算尺寸
}複製程式碼

喜歡的小夥伴請點贊一下吧!如果有不足的地方,請大家及時幫忙糾正與補充,順便談談你的建議! 原文閱讀

相關文章