Interface Builder一些使用技巧

當年輕的開發者朋友們寫了一大堆的代碼時,他們會覺得很高興,并且喜歡夸耀自己寫的項目有成千上萬行代碼。

隨著經驗越來越豐富,我們意識到代碼是昂貴的,因為要花很多時間去維護、調試和尋找原因。

這也是為什么我們應該使用工具來幫助我們避免寫出樣板代碼。Interface Builder就是這些工具中的一個。

下面說一些我喜歡的小提示和技巧。

Storyboards還是Xib

很顯然,蘋果是極力推薦我們使用storyboard的,因為下列的一些特性只在storyboard中才有:

  • Prototype cells
  • Segues
  • Container View Controllers

很多人抱怨在團隊合作的時候使用storyboard很困難,因為在合并代碼的時候會有沖突。以前這個問題是會經常出現,但是現在storyboard的格式更好、更容易理解了。

即使這樣,我也不喜歡干合并代碼的活。不過幸運的是,當我們使用多個storyboard合理地劃分項目時,就可以避免很多潛在的沖突。

我比較喜歡給每一個場景創建一個storyboard,例如最新的那個app:

  • 入職 — 新建賬戶以及初始的信息界面
  • 新建流 — 當用戶可以新建內容時
  • 活動 — 查看提到自己、自己或其他用戶的活動的相關界面

iOS 9給我們介紹了 Storyboard References ,使我們能夠更容易地使用多個storyboard來分割應用。

如果需要部署在老版本的iOS target上呢?

玩轉Storyboard, Xib或類引用

如果你運氣不夠好,未能使用iOS9作為最低的部署目標,仍舊可以用你自己的方法實現。雖然需要花費點功夫,但這些努力都是值得的。多年以來,我一直在使用一個非常簡單的擴展去擁有我自己的Storyboard, xib 和 類引用。

擴展的主要內容:

  • 在storyboard中新建一個空的UIViewController
  • 在UIViewController中用IBInspectable擴展來支持自定義的replacementIdentifier圖式

storyboard.StoryboardName.ControllerIdentifier

storyboard.StoryboardName -> loads the initial vc

class.name

xib.name

Objective-C

@interface UIViewController (KZReplacement)

@property (nonatomic, copy) IBInspectable NSString *replacementIdentifier;

//! 如果設了replacementIdentifier,就新建一個controller,否則返回self

  • (UIViewController *)kz_replacementController;

@end</pre>

@interface UIViewController (KZReplacement)
 
@property (nonatomic, copy) IBInspectable NSString *replacementIdentifier;
 
//! 如果設了replacementIdentifier,就新建一個controller,否則返回self

  • (UIViewController *)kz_replacementController;   @end</pre> </div>

    在Interface Builder里是這樣的:

    這個方法有一個簡單的實現,下面的是一個最“復雜”的:

    Objective-C

    - (UIViewController )kz_replacementControllerWithIdentifier:(NSString )identifier;
    {
      NSDictionary *replacementSchemes = @{

          @"class" : [NSValue valueWithPointer:@selector(kz_createControllerWithClassIdentifier:)],
          @"storyboard" : [NSValue valueWithPointer:@selector(kz_loadControllerWithStoryboardIdentifier:)],
          @"xib" : [NSValue valueWithPointer:@selector(kz_loadControllerWithXibIdentifier:)]
    

    };

    __block UIViewController replacement = nil; [replacementSchemes enumerateKeysAndObjectsUsingBlock:^(NSString scheme, NSValue selectorValue, BOOL stop) {

      const struct _NSRange range = [identifier rangeOfString:scheme];
      if (range.location == 0)
      {
          *stop = YES;
          UIViewController *(*objc_msgSendTyped)(id, SEL, NSString *) = (UIViewController *(*)(id, SEL, NSString *))objc_msgSend;
          replacement = objc_msgSendTyped(self, selectorValue.pointerValue, [identifier substringFromIndex:range.length + 1]);
      }
    

    }];

    return replacement;

}</pre>

