日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS Xib Storyboard

發布時間:2023/12/8 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS Xib Storyboard 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

iOS Xib & Storyboard

      • InterfaceBuilder
      • Xib文件
      • Xib解析方式
      • 模擬示例
      • Storyboard
        • storyboard分析
        • storyboard的啟動

在iOS開發中,我們會經常接觸到的 xib 文件,還能聽到它的另一個名字 nib,其實它們倆差不多是指代同一個東西,只不過 xib 是編譯前,nib是編譯后, 還有后來的 storyboard,它們其實都xml文件,通過右鍵這些文件然后 open as > source code 就可以看到文件的源碼。

這次主要從以下幾點分析Xib & Storyboard

  • InterfaceBuilder簡介
  • Xib文件格式分析
  • Xib解析方式猜測
  • 簡單模擬ViewControll加載xib
  • 平時使用xib方式的注意事項
  • 自定義View關聯xib的兩種方式
  • Storyboard簡單介紹
  • Storyboard的啟動

InterfaceBuilder

Interface Builder(縮寫:IB)是用于蘋果公司Mac OS X操作系統的軟件開發程序,是Xcode套件的一部分。Cocoa開發者可以使用Interface Builder來創建和修改應用程序的圖形用戶界面。其數據以XML的形式被儲存在.xib文件中。

Xib文件

如果你仔細比對xib和storyboard的xml的文件內容,你會發現差別很小,其中兩個重要的差別是:

  • storyboard的type是com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB
  • xib的type是com.apple.InterfaceBuilder3.CocoaTouch.XIB
  • storyboard相對于xib多了一個scene的概念, 所有xml里會有一個頂層標簽是scenes而xib里的頂層標簽是objects

xib和storyboard就像一個配置文件,在圖形化界面里將想要的界面搭建好,然后調用系統提供的方法來讀取這些文件來構建一個個對象。

最常用的就是從xib里面初始化ViewController了。在創建ViewController的時候,Xcode會詢問是否創建一個xib,如果選擇是那么和這個ViewController同名的xib將會被創建。

ViewController加載xib流程

- (instancetype)init; //該方法會轉調下面的方法 - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;

以下均可正常展示

1. Test1ViewController *viewController = [[Test1ViewController alloc] init]; [self.navigationController pushViewController:viewController animated:YES];2. Test1ViewController *viewController = [[Test1ViewController alloc] initWithNibName:@"Test1ViewController" bundle:[NSBundle mainBundle]]; [self.navigationController pushViewController:viewController animated:YES];3. Test1ViewController *viewController = [[Test1ViewController alloc] initWithNibName:@"Test2ViewController" bundle:[NSBundle mainBundle]]; [self.navigationController pushViewController:viewController animated:YES];

默認情況下ViewController會找對應類名的xib文件,需要注意的是,xib中如果有connections*(用IB拖出來的線會在conections標簽下生成相關數據)*則ViewController一定要有相對應的IBOutlet,否則會因找不到相應的selector造成crash。

分析一下xib文件

<?xml version="1.0" encoding="UTF-8"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"><device id="retina4_7" orientation="portrait"><adaptation id="fullscreen"/></device><dependencies><deployment identifier="iOS"/><plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/><capability name="Safe area layout guides" minToolsVersion="9.0"/><capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/></dependencies><objects><placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TestCustom"><connections><outlet property="titleLabel" destination="HBy-2B-FHf" id="Aqm-dl-WXp"/><outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/></connections></placeholder><placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/><view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT"><rect key="frame" x="0.0" y="0.0" width="375" height="667"/><autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/><subviews><label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Test for ViewController" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HBy-2B-FHf"><rect key="frame" x="100" y="323" width="175" height="21"/><fontDescription key="fontDescription" type="system" pointSize="17"/><nil key="textColor"/><nil key="highlightedColor"/></label></subviews><color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/><constraints><constraint firstItem="HBy-2B-FHf" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="3Jb-mF-KMo"/><constraint firstItem="HBy-2B-FHf" firstAttribute="centerY" secondItem="i5M-Pr-FkT" secondAttribute="centerY" id="7fQ-gk-QU8"/></constraints><viewLayoutGuide key="safeArea" id="Q5M-cg-NOt"/></view></objects> </document>

xib文件使用的xml格式其中除了objects標簽內數據其他均是配置信息,我們主要分析objects標簽。

