談談 MVX 中的 View

Follow GitHub: Draveness

這是談談 MVX 系列的第二篇文章,上一篇文章中對 iOS 中 Model 層的設計進行了簡要的分析;而在這里语泽,我們會對 MVC 中的視圖層進行討論,談一談現有的視圖層有著什么樣的問題刽漂,如何在框架的層面上去改進僻焚,同時與服務端的視圖層進行對比,分析它們的差異衅疙。

UIKit

UIKit 是 Cocoa Touch 中用于構建和管理應用的用戶界面的框架莲趣,其中幾乎包含著與 UI 相關的全部功能,而我們今天想要介紹的其實是 UIKit 中與視圖相關的一部分饱溢,也就是 UIView 以及相關類喧伞。

UIView 可以說是 iOS 中用于渲染和展示內容的最小單元,作為開發(fā)者能夠接觸到的大多數屬性和方法也都由 UIView 所提供,比如最基本的布局方式 frame 就是通過 UIView 的屬性所控制潘鲫,在 Cocoa Touch 中的所有布局系統(tǒng)最終都會轉化為 CFRect 并通過 frame 的方式完成最終的布局翁逞。

Frame-And-Components

UIView 作為 UIKit 中極為重要的類,它的 API 以及設計理念決定了整個 iOS 的視圖層該如何工作溉仑,這也是理解視圖層之前必須要先理解 UIView 的原因挖函。

UIView

在 UIKit 中,除了極少數用于展示的類不繼承自 UIView 之外浊竟,幾乎所有類的父類或者或者祖先鏈中一定會存在 UIView怨喘。

UIView-And-Subclasses

我們暫且拋開不繼承自 UIViewUIBarItem 類簇不提,先通過一段代碼分析一下 UIView 具有哪些特性振定。

UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"backgoundImage"]];
UIImageView *logoView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"logo"]];

UIButton *loginButton = [[UIButton alloc] init];
[loginButton setTitle:@"登錄" forState:UIControlStateNormal];
[loginButton setTitleColor:UIColorFromRGB(0xFFFFFF) forState:UIControlStateNormal];
[loginButton.titleLabel setFont:[UIFont boldSystemFontOfSize:18]];
[loginButton setBackgroundColor:UIColorFromRGB(0x00C3F3)];

[self.view addSubview:backgroundView];
[backgroundView addSubview:logoView];
[backgroundView addSubview:loginButton];

UIView 作為視圖層大部分元素的根類必怜,提供了兩個非常重要的特性:

  • 由于 UIView 具有 frame 屬性,所以為所有繼承自 UIView 的類提供了絕對布局相關的功能后频,也就是在 Cocoa Touch 中梳庆,所有的視圖元素都可以通過 frame 設置自己在父視圖中的絕對布局;

  • UIView 在接口中提供了操作和管理視圖層級的屬性和方法卑惜,比如 superview靠益、subviews 以及 -addSubview: 等方法;

    @interface UIView (UIViewHierarchy)
    
    @property (nullable, nonatomic, readonly) UIView       *superview;
    @property (nonatomic, readonly, copy) NSArray<__kindof UIView *> *subviews;
    
    - (void)addSubview:(UIView *)view;
    
    ...
    
    @end
    

    也就是說 UIView 和它所有的子類都可以擁有子視圖残揉,成為容器并包含其他 UIView 的實例胧后。

    [self.view addSubview:backgroundView];
    [backgroundView addSubview:logoView];
    [backgroundView addSubview:loginButton];
    

這種使用 UIView 同時為子類提供默認的 frame 布局以及子視圖支持的方式在一定程度上能夠降低視圖模型的復雜度:因為所有的視圖都是一個容器,所以在開發(fā)時不需要區(qū)分視圖和容器抱环,但是這種方式雖然帶來了一些方便壳快,但是也不可避免地帶來了一些問題。

UIView 與布局