- (UIViewController *)kz_replacementControllerWithIdentifier:(NSString *)identifier;
{
    NSDictionary *replacementSchemes = @{
            @"class" : [NSValuevalueWithPointer:@selector(kz_createControllerWithClassIdentifier:)],
            @"storyboard" : [NSValuevalueWithPointer:@selector(kz_loadControllerWithStoryboardIdentifier:)],
            @"xib" : [NSValuevalueWithPointer:@selector(kz_loadControllerWithXibIdentifier:)]
    };
 
    __blockUIViewController *replacement = nil;
    [replacementSchemesenumerateKeysAndObjectsUsingBlock:^(NSString *scheme, NSValue *selectorValue, BOOL *stop) {
        const struct _NSRangerange = [identifierrangeOfString:scheme];
        if (range.location == 0)
        {
            *stop = YES;
            UIViewController *(*objc_msgSendTyped)(id, SEL, NSString *) = (UIViewController *(*)(id, SEL, NSString *))objc_msgSend;
            replacement = objc_msgSendTyped(self, selectorValue.pointerValue, [identifiersubstringFromIndex:range.length + 1]);
        }
    }];
 
    return replacement;
 
}
</div>

現在你只需在容器內的每一個VC執行這個方法。舉個例子,如果你的app使用了TabBar,下面的方法對你每一個UITabBarController的子類都會起作用:

Objective-C