File's Owner 標簽

我們用Xcode創建ViewController時勾選xib, 其中Xcode會自動設置為對應的ViewController類, 表現為File’s Owner對應標簽的customClass為ViewController的類名(如下HomeViewController);并且會有一個property="view"的outlet標簽,該標簽和ViewController.view對象關聯(如下outlet標簽)。

<objects><placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="HomeTabViewController"><connections><outlet property="view" destination="iN0-l3-epB" id="HvV-il-KfN"/></connections></placeholder><placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/><view contentMode="scaleToFill" id="iN0-l3-epB">...</view> </objects>

outlet標簽指定了iN0-l3-epB,而iN0-l3-epB就是view的id。

在ViewController中可以通過如下方式拿到對應的view

- (void)viewDidLoad {[super viewDidLoad];self.view }

如果手動刪除xib中view這個outlet標簽,self.view就是nil

其實xib被解析時,如果有outlet則調用對應class的selector,而調用那個selector則是由property這一屬性值決定。

view subviews label 等標簽是布局相關的 constraints標簽是配置約束關系

了解這幾個標簽后,初步知道xib是被解析然后綁定到ViewController上的,接下來更加細化的分析和驗證一下.

Xib解析方式

xib文件的解析是 NSBundle(UINibLoadingAdditions) 完成的,h文件如下

#import <Foundation/Foundation.h> #import <UIKit/UIKitDefines.h>NS_ASSUME_NONNULL_BEGINtypedef NSString * UINibOptionsKey NS_TYPED_ENUM;UIKIT_EXTERN UINibOptionsKey const UINibExternalObjects NS_AVAILABLE_IOS(3_0);@interface NSBundle(UINibLoadingAdditions) - (nullable NSArray *)loadNibNamed:(NSString *)name owner:(nullable id)owner options:(nullable NSDictionary<UINibOptionsKey, id> *)options; @end@interface NSObject(UINibLoadingAdditions) - (void)awakeFromNib NS_REQUIRES_SUPER; - (void)prepareForInterfaceBuilder NS_AVAILABLE_IOS(8_0); @endUIKIT_EXTERN NSString * const UINibProxiedObjectsKey NS_DEPRECATED_IOS(2_0, 3_0) __TVOS_PROHIBITED;NS_ASSUME_NONNULL_END

import了這個h文件后,任何對象就會新增兩個方法 awakeFromNib prepareForInterfaceBuilder xib加載好 (不包含布局好,這時候控件的位置和大小還未完全確定) 就會調對應Class的這兩個方法。

由于看不到源碼,所以只能猜了,下面就根據猜測模擬一下

模擬示例

簡單模擬ViewController加載

TestCustom.h

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface TestCustom : UIResponder@endNS_ASSUME_NONNULL_END

TestCustom.m

#import <UIKit/UIKit.h>#import "TestCustom.h"@interface TestCustom()//這兩行可以手動加,也可以改一下xib中File's owner后鼠標拖出 @property (weak, nonatomic) IBOutlet UIView *view; @property (weak, nonatomic) IBOutlet UILabel *titleLabel;@end@implementation TestCustom- (instancetype)init {self = [super init];if (self) {[self setup];}return self; }- (void)setup {//調用loadNibName:::方法, owner設置成self,這樣解析outlet時就會調owner對應的方法[[NSBundle mainBundle] loadNibNamed:@"TestCustom" owner:self options:nil];NSLog(@"TestCustom, setup, view = %@, titleLabel = %@", self.view, self.titleLabel); }- (void)awakeFromNib {NSLog(@"TestCustom, awakeFromNib"); }@end

Log:

TestCustom, setup
view = <UIView: 0x7fcf84f53f10;...>
titleLabel = <UILabel: 0x7fcf84f6c4c0;...>

TestCustom.xib文件

<objects><placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TestCustom"><connections><outlet property="titleLabel" destination="HBy-2B-FHf" id="Aqm-dl-WXp"/><outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/></connections></placeholder><placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/><view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT"><rect key="frame" x="0.0" y="0.0" width="375" height="667"/><autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/><subviews><label opaque="NO" ...... id="HBy-2B-FHf"><rect key="frame" x="60" y="48" width="92" height="21"/><fontDescription key="fontDescription" type="system" pointSize="17"/><nil key="textColor"/><nil key="highlightedColor"/></label></subviews>......</view> </objects>