在早期的 Cocoa Touch 中镇草,整個視圖層的布局都只是通過 frame 屬性來完成的(絕對布局)眶痰,一方面是因為在 iPhone5 之前,iOS 應用需要適配的屏幕尺寸非常單一梯啤,完全沒有適配的兼容問題竖伯,所以使用單一的 frame 布局方式完全是可行的。

但是在目前各種屏幕尺寸的種類暴增的情況下因宇,就很難使用 frame 對所有的屏幕進行適配七婴,在這時蘋果就引入了 Auto Layout 采用相對距離為視圖層的元素進行布局。

AutoLayout

不過察滑,這算是蘋果比較失敗的一次性嘗試打厘,主要是因為使用 Auto Layout 對視圖進行布局實在太過復雜,所以剛出來的時候也不溫不火贺辰,很少有人使用户盯,直到 Masonry 的出現使得編寫 Auto Layout 代碼沒有那么麻煩和痛苦才普及起來嵌施。

但是由于 Auto Layout 的工作原理實際上是解 N 元一次方程組,所以在遇到復雜視圖時莽鸭,會遇到非常嚴重的性能問題吗伤,如果想要了解相關的問題的話,可以閱讀 從 Auto Layout 的布局算法談性能 這篇文章硫眨,在這里就不再贅述了牲芋。

然而 Auto Layout 的相對布局雖然能夠在一定程度上解決適配屏幕大小和尺寸接近的適配問題,比如 iPhone4s捺球、iPhone5、iPhone6 Plus 等移動設備夕冲,或者iPad 等平板設備氮兵。但是,Auto Layout 不能通過一套代碼打通 iPhone 和 iPad 之間布局方式的差異歹鱼,只能通過代碼中的 if 和 else 進行判斷泣栈。

在這種背景下,蘋果做了很多的嘗試弥姻,比如說 Size-Class-Specific Layout南片,Size Class 將屏幕的長寬分為三種:

  • Compact
  • Regular
  • Any

這樣就出現了最多 3 x 3 的組合,比如屏幕寬度為 Compact 高度為 Regular 等等庭敦,它與 Auto Layout 一起工作省去了一些 if 和 else 的條件判斷疼进,但是從實際效果上來說,它的用處并不是特別大秧廉,而且使用代碼來做 Size Class 的相關工作依然非常困難伞广。

除了 Auto Layout 和 Size Class 之外,蘋果在 iOS9 還推出了 UIStackView 來增加 iOS 中的布局方式和手段疼电,這是一種類似 flexbox 的布局方式嚼锄。

雖然 UIStackView 可以起到一定的作用,但是由于大多數 iOS 應用都要求對設計稿進行嚴格還原并且其 API 設計相對啰嗦蔽豺,開發(fā)者同時也習慣了使用 Auto Layout 的開發(fā)方式区丑,在慣性的驅動下,UIStackView 應用的也不是非常廣泛修陡。

UIStackVie

不過現在很多跨平臺的框架都是用類似 UIStackView 的方式進行布局沧侥,比如 React Native、Weex 等魄鸦,其內部都使用 Facebook 開源的 Yoga正什。

由于 flexbox 以及類似的布局方式在其他平臺上都有類似的實現,并且其應用確實非常廣泛号杏,筆者認為隨著工具的完善婴氮,這種布局方式會逐漸進入 iOS 開發(fā)者的工具箱中斯棒。

三種布局方式 frame、Auto Layout 以及 UIStackView 其實最終布局都會使用 frame主经,其他兩種方式 Auto Layout 和 UIStackView 都會將代碼描述的布局轉換成 frame 進行荣暮。

布局機制的混用

Auto Layout 和 UIStackView 的出現雖然為布局提供了一些方便,但是也增加了布局系統(tǒng)的復雜性罩驻。

因為在 iOS 中幾乎所有的視圖都繼承自 UIView穗酥,這樣也同時繼承了 frame 屬性,在使用 Auto Layout 和 UIStackView 時惠遏,并沒有禁用 frame 布局砾跃,所以在混用卻沒有掌握技巧時可能會有一些比較奇怪的問題。