- (void)awakeFromNib;
{
    [super awakeFromNib];
    [super setViewControllers:[self processViewControllersByReplacingMocks:self.viewControllers] animated:NO];
}

  • (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated; { [super setViewControllers:[self processViewControllersByReplacingMocks:viewControllers] animated:animated]; }

  • (void)setViewControllers:(NSArray *)viewControllers; { [super setViewControllers:[self processViewControllersByReplacingMocks:viewControllers]]; }

  • (NSArray )processViewControllersByReplacingMocks:(NSArray )viewControllersAndMocks; { NSMutableArray array = [NSMutableArray new]; [viewControllersAndMocks enumerateObjectsUsingBlock:^(UIViewController viewController, NSUInteger idx, BOOL *stop) {

      UIViewController *replacement = [viewController kz_replacementController];
      [array addObject:replacement ?: viewController];
    

    }]; return [array copy]; }</pre>

    - (void)awakeFromNib;
    {
        [super awakeFromNib];
        [supersetViewControllers:[selfprocessViewControllersByReplacingMocks:self.viewControllers]animated:NO];
    }
     

  • (void)setViewControllers:(NSArray *)viewControllersanimated:(BOOL)animated; {     [supersetViewControllers:[selfprocessViewControllersByReplacingMocks:viewControllers]animated:animated]; }  
  • (void)setViewControllers:(NSArray *)viewControllers; {     [supersetViewControllers:[selfprocessViewControllersByReplacingMocks:viewControllers]]; }  
  • (NSArray )processViewControllersByReplacingMocks:(NSArray )viewControllersAndMocks; {     NSMutableArray array = [NSMutableArray new];     [viewControllersAndMocksenumerateObjectsUsingBlock:^(UIViewController viewController, NSUInteger idx, BOOL stop) {         UIViewController replacement = [viewControllerkz_replacementController];         [arrayaddObject:replacement ?: viewController];     }];     return [arraycopy]; }</pre> </div>

    使用行為(Behaviours)

    如果要給你們推薦一種模式的話,毫無疑問我會推薦“組合”(composition),這是個非常棒的模式,并且能自然而然地帶來其他好的方面。

    行為是一種組合,只能基于Interface Builder配置。

    之前我在 objc.io 的架構指南中寫過。

    運行時屬性和@IBInspectable

    多年前我們就可以直接在IB內設置一些自定義屬性了,但是還有很多人是不知道的。他們在Interface Builder中新建了一個UIView,然后再用代碼來設置View的一些屬性。

    Xcode 6的運行時屬性升級到支持@IBInspectable了,將老且脆弱的運行時屬性:

    修改成了一個可編輯的屬性,我們再也不會拼寫錯誤了:

    可支持的屬性有下列這些類型:

    • Int
    • CGFloat
    • Double
    • String
    • Bool
    • CGPoint
    • CGSize
    • CGRect
    • UIColor
    • UIImage

    擴展UIKit

    你還可以擴展現有的類型,比如UIKit中的類:

    Objective-C

    extension UIView {
      @IBInspectable var kz_borderColor: UIColor? {

      get {
          if let colorRef = layer.borderColor {
              return UIColor(CGColor: colorRef)
          }
    
          return nil
      }
      set {
          layer.borderColor = newValue?.CGColor
      }
    

    } }</pre>

    extensionUIView {
        @IBInspectable varkz_borderColor: UIColor? {
            get {
                if letcolorRef = layer.borderColor {
                    return UIColor(CGColor: colorRef)
                }
     
                return nil
            }
            set {
                layer.borderColor = newValue?.CGColor
            }
        }
    }
    </div>

    給UIView增加了一個漂亮的邊界顏色編輯器

    消除副作用

    給NSLayoutConstraint增加一個標示符會咋樣呢?iOS7增加了一個identifier字段作調試用,不過我在另外的一些地方使用了自定義的identifier。

    我們的應用在不同的iOS設備的顯示略有一些差異,不幸的是Size Classes未能滿足這個特別的設計。

    為了處理這個需求,我創建了一種簡單的DSL語法來描述每一種設備的值:

    Objective-C

    KZ_SPEC(Create, (@{
    @"create.size.captionTextWidth" : kz_spec(260, 250, 234, 220),
    @"create.size.segmentCollectionView.height" : kz_spec(116, 116, 116, 116).offsetBy(16)
    }))
    KZ_SPEC(Create, (@{
      @"create.size.captionTextWidth" : kz_spec(260, 250, 234, 220),
      @"create.size.segmentCollectionView.height" : kz_spec(116, 116, 116, 116).offsetBy(16)
    }))
    </div>

    并且擴展了NSLayoutConstraint,使我們能夠為實際的界面綁定規格。這樣就避免了在view中寫樣板代碼了:

    Objective-C

    @interface NSLayoutConstraint(KZDesignSpec) @property (nonatomic, copy) IBInspectable NSString* kz_specName; @end

    @implementation NSLayoutConstraint (KZDesignSpe) static void const *kDesignSpecKey = &kDesignSpecKey;

    • (NSString *)kz specName; { return objc getAssociatedObject(self, kDesignSpecKey); }

    • (void)setKz specName:(NSString *)kz specName; { objc setAssociatedObject(self, kDesignSpecKey, kz specName, OBJC ASSOCIATION COPY NONATOMIC); self.constant = kz floatForSpec(kz specName); self.identifier = kz specName; } @end

    @interface NSLayoutConstraint(KZDesignSpec)
    @property (nonatomic, copy) IBInspectable NSString kz_specName;
    @end
     
    @implementation NSLayoutConstraint (KZDesignSpe)
    static void const kDesignSpecKey = &kDesignSpecKey;
     

  • (NSString *)kz_specName; {   return objc_getAssociatedObject(self, kDesignSpecKey); }  
  • (void)setKz_specName:(NSString *)kz_specName; {   objc_setAssociatedObject(self, kDesignSpecKey, kz_specName, OBJC_ASSOCIATION_COPY_NONATOMIC);   self.constant = kz_floatForSpec(kz_specName);   self.identifier = kz_specName; } @end</pre> </div>

    快捷鍵和隱藏選項

    確認你知道自己的快捷鍵以及Interface Builder的一些隱藏選項。

    我還記得在我使用了2年的IB后看見一個人使用Media Tab拉出一張圖片,并創建了一個合適尺寸的UIImageView.而這時的我還是手動的……

    快捷鍵有很多, 這里給出了一些不錯的

    當你在設置AutoLayout的時候,記得在拉約束的同時按Option鍵,或者使用編輯菜單。

    總結

    Interface Builder離完美還有很長的路,但它也遠不是一些開發者認為的一個糟糕的工具。

    很多應用不能說是“用Interface Builder來實現太復雜”了,這是很多人在我問他們為什么不用IB的時候的回答。

    FoldifyStorest 都大量地使用IB.

    實際上,很多情況下使用IB足矣,特別是加上我之前給出的擴展,或者是你自己實現的擴展。

    其他情況下使用代碼。代碼或IB,不是一個只能二選一的問題。

來自: http://ios.jobbole.com/83754/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!