解析 <outlet property="titleLabel" ..."/> 和 <outlet property="view" ..."/> 時調了TestCustom設置titleLabel的selector和設置view的selector,如果刪除TestCustom中的 IBOutlet UIView *view 和 IBOutlet UILabel *titleLabel 就會出crash,如下

*** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<TestCustom 0x600002709660> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key titleLabel.’

*** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<TestCustom 0x600000662370> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key view.’

如果再將view放到window上那一個可見的界面就產生了,同時TestCustom繼承了UIResponder, 不僅能看到界面還能處理用戶操作,如果再維護一個NavigationController就完完全全是一個ViewController了,當然肯定還有其他很多的細節.

###自定義View實現方式

第一種:

#import "Test1View.h"@interface Test1View()@property (weak, nonatomic) IBOutlet UILabel *titleLabel;@end@implementation Test1View+ (instancetype)view {return [[[NSBundle mainBundle] loadNibNamed:@"Test1" owner:nil options:nil] firstObject]; }- (void)awakeFromNib {[super awakeFromNib];NSLog(@"Test1View, awakeFromNib"); }- (void)layoutSubviews {[super layoutSubviews];NSLog(@"Test1View, layoutSubviews"); }@end

對應的xib文件

<objects><placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/><placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/><view contentMode="scaleToFill" id="iN0-l3-epB" customClass="Test1View">...<connections><outlet property="titleLabel" destination="Hcy-Sp-Hbg" id="znM-fY-xwR"/></connections></view> </objects>

log都正常

xib中 File's Owner 沒有指定 customClass , 代碼上 owner 為空

xib中objects下一層也沒有outlet;

xib中view的下一層添加了outlet 同時指定了view是由Test1View實現的,xib解析時就會創建Test1View并調Test1View的titleLabel的設置方法。

這種方式需要注意view的customClass一定要有對應的IBOutlet,否則會Crash,正常Xcode手動操作不會出問題,切記手動修改類名或者xib文件名.


第二種:

#import "Test2View.h"@interface Test2View()@property (weak, nonatomic) IBOutlet UIView *view; @property (weak, nonatomic) IBOutlet UILabel *titleLabel;@end@implementation Test2View+ (instancetype)view {return [[Test2View alloc] init]; }- (instancetype)init {self = [super init];if (self) {[[NSBundle mainBundle] loadNibNamed:@"Test2" owner:self options:nil];[self addSubview:self.view];NSLog(@"Test2View, init, view = %@, titleLabel = %@", self.view, self.titleLabel);}return self; }- (void)awakeFromNib {[super awakeFromNib];NSLog(@"Test2View, awakeFromNib"); }- (void)layoutSubviews {[super layoutSubviews];NSLog(@"Test2View, layoutSubviews"); }@end

對應的xib文件

<objects><placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="Test2View"><connections><outlet property="titleLabel" destination="Hcy-Sp-Hbg" id="3Mf-4P-MnA"/><outlet property="view" destination="iN0-l3-epB" id="HvV-il-KfN"/></connections></placeholder><placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/><view contentMode="scaleToFill" id="iN0-l3-epB">。。。</view> </objects>

log中沒有awakeFromNib, 從xib解析中實例化的view才會有awakeFromNib的回調,這一點可以通過在xib中view的實現者來驗證

xib中 File's Owner 指定了 customClass , 代碼上 owner 指定了 self

xib中objects有outlet,對應owner有相關的IBOutlet,xib解析時就會owner的titleLabel和view的設置方法,

這種方式需要注意owner一定要有對應的IBOutlet,否則會Crash,正常xcode手動操作不會出問題,切記手動修改類名或者xib文件名。