其實节吮,在混用 Auto Layout 和 frame 時遇到的大部分奇怪的問題都是因為 translatesAutoresizingMaskIntoConstraints 屬性沒有被正確設置的原因抽高。

If this property’s value is true, the system creates a set of constraints that duplicate the behavior specified by the view’s autoresizing mask. This also lets you modify the view’s size and location using the view’s frame, bounds, or center properties, allowing you to create a static, frame-based layout within Auto Layout.

在這里就不詳細解釋該屬性的作用和使用方法了。

對動畫的影響

在 Auto Layout 出現之前透绩,由于一切布局都是使用 frame 工作的翘骂,所以在 iOS 中完成對動畫的編寫十分容易。

UIView.animate(withDuration: 1.0) { 
    view.frame = CGRect(x: 10, y: 10, width: 200, height: 200)
}

而當大部分的 iOS 應用都轉而使用 Auto Layout 之后帚豪,對于視圖大小碳竟、位置有關的動畫就比較麻煩了:

topConstraint.constant = 10
leftConstraint.constant = 10
heightConstraint.constant = 200
widthConstraint.constant = 200
UIView.animate(withDuration: 1.0) {
    view.layoutIfNeeded()
}

我們需要對視圖上的約束對象一一修改并在最后調用 layoutIfNeeded 方法才可以完成相同的動畫。由于 Auto Layout 對動畫的支持并不是特別的優(yōu)秀狸臣,所以在很多時候筆者在使用 Auto Layout 的視圖上莹桅,都會使用 transform 屬性來改變視圖的位置,這樣雖然也沒有那么的優(yōu)雅烛亦,不過也是一個比較方便的解決方案统翩。

lottie

frame 的問題

每一個 UIViewframe 屬性其實都是一個 CGRect 結構體,這個結構體展開之后有四個組成部分:

  • origin
    • x
    • y
  • size
    • width
    • height

當我們設置一個 UIView 對象的 frame 屬性時此洲,其實是同時設置了它在父視圖中的位置和它的大小厂汗,從這里可以獲得一條比較重要的信息:

iOS 中所有的 UIView 對象都是使用 frame 布局的,否則 frame 中的 origin 部分就失去了意義呜师。

但是如果為 UIStackView 中的視圖設置 frame 的話娶桦,這個屬性就完全沒什么作用了,比如下面的代碼:

UIStackView *stackView = [[UIStackView alloc] init];
stackView.frame = self.view.frame;
[self.view addSubview:stackView];

UIView *greenView = [[UIView alloc] init];
greenView.backgroundColor = [UIColor greenColor];
greenView.frame = CGRectMake(0, 0, 100, 100);
[stackView addArrangedSubview:greenView];

UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
redView.frame = CGRectMake(0, 0, 100, 100);
[stackView addArrangedSubview:redView];

frame 屬性在 UIStackView 上基本上就完全失效了汁汗,我們還需要使用約束來控制 UIStackView 中視圖的大小衷畦,不過如果你要使用 frame 屬性來查看視圖在父視圖的位置和大小,在恰當的時機下是可行的知牌。

談談 origin

但是 frame 的不正確使用會導致視圖之間的耦合祈争,如果內部視圖設置了自己在父視圖中的 origin,但是父視圖其實并不會使用直接 frame 布局該怎么辦角寸?比如菩混,父視圖是一個 UIStackView忿墅,它就會重寫子視圖的 origin 甚至是沒有正確設置的 size 屬性。

最重要的是 UIViewframe 的設計導致了視圖之間可能會有較強的耦合沮峡,因為子視圖不應該知道自己在父視圖中的位置疚脐,它應該只關心自己的大小。

也就是作為一個簡單的 UIView 它應該只能設置自己的 size 而不是 origin邢疙,因為父視圖可能是一個 UIStackView 也可能是一個 UITableView 甚至是一個扇形的視圖也不是不可能棍弄,所以位置這一信息并不是子視圖應該關心的

如果視圖設置了自己的 origin 其實也就默認了自己的父視圖一定是使用 frame 進行布局的疟游,而一旦依賴于外部的信息呼畸,它就很難進行復用了。

再談 size

關于視圖大小的確認颁虐,其實也是有一些問題的蛮原,因為視圖在布局時確實可能依賴于父視圖的大小,或者更確切的說是需要父視圖提供一個可供布局的大小聪廉,然后讓子視圖通過這個 CGSize 返回一個自己需要的大小給父視圖。

texture

這種計算視圖大小的方式故慈,其實比較像 Texture 也就是原來的 AsyncDisplayKit 中對于布局系統(tǒng)的實現板熊。

父視圖通過調用子視圖的 -layoutSpecThatFits: 方法獲取子視圖布局所需要的大小,而子視圖通過父視圖傳入的 CGSizeRange 來設置自己的大小察绷。

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
    ...
}

通過這種方式干签,子視圖對父視圖一無所知,它不知道父視圖的任何屬性拆撼,只通過 -layoutSpecThatFits: 方法傳入的參數進行布局容劳,實現了解耦以及代碼復用。

storyboard 和 xib

小結

由于確實需要對多尺寸的屏幕進行適配闸度,蘋果推出 Auto Layout 和 UIStackView 的初衷也沒有錯竭贩,但是在筆者看來,因為絕大部分視圖都繼承自 UIView莺禁,所以在很多情況下并沒有對開發(fā)者進行強限制留量,比如在使用 UIStackView 時只能使用 flexbox 式的布局,在使用 Auto Layout 時也只能使用約束對視圖進行布局等等哟冬,所以在很多時候會帶來一些不必要的問題楼熄。

同時 UIView 中的 frame 屬性雖然在一開始能夠很好的解決的布局的問題,但是隨著布局系統(tǒng)變得越來越復雜浩峡,使得很多 ?UI 組件在與非 frame 布局的容器同時使用時產生了沖突可岂,最終破壞了良好的封裝性。

到目前為止 iOS 中的視圖層的問題主要就是 UIView 作為視圖層中的上帝類翰灾,提供的 frame 布局系統(tǒng)不能良好的和其他布局系統(tǒng)工作缕粹,在一些時候 frame 屬性完全成為了擺設稚茅。

其他平臺對視圖層的設計

在接下來的文章中,我們會介紹和分析其他平臺 Android致开、Web 前端以及后端是如何對視圖層進行設計的峰锁。

Android 與 View

與 iOS 上使用命令式的風格生成界面不同,Android 使用聲明式的 XML 對界面進行描述双戳,在這里舉一個最簡單的例子:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.draveness.myapplication.DisplayMessageActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="TextView"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

整個 XML 文件同時描述了視圖的結構和樣式虹蒋,而這也是 Android 對于視圖層的設計方式,將結構和樣式混合在一個文件中飒货。

我們首先來分析一下上述代碼的結構魄衅,整個 XML 文件中只有兩個元素,如果我們去掉其中所有的屬性塘辅,整個界面的元素就是這樣的:

<ConstraintLayout>
    <TextView/>
</ConstraintLayout>

由一個 ConstraintLayout 節(jié)點包含一個 TextView 節(jié)點晃虫。

View 和 ViewGroup

我們再來看一個 Android 中稍微復雜的視圖結構:

<LinearLayout>
    <RelativeLayout>
        <ImageView/>
        <LinearLayout>
            <TextView/>
            <TextView/>
        </LinearLayout>
    </RelativeLayout>
    <View/>
</LinearLayout>

上面的 XML 代碼描述了一個更加復雜的視圖樹,這里通過一張圖更清晰地展示該視圖表示的結構:

Android-View-Tree

我們可以發(fā)現扣墩,Android 的視圖其實分為兩類:

  • 一類是不能有子節(jié)點的視圖哲银,比如 ViewImageViewTextView 等呻惕;
  • 另一類是可以有子節(jié)點的視圖荆责,比如 LinearLayoutRelativeLayout 等;