第一種和第二種的區別:

  • 第一種拿到的是xib中objects第一層級的view,xib加載生成的實例
  • 第二種拿到的是添加outlet的View,該view的實例也是xib加載生成,只是通過IBOutlet給了Test2View.view
  • 綜上并結合ViewController的xib內容,可以斷定ViewController用的就是第二種方式,并且ViewController - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil; 方法中調了 [[NSBundle mainBundle] loadNibNamed:@"XXX" owner:self options:nil]

    Storyboard

    storyboard相對于xib多了一個scene的概念,xml里會有一個頂層標簽是scenes而xib里的頂層標簽是objects。

    storyboard分析

    要介紹Storyboard是什么,從這張圖講起:

    上面是Main.storyboard的內容,從左到右依次是 Navigation Controller Scenc View Controller Scenc Tab2 View Controller Scene Tab1 View Controller Scene

    Tab1ViewController和Tab2ViewController對應的線分別是MainButton1和MainButton2,這樣點擊Button1就會跳轉到Tab1ViewController點擊Button2就會跳到Tab2ViewController

    看一下Main.stroyboard文件

    ... <scene sceneID="tne-QT-ifu"><objects><viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController"><view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">...<subviews><label ...><button .../><connections><segue destination="D7z-E4-gRL" kind="push" id="1Of-px-wpF"/></connections></button><button ...></subviews>...</view><navigationItem key="navigationItem" id="jgj-Ur-zGs"/></viewController>...</objects><point key="canvasLocation" x="1199" y="167"/> </scene> ... ... <!--Tab1 View Controller--> <scene sceneID="zkj-DY-y2h"><objects><viewController id="D7z-E4-gRL" customClass="Tab1ViewController" sceneMemberID="viewController"><view key="view" contentMode="scaleToFill" id="exE-0e-ynF">...</view><navigationItem key="navigationItem" id="s1g-wa-3DJ"/></viewController>...</objects><point key="canvasLocation" x="2703" y="54"/> </scene> ... ... <!--Tab2 View Controller--> <scene sceneID="7uO-pe-oCU"><objects><viewController id="VPd-VG-xrk" customClass="Tab2ViewController" sceneMemberID="viewController"><view key="view" contentMode="scaleToFill" id="7CO-RC-wiG">...</view></viewController>...</objects><point key="canvasLocation" x="2004" y="495"/> </scene> ...

    上面分析xib時我們知道xib文件中objects標簽包含的為主要內容,storyboard是以scenes標簽頁包含的為主要內容,每一個scene下包含objects. storyboard相當于多個xib的集合。

    其中點擊button跳轉ViewController在segue標簽內聲明。button點擊自動跳轉其實是storyboard解析時給button添加了targets, 如下是加斷點打印出button的targets

    2019-03-08 11:15:13.892396+0800 LayoutTest[13748:366986] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform. (lldb) po self.tab1Button.allTargets {(<UIStoryboardPushSegueTemplate: 0x600003724fc0> )}(lldb)

    storyboard和xib的本質都一樣:在解析中執行代碼

    storyboard的啟動

    默認情況下storyboard是這樣啟動

    info.plist 中 Main storyboard file base name 這一項指定了app啟動的storyboard,其實也是執行代碼,把這個配置刪掉用代碼如下:

    AppDelegate.h

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];UIStoryboard * storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];self.window.rootViewController = [storyBoard instantiateInitialViewController];self.window.backgroundColor = [UIColor whiteColor];[self.window makeKeyAndVisible];return YES; }

    UIStoryboard.h

    #import <Foundation/Foundation.h> #import <UIKit/UIKitDefines.h>@class UIViewController;NS_ASSUME_NONNULL_BEGINNS_CLASS_AVAILABLE_IOS(5_0) @interface UIStoryboard : NSObject { }+ (UIStoryboard *)storyboardWithName:(NSString *)name bundle:(nullable NSBundle *)storyboardBundleOrNil;- (nullable __kindof UIViewController *)instantiateInitialViewController; - (__kindof UIViewController *)instantiateViewControllerWithIdentifier:(NSString *)identifier;@endNS_ASSUME_NONNULL_END

    storyboard是UIStoryboard類解析的,并且storyboard的啟動其實就是指定一個ViewController作為RootViewController。

    storyboard相比于xib, 其實是一個包含的關系,storyboard只是多出了scenes這個,至于這些標簽和xib思路是想通的,具體什么含義就不再贅述。

    使用Storyboard可以更好地了解App中所有的視圖以及它們之間的關聯的概況。掌控全局更加容易,因為所有的設計都包含在一個文件中,而不是分散在很多單獨的xib文件中,當然這也是storyboard的一個弊端,都集中在一個文件不利于團隊開發,所以在平時的開發還是要以具體情況分析。

    總結

    以上是生活随笔為你收集整理的iOS Xib Storyboard的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。