在 Android 中亚脆,這兩類的前者都是 View 的子類做院,也就是視圖;后者是 ViewGroup 的子類濒持,它主要充當視圖的容器键耕,與它的子節(jié)點以樹形的結構形成了一個層次結構。

這種分離視圖和容器的方式很好的分離了職責柑营,將管理和控制子視圖的功能劃分給了 ViewGroup屈雄,將顯示內容的職責拋給了 View 對各個功能進行了合理的拆分。

子視圖的布局屬性只有在父視圖為特定 ViewGroup 時才會激活官套,否則就會忽略在 XML 中聲明的屬性棚亩。

混合的結構與樣式

在使用 XML 或者類 XML 的這種文本來描述視圖層的內容時,總會遇到一種無法避免的爭論:樣式到底應該放在哪里虏杰?上面的例子顯然說明了 Android 對于這一問題的選擇讥蟆,也就是將樣式放在 XML 結構中。

這一章節(jié)中并不會討論樣式到底應該放在哪里這一問題纺阔,我們會在后面的章節(jié)中具體討論瘸彤,將樣式放在 XML 結構中和單獨使用各自的優(yōu)缺點。

Web 前端

隨著 Web 前端應用變得越來越復雜笛钝,在目前的大多數 Web 前端項目的實踐中质况,我們已經會使用前后端分離方式開發(fā) Web 應用愕宋,而 Web 前端也同時包含 Model、View 以及 Controller 三部分结榄,不再通過服務端直接生成前端的 HTML 代碼了中贝。

html-css

現在最流行的 Web 前端框架有三個,分別是 React臼朗、Vue 和 Angular邻寿。不過,這篇文章會以最根本的 HTML 和 CSS 為例视哑,簡單介紹 Web 前端中的視圖層是如何工作的绣否。

<div>
  <h1 class="text-center">Header</h1>
</div>

.text-center {
  text-align: center;
}

在 HTML 中其實并沒有視圖和容器這種概念的劃分,絕大多數的元素節(jié)點都可以包含子節(jié)點挡毅,只有少數的無內容標簽蒜撮,比如說 brhr跪呈、img段磨、inputlink 以及 meta 才不會解析自己的子節(jié)點耗绿。

分離的結構與樣式

與 Android 在定義視圖時苹支,使用混合的結構與樣式不同,Web 前端在視圖層中缭乘,采用 HTML 與 CSS 分離沐序,即結構與樣式分離的方式進行設計琉用;雖然在 HTML 中堕绩,我們也可以使用 style 將 CSS 代碼寫在視圖層的結構中,不過在一般情況下邑时,我們并不會這么做奴紧。

<body style="background-color:powderblue;">
</body>

結構與樣式

在這一章節(jié)中,我們會對結構與樣式組織方式之間的優(yōu)劣進行簡單的討論晶丘。

Android 和 Web 前端使用不同的方式對視圖層的結構和樣式進行組織黍氮,前者使用混合的方式,后者使用分離的結構和樣式浅浮。

相比于分離的組織方式沫浆,混合的組織方式有以下的幾個優(yōu)點:

  • 不需要實現元素選擇器,降低視圖層解析器實現的復雜性滚秩;
  • 元素的樣式是內聯(lián)的专执,對于元素的樣式的定義一目了然,不需要考慮樣式的繼承等復雜特性郁油;

分離的組織方式卻正相反:

  • 元素選擇器的實現本股,增加了 CSS 樣式代碼的復用性攀痊,不需要多次定義相同的樣式;
  • 將 CSS 代碼從結構中抽離能夠增強 HTML 的可讀性拄显,可以非常清晰苟径、直觀的了解 HTML 的層級結構;

對于結構與樣式躬审,不同的組織方式能夠帶來不同的收益棘街,這也是在設計視圖層時需要考慮的事情,我們沒有辦法在使用一種組織方式時獲得兩種方式的優(yōu)點盒件,只能盡可能權衡利弊蹬碧,選擇最合適的方法。

后端的視圖層

這一章節(jié)將會研究一下后端視圖層的設計炒刁,不過在真正開始分析其視圖層設計之前恩沽,我們需要考慮一個問題,后端的視圖層到底是什么翔始?它有客戶端或者 Web 前端中的用于展示內容視圖層么罗心?

這其實是一個比較難以回答的問題,不過嚴格意義上的后端是沒有用于展示內容的視圖層的城瞎,也就是為客戶端提供 API 接口的后端渤闷,它們的視圖層,其實就是用于返回 JSON 的模板脖镀。

json.extract! user, :id, :mobile, :nickname, :gender, :created_at, :updated_at
json.url user_url user, format: :json

在 Ruby on Rails 中一般都是類似于上面的 jbuilder 代碼飒箭。擁有視圖層的后端應用大多都是使用了模板引擎技術,直接為 HTTP 請求返回渲染之后的 HTML 和 CSS 等前端代碼蜒灰。

總而言是弦蹂,使用了模板引擎的后端應用其實是混合了 Web 前端和后端,整個服務的視圖層其實就是 Web 前端的代碼强窖;而現在的大多數 Web 應用凸椿,由于遵循了前后端分離的設計,兩者之間的通信都使用約定好的 API 接口翅溺,所以后端的視圖層其實就是單純的用于渲染 JSON 的代碼脑漫,比如 Rails 中的 jbuilder。

理想中的視圖層

iOS 中理想的視圖層需要解決兩個最關鍵的問題:

  1. 細分 UIView 的職責咙崎,將其分為視圖和容器兩類优幸,前者負責展示內容,后者負責對子視圖進行布局褪猛;
  2. 去除整個視圖層對于 frame 屬性的依賴网杆,不對外提供 frame 接口,每個視圖只能知道自己的大小跛璧;

解決上述兩個問題的辦法就是封裝原有的 UIView 類严里,使用組合模式為外界提供合適的接口。

Node-Delegate-UIVie

細分 UIView 的職責

Node 會作為 UIView 的代理追城,同時也作為整個視圖層新的根類刹碾,它將屏蔽掉外界與 UIView 層級操作的有關方法,比如說:-addSubview: 等座柱,同時迷帜,它也會屏蔽掉 frame 屬性,這樣每一個 Node 類的實例就只能設置自己的大小了色洞。

public class Node: Buildable {
    public typealias Element = Node
    public let view: UIView = UIView()
    
    @discardableResult 
    public func size(_ size: CGSize) -> Element {
        view.size = size
        return self
    }    
}

上面的代碼簡單說明了這一設計的實現原理戏锹,我們可以理解為 Node 作為 UIView 的透明代理,它不提供任何與視圖層級相關的方法以及 frame 屬性火诸。

Node-Delegate-Filte

容器的實現

除了添加一個用于展示內容的 Node 類锦针,我們還需要一個 Container 的概念,提供為管理子視圖的 API 和方法置蜀,在這里奈搜,我們添加了一個空的 Container 協(xié)議:

public protocol Container { }

利用這個協(xié)議,我們構建一個 iOS 中最簡單的容器 AbsoluteContainer盯荤,內部使用 frame 對子視圖進行布局馋吗,它應該為外界提供添加子視圖的接口,在這里就是 build(closure:) 方法:

public class AbsoluteContainer: Node, Container {
    typealias Element = AbsoluteContainer
    @discardableResult 
    public func build(closure: () -> Node) -> Relation<AbsoluteContainer> {
        let node = closure()
        view.addSubview(node.view)
        return Relation<AbsoluteContainer>(container: self, node: node)
    }
}

該方法會在調用后返回一個 Relation 對象秋秤,這主要是因為在這種設計下的 origin 或者 center 等屬性不再是 Node 的一個接口宏粤,它應該是 Node 節(jié)點出現在 AbsoluteContainer 時的產物,也就是說灼卢,只有在這兩者同時出現時绍哎,才可以使用這些屬性更新 Node 節(jié)點的位置:

public class Relation<Container> {
    public let container: Container
    public let node: Node

    public init(container: Container, node: Node) {
        self.container = container
        self.node = node
    }
}

public extension Relation where Container == AbsoluteContainer {
    @discardableResult 
    public func origin(_ origin: CGPoint) -> Relation {
        node.view.origin = origin
        return self
    }
}

這樣就完成了對于 UIView 中視圖層級和位置功能的剝離,同時使用透明代理以及 RelationNode 提供其他用于設置視圖位置的接口芥玉。

這一章節(jié)中的代碼都來自于 Mineral蛇摸,如果對代碼有興趣的讀者备图,可以下載自行查看灿巧。

總結

Cocoa Touch 中的 UIKit 對視圖層的設計在一開始確實是沒有問題的,主要原因是在 iOS 早期的布局方式并不復雜揽涮,只有單一的 frame 布局抠藕,而這種方式也恰好能夠滿足整個平臺對于 iOS 應用開發(fā)的需要,但是隨著屏幕尺寸的增多蒋困,蘋果逐漸引入的其它布局方式與原有的體系發(fā)生了一些沖突盾似,導致在開發(fā)時可能遇到奇怪的問題,而這也是本文想要解決的,將原有屬于 UIView 的職責抽離出來零院,提供更合理的抽象溉跃。

References

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市告抄,隨后出現的幾起案子撰茎,更是在濱河造成了極大的恐慌,老刑警劉巖打洼,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件龄糊,死亡現場離奇詭異,居然都是意外死亡募疮,警方通過查閱死者的電腦和手機炫惩,發(fā)現死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阿浓,“玉大人他嚷,你說我怎么就攤上這事“疟校” “怎么了裳仆?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長硅堆。 經常有香客問我皂冰,道長,這世上最難降的妖魔是什么苛聘? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任涂炎,我火速辦了婚禮,結果婚禮上设哗,老公的妹妹穿的比我還像新娘唱捣。我一直安慰自己,他們只是感情好网梢,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布震缭。 她就那樣靜靜地躺著,像睡著了一般战虏。 火紅的嫁衣襯著肌膚如雪拣宰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天烦感,我揣著相機與錄音巡社,去河邊找鬼。 笑死手趣,一個胖子當著我的面吹牛晌该,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朝群,長吁一口氣:“原來是場噩夢啊……” “哼燕耿!你這毒婦竟也來了?” 一聲冷哼從身側響起姜胖,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤缸棵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谭期,有當地人在樹林里發(fā)現了一具尸體堵第,經...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年隧出,在試婚紗的時候發(fā)現自己被綠了踏志。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡胀瞪,死狀恐怖针余,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情凄诞,我是刑警寧澤圆雁,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站帆谍,受9級特大地震影響伪朽,放射性物質發(fā)生泄漏。R本人自食惡果不足惜汛蝙,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一烈涮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窖剑,春花似錦坚洽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至需了,卻和暖如春跳昼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背援所。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工庐舟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留欣除,地道東北人住拭。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親滔岳。 傳聞我的和親對象是個殘疾皇子杠娱,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內容

  • 目錄 0、前言 一谱煤、Auto Layout前世今生 二摊求、Auto Layout基礎知識 1.Auto Layout...
    浮游lb閱讀 24,485評論 3 89
  • 發(fā)現 關注 消息 iOS 第三方庫、插件刘离、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,094評論 4 62
  • 威廉·荷加斯(1697-1764)室叉,出生于倫敦一個清苦的教師家庭,他從小沒有受過正規(guī)的藝術教育硫惕,15歲時父親把他送...
    莫問2017閱讀 388評論 0 0
  • 我會努力的茧痕,不管前面是什么。
    正能量L7閱讀 135評論 0 0
  • 看完《北京遇上西雅圖2之不二情書》后恼除,形形色色的觀眾都被安利了一本書《查令十字街84號》踪旷,我這么容易被煽動的...
    夢竹草閱讀 413評論 0 0