YYoga 教程: 使用跨平臺(tái)布局引擎
Yoga 是一個(gè)基于 Flexbox 的跨平臺(tái)布局引擎,能使布局工作更容易。你可以使用 Yoga 作為一個(gè)通用的布局系統(tǒng),來(lái)代替 iOS 上的 Auto Layout 或 web 上的 Cascading Style Sheets (CSS)。
最初是 Facebook 在 2014 年推出的一個(gè) CSS 布局的開(kāi)源庫(kù),2016 年改版并更名為 Yoga。Yoga 支持多個(gè)平臺(tái),包括 Java、C#、C 和 Swift。
庫(kù)開(kāi)發(fā)者可以集成 Yoga 到他們的布局系統(tǒng),就如 Facebook 已經(jīng)集成進(jìn)了它的兩個(gè)開(kāi)源項(xiàng)目:React Native 和 Litho。然而,Yoga 也是一個(gè) iOS 開(kāi)發(fā)者可以直接用來(lái)布局視圖的框架。
在這份教程里,你將學(xué)習(xí) Yoga 的核心概念,然后通過(guò)構(gòu)建 FlexAndChill app 來(lái)練習(xí)并擴(kuò)展它們。
即便你將使用 Yoga 布局引擎,在閱讀這份教程之前,熟悉 Auto Layout 也是有好處的。你也想學(xué)習(xí)在你的項(xiàng)目中使用 CocoaPods 引入 Yoga 的知識(shí)。
拆包 Flexbox
Flexbox 也稱為 CSS Flexible Box,被創(chuàng)建用來(lái)處理 web 上的復(fù)雜布局。一個(gè)關(guān)鍵特征是在給定方向上高效布局內(nèi)容,并能“靈活”處理自身大小來(lái)適應(yīng)一些空間。
Flexbox 由 flex 容器組成,每個(gè)含有一個(gè)或多個(gè) flex 項(xiàng)目:
Flexbox 定義 flex 項(xiàng)目如何在一個(gè) flex 容器里布置。Flex 容器之外和 flex 項(xiàng)目?jī)?nèi)部的內(nèi)容會(huì)照常渲染。Flex 項(xiàng)目沿容器內(nèi)的單一方向布置(盡管它們可以任意包裹)。這將設(shè)置項(xiàng)目的主軸。相反的方向被稱為橫軸。
Flexbox 允許你指定項(xiàng)目在主軸與橫軸上的定位和間隔。對(duì)齊內(nèi)容(justify-content)指定項(xiàng)目沿容器的主軸對(duì)齊。下面的示例顯示容器的 flex 方向?yàn)樾袝r(shí)的項(xiàng)目展示位置:
flex-start:項(xiàng)目被定位在容器的開(kāi)端。
flex-end:項(xiàng)目被定位在容器的末端。
center:項(xiàng)目被定位在容器的中間。
space-between:項(xiàng)目在容器內(nèi)被空白空間均勻間隔開(kāi),第一個(gè)項(xiàng)目在開(kāi)端位置,最后一個(gè)項(xiàng)目在末端位置。
space-around:項(xiàng)目周圍以同等空間均勻間隔。
對(duì)齊項(xiàng)目(align-items)指定項(xiàng)目沿容器的橫軸對(duì)齊。這個(gè)例子顯示容器的 flex 方向?yàn)?b>行時(shí)(這意味著橫軸垂直運(yùn)行)的項(xiàng)目展示位置:
項(xiàng)目在容器的開(kāi)端、中間和末端垂直對(duì)齊。
這些初步的 Flexbox 屬性應(yīng)該讓你感受了 Flexbox 的工作原理。還有更多屬性可供你使用。有些控制項(xiàng)目依據(jù)可用容器空間來(lái)拉伸或收縮的方式。另一些可以設(shè)置填充(padding)、邊距(margin),甚至大?。╯ize)。
Flexbox 樣例
一個(gè)完美試用 Flexbox 概念的地方是?jsFiddle,一個(gè)在線的 JavaScript,HTML 和 CSS 運(yùn)行環(huán)境。
前往這里啟動(dòng) JSFiddle,并看看。你應(yīng)該看到 4 個(gè)窗格:
三個(gè)編輯框里的代碼驅(qū)動(dòng)輸出到你看見(jiàn)的右下方的窗格。啟動(dòng)的例子展示了一個(gè)白色的盒子。
注意在 CSS 編輯器里定義了?yoga?的類選擇器。這些代表了 Yoga 實(shí)現(xiàn)的 CSS 默認(rèn)值。一些值不同于?Flexbox w3 規(guī)范的默認(rèn)值。例如,Yoga 默認(rèn) flex 方向是縱向,并且項(xiàng)目被定位在容器的開(kāi)端。任何 HTML 元素的樣式經(jīng)由?class=”yoga”?將開(kāi)啟 “Yoga” 模式。
檢查 HTML 源代碼:
這個(gè)?div?的基本樣式是 yoga。另外的樣式屬性設(shè)置了大小,背景色并覆蓋了默認(rèn)的 flex 方向,所以項(xiàng)目將沿行排列。
在 HTML 編輯器里,添加下面的代碼到?div?閉標(biāo)簽之前:
這添加了一個(gè) 80 像素寬,紅色盒子的?yoga?樣式到?div?容器里。
點(diǎn)擊頂部菜單的 Run。你應(yīng)該看到如下輸出:
添加下面的子元素到根 div,在紅色盒子的?div?之后:
這添加了一個(gè) 80 像素寬的藍(lán)色盒子。
點(diǎn)擊 Run。更新后的輸出顯示了藍(lán)色盒子堆疊在紅色盒子的右邊:
使用下面的代碼替換藍(lán)色盒子的?div:
額外的?flex-grow?屬性允許盒子拉伸并填充任何可用空間。
點(diǎn)擊?Run?查看更新后的輸出,藍(lán)色盒子被拉伸:?
使用下面的代碼替換整個(gè) HTML 代碼:
這介紹了填充子項(xiàng)目,添加紅色盒子的右邊距,設(shè)置藍(lán)色盒子的高度,并讓藍(lán)色盒子與容器的中心對(duì)齊。
點(diǎn)擊 Run 查看輸出的結(jié)果:
need-to-insert-img
你可以在這里查看最終的 jsFiddle。請(qǐng)隨意使用其它布局屬性和值玩耍。
Yoga vs. Flexbox
即使 Yoga 是基于 Flexbox 的,它們也有一些不同。
Yoga 并沒(méi)有實(shí)現(xiàn)全部 CSS Flexbox。它省略了非布局屬性,如設(shè)置顏色。Yoga 改進(jìn)了一些 Flexbox 的屬性來(lái)提供更好的從右到左的支持。最后,Yoga 增加了一個(gè)新的比例(AspectRatio)屬性來(lái)處理在布置某些元素如圖片時(shí)常見(jiàn)的需求。
介紹 YogaKit
雖然你可能想要留在美妙的互聯(lián)網(wǎng)上,但這是一份 Swift 教程。不要害怕,Yoga API 將使你沐浴在 Flexbox 熟悉度的余暉中。你將可以在 Swift app 布局中應(yīng)用你學(xué)到的 Flexbox。
Yoga 使用 C 編寫,主要關(guān)注于優(yōu)化性能和簡(jiǎn)便集成到其它平臺(tái)。對(duì)于開(kāi)發(fā) iOS app,你將使用?YogaKit?工作,這是一個(gè)由 C 實(shí)現(xiàn)的封裝包。
回顧 Flexbox 在 web 里的樣例,布局是通過(guò)樣式屬性來(lái)配置的。而?YogaKit,布局配置是交由?YGLayout?對(duì)象來(lái)完成。YGLayout?包含的屬性有 flex 方向,對(duì)齊內(nèi)容,對(duì)齊項(xiàng)目,填充和邊距。
YogaKit?曝露?YGLayout?作為?UIView?上的一個(gè) Category。這個(gè) Category 添加?configureLayout(block:)?方法到?UIView。將 YGLayout 參數(shù)傳進(jìn)閉合塊里,并使用這些信息來(lái)配置視圖的布局屬性。
通過(guò)使用所需的 Yoga 屬性配置每個(gè)參與的視圖來(lái)構(gòu)建你的布局。一旦完成,你在根視圖的?YGLayout?上調(diào)用?applyLayout(preservingOrigin:)。這會(huì)計(jì)算并應(yīng)用布局到根視圖和子視圖。
你的第一個(gè)布局
使用 Single View Application 模版創(chuàng)建一個(gè)新的 Swift iPhone 工程,命名為 YogaTryout。
你將創(chuàng)建自己的 UI 編程方式,所以不需要使用 Storyboard。
打開(kāi) Info.plist,刪除默認(rèn)文件名為 Main 的 storyboard 屬性。接著設(shè)置啟動(dòng)界面(Launch screen)文件名的值為空字符串。
打開(kāi) AppDelegate.swift,在 application(_:didFinishLaunchingWithOptions:) 返回之前添加以下代碼:
window= UIWindow(frame: UIScreen.main.bounds)window?.rootViewController = ViewController()window?.backgroundColor = .whitewindow?.makeKeyAndVisible()
編譯(build)并運(yùn)行(run)app。你會(huì)看到一個(gè)空白的白色屏幕。
關(guān)閉 Xcode 項(xiàng)目。
如果你還沒(méi)有安裝CocoaPods,打開(kāi) Terminal,并輸入以下代碼安裝 CocoaPods:
sudogem install cocoapods
在 Terminal 里,跳轉(zhuǎn)到 YogaTryout.xcodeproj 所在的本地文件夾。創(chuàng)建一個(gè)名為 Podfile 的文件,并設(shè)置以下內(nèi)容:
platform:ios,'10.3'use_frameworks!target'YogaTryout'dopod'YogaKit','~> 1.5'end
在 Terminal 里運(yùn)行如下命令,安裝 YogaKit 的依賴:
podinstall
你會(huì)看到類似下面的輸出:
Analyzing dependenciesDownloading dependenciesInstalling Yoga (1.5.0)Installing YogaKit (1.5.0)Generating Pods projectIntegrating client project[!] Please close any current Xcode sessions anduse`YogaTryout.xcworkspace`forthisprojectfromnowon.Sending statsPod installationcomplete! Thereis1dependencyfromthe Podfileand2total pods installed.
從這里開(kāi)始,你將使用 YogaTryout.xcworkspace 工作。
Open YogaTryout.xcworkspace then build and run. You should still see a blank white screen.
打開(kāi) YogaTryout.xcworkspace,接著編譯(build)并運(yùn)行(run)。你依然看到一個(gè)空白的白色屏幕。
打開(kāi) ViewController.swift,添加下面的引入:
importYogaKit
這引入了 YogaKit 框架。
在 viewDidLoad() 的結(jié)尾處添加如下代碼:
// 1letcontentView = UIView()contentView.backgroundColor = .lightGray// 2contentView.configureLayout { (layout)in// 3layout.isEnabled =true// 4layout.flexDirection = .row? layout.width =320layout.height =80layout.marginTop =40layout.marginLeft =10}view.addSubview(contentView)// 5contentView.yoga.applyLayout(preservingOrigin:true)
這段代碼的作用如下:
創(chuàng)建一個(gè)視圖,并設(shè)置背景色。
設(shè)置布局配置閉包。
在視圖布局期間啟用 Yoga 樣式。
設(shè)置各個(gè)布局屬性,包括 flex 方向,框架大小和邊距。
計(jì)算并應(yīng)用布局到 contentView。
在 iPhone 7 Plus 上編譯(build)并運(yùn)行(run)app。你會(huì)看到一個(gè)灰色盒子:
你也許會(huì)抓頭,想知道為什么你不能使用期望的框架大小和設(shè)置背景色來(lái)簡(jiǎn)單實(shí)例化一個(gè) UIView。耐心點(diǎn),我的孩子。當(dāng)你添加子項(xiàng)目到這個(gè)初始容器時(shí),魔法開(kāi)始了。
在 viewDidLoad() 里,在應(yīng)用布局到 contentView 之前添加以下代碼:
letchild1 = UIView()child1.backgroundColor = .redchild1.configureLayout{ (layout)inlayout.isEnabled =truelayout.width = 80}contentView.addSubview(child1)
這段代碼添加了一個(gè) 80像素寬的紅色盒子到 contentView。
現(xiàn)在,在上一段代碼之后添加下面的代碼:
letchild2 = UIView()child2.backgroundColor = .bluechild2.configureLayout{ (layout)inlayout.isEnabled =truelayout.width = 80? layout.flexGrow = 1}contentView.addSubview(child2)
這添加了一個(gè)藍(lán)色盒子到容器里,它寬 80 像素,但被允許自增長(zhǎng)去填充容器內(nèi)的可用空間。如果這里開(kāi)始看起來(lái)熟悉,是因?yàn)槟阍?jsFiddle 里做過(guò)同樣的事。
編譯(build)并運(yùn)行(run)。你會(huì)看到如下:?
現(xiàn)在,給 contentView 的布局配置塊里添加以下聲明:
layout.padding = 10
這設(shè)置了全部子項(xiàng)目的填充值。
給 child1 的布局配置塊里添加以下代碼:
layout.marginRight = 10
這設(shè)置了紅色盒子的右邊距。
最后,給 child2 的布局配置塊里添加以下代碼:
layout.height = 20
layout.alignSelf = .center
這設(shè)置了藍(lán)色盒子的高度和居中對(duì)齊于父容器。
編譯(build)并運(yùn)行(run)。你會(huì)看到如下:
什么可以讓你實(shí)現(xiàn)將整個(gè)灰色盒子水平居中呢?好的,你可以在 contentView 的父視圖 self.view 里啟用 Yoga。
添加如下代碼到 viewDidLoad(),在調(diào)用 super 之后。
view.configureLayout { (layout)inlayout.isEnabled =truelayout.width = YGValue(self.view.bounds.size.width)? layout.height = YGValue(self.view.bounds.size.height)? layout.alignItems = .center}
這在根視圖上啟用了 Yoga,并基于視圖的范圍(bound)配置了布局的寬度和高度。alignItems 配置了子項(xiàng)目水平居中。記住 alignItems 指定容器的子項(xiàng)目沿橫軸對(duì)齊。容器的默認(rèn) flex 方向是縱向。所以橫軸是水平方向。
移除 contentView 布局配置里的 layout.marginLeft 聲明。你將通過(guò)它的父容器居中這個(gè)項(xiàng)目,所以不再需要了。
最后,替換:
contentView.yoga.applyLayout(preservingOrigin:true)
為如下代碼:
view.yoga.applyLayout(preservingOrigin:true)
這會(huì)計(jì)算并應(yīng)用布局到 self.view 和它的子視圖,包括 contentView。
編譯(build)并運(yùn)行(run)。注意灰色盒子現(xiàn)在水平居中:?
讓灰色盒子在屏幕上垂直居中很簡(jiǎn)單。添加下面的布局配置塊到 self.view:
layout.justifyContent = .center
移除 contentView 布局配置里的 layout.marginTop 聲明。在父容器控制了垂直居中時(shí),就不再需要了。
編譯(build)并運(yùn)行(run)。你會(huì)看到灰色盒子既水平又垂直居中:?
旋轉(zhuǎn)設(shè)備到橫屏模式。哦-哦,不再居中了:?
幸好,有一個(gè)方法可以得到屏幕改變方向的通知,以幫助解決它。
在類最后添加下面的方法:
overridefuncviewWillTransition(
? to size: CGSize,
? with coordinator: UIViewControllerTransitionCoordinator){super.viewWillTransition(to: size, with: coordinator)// 1view.configureLayout{ (layout)inlayout.width =YGValue(size.width)? ? layout.height =YGValue(size.height)? }// 2view.yoga.applyLayout(preservingOrigin:true)}
這段代碼做了以下事:
依據(jù)新方向的(屏幕)大小更新布局配置。請(qǐng)注意,只有受影響的屬性才會(huì)更新。
重新計(jì)算并應(yīng)用布局。
旋轉(zhuǎn)設(shè)備回豎屏模式。編譯(build)并運(yùn)行(run)。旋轉(zhuǎn)設(shè)備到橫屏模式。灰色盒子現(xiàn)在應(yīng)該好好的居中了:?
如果你想比較你的代碼,可以在這里下載最終的 tryout 項(xiàng)目。
誠(chéng)然,你大概會(huì)在你的呼吸里嘀咕,你可以使用 Interface Builder,不到 3 分鐘就構(gòu)建好了這個(gè)布局,包括妥善處理旋轉(zhuǎn):?
當(dāng)你的布局開(kāi)始變得比你想的更加復(fù)雜,如需要適應(yīng)嵌入式堆棧視圖時(shí),你會(huì)想給 Yoga 一個(gè)新鮮的看法。
另一方面,你可能早已放棄使用 Interface Builder 編寫布局方式,如布局錨定或可視化格式語(yǔ)言。如果那些為你工作,無(wú)需改變。在心里記住可視化格式語(yǔ)言不支持寬高比,而 Yoga 支持。
一旦你理解了 Flexbox,Yoga 就是如此易于掌握。這兒有許多資源,你可以在 iOS 上使用 Yoga 構(gòu)建它們之前快速嘗試 Flexbox 布局。
高級(jí)布局
你構(gòu)建白色、紅色和藍(lán)色盒子的喜悅大概已經(jīng)磨損了。是時(shí)候搖一搖。在接下來(lái)的部分,你將帶著你新構(gòu)造的 Yoga 技能來(lái)創(chuàng)建類似下面的視圖:?
下載并瀏覽開(kāi)始的工程。它已經(jīng)包含了 YogaKit 的依賴。其它主要的類有:
ViewController:顯示主視圖。你將主要在這個(gè)類里工作。
ShowTableViewCell:用來(lái)顯示列表視圖里的插曲。
Show: 節(jié)目的模型(Model)對(duì)象。
編譯(build)并運(yùn)行(run)app。你會(huì)看到一個(gè)黑色的屏幕。
這是一個(gè)所需布局的線框分解,來(lái)幫助計(jì)劃事情:?
讓我們快速剖析圖中各個(gè)盒子的布局:
展示節(jié)目的圖片。
展示這一項(xiàng)目所屬系列的摘要信息,橫向排列。
展示節(jié)目的標(biāo)題信息,橫向排列。
展示節(jié)目的描述信息,縱向排列。
展示可以進(jìn)行的操作。主容器橫向排列。每個(gè)子項(xiàng)目都是一個(gè)容器,縱向排列。
展示項(xiàng)目的標(biāo)簽,橫向排列。
展示一個(gè)列表,用來(lái)填充其余的空間。
隨著構(gòu)建每一片布局,你將對(duì)額外的 Yoga 屬性獲得更多好感,和如何微調(diào)一個(gè)布局。
打開(kāi) ViewController.swift,添加以下代碼到 viewDidLoad(),在節(jié)目剛剛從 plist 加載之后:
這設(shè)置了要展示的節(jié)目。
寬高比(Aspect Ratio)
Yoga 介紹了一個(gè)寬高比(aspectRatio)屬性用來(lái)幫助對(duì)已知寬高比的項(xiàng)目布置視圖。寬高比代表了寬度與高度的比例。
添加以下代碼在 contentView 加入到它的父視圖之后:
// 1letepisodeImageView = UIImageView(frame: .zero)episodeImageView.backgroundColor = .gray// 2letimage = UIImage(named: show.image)episodeImageView.image = image// 3letimageWidth = image?.size.width ??1.0letimageHeight = image?.size.height ??1.0// 4episodeImageView.configureLayout { (layout)inlayout.isEnabled =truelayout.flexGrow =1.0layout.aspectRatio = imageWidth / imageHeight}contentView.addSubview(episodeImageView)
讓我們一步步進(jìn)入代碼:
創(chuàng)建一個(gè) UIImageView。
基于選中的節(jié)目設(shè)置圖片。
梳理出圖片大小。
配置布局和設(shè)置基于圖片大小的寬高比。
編譯(build)并運(yùn)行(run)app。你會(huì)看到圖片垂直拉伸,但保持了圖片的寬高比:?
need-to-insert-img
FlexGrow
迄今你見(jiàn)過(guò)了 flexGrow 應(yīng)用在容器里的項(xiàng)目上。你在前一個(gè)例子里通過(guò)設(shè)置 flexGrow 屬性為 1,拉伸了藍(lán)色盒子。
如果不止一個(gè)子項(xiàng)目設(shè)置 flexGrow 屬性,那么會(huì)優(yōu)先基于它們需要的空間布置子項(xiàng)目。然后使用每個(gè)子元素的 flexGrow 分配剩余的空間。
在系列概述視圖里,你將布置子項(xiàng)目以便中間部分占據(jù)其余兩部分的兩倍空間。
添加以下代碼到 episodeImageView 加入到它的父視圖之后:
let summaryView =UIView(frame: .zero)summaryView.configureLayout { (layout)inlayout.isEnabled =truelayout.flexDirection = .row? layout.padding =self.padding}
這段代碼定義了子項(xiàng)目將橫向布置,并包括填充空間。
將以下代碼加到上一段代碼之后:
letsummaryPopularityLabel = UILabel(frame: .zero)summaryPopularityLabel.text =String(repeating:"★", count: showPopularity)summaryPopularityLabel.textColor = .redsummaryPopularityLabel.configureLayout { (layout)inlayout.isEnabled =truelayout.flexGrow =1.0}summaryView.addSubview(summaryPopularityLabel)contentView.addSubview(summaryView)
這添加了一個(gè)人氣標(biāo)簽,并設(shè)置了它的 flexGrow 屬性為 1。
編譯(build)并運(yùn)行(run)app 查看人氣信息:?
need-to-insert-img
添加以下代碼在 summaryView 加入到它的父視圖之前:
letsummaryInfoView = UIView(frame: .zero)summaryInfoView.configureLayout { (layout)inlayout.isEnabled =truelayout.flexGrow = 2.0? layout.flexDirection = .row? layout.justifyContent = .spaceBetween}
這為概述標(biāo)簽子項(xiàng)目設(shè)置了一個(gè)新的容器視圖。注意 flexGrow 設(shè)置為 2。因此,summaryInfoView 將占據(jù)比 summaryPopularityLabel 多兩倍的額外空間。
添加以下代碼在前一段代碼塊之后:
fortextin[showYear, showRating, showLength] {? let summaryInfoLabel =UILabel(frame: .zero)? summaryInfoLabel.text = text? summaryInfoLabel.font =UIFont.systemFont(ofSize:14.0)? summaryInfoLabel.textColor = .lightGray? summaryInfoLabel.configureLayout { (layout)inlayout.isEnabled =true}? summaryInfoView.addSubview(summaryInfoLabel)}summaryView.addSubview(summaryInfoView)
這里循環(huán)使用概述標(biāo)簽來(lái)展示節(jié)目。每個(gè)標(biāo)簽都是 summaryInfoView 容器的一個(gè)子元素。那個(gè)容器的布局指定了標(biāo)簽放置在開(kāi)端,中間和末端。
編譯(build)并運(yùn)行(run)app 查看節(jié)目標(biāo)簽:?
need-to-insert-img
調(diào)整布局以得到正確的空間,你將再添加一個(gè)項(xiàng)目到 summaryView。接著添加下面的代碼:
let summaryInfoSpacerView =UIView(frame:CGRect(x:0, y:0, width:100, height:1))summaryInfoSpacerView.configureLayout { (layout)inlayout.isEnabled =truelayout.flexGrow =1.0}summaryView.addSubview(summaryInfoSpacerView)
這里通過(guò)設(shè)置 flexGrow 為 1 提供了一個(gè)空間。summaryView 有 3 個(gè)子項(xiàng)目。第一和第三個(gè)項(xiàng)目將占據(jù)任何剩余容器空間的 25%,而第二個(gè)項(xiàng)目將占據(jù)可用空間的 50%。
編譯(build)并運(yùn)行(run)app 查看屬性調(diào)整過(guò)的布局:?
need-to-insert-img
更多樣例
繼續(xù)構(gòu)建布局,查看更多間距和定位的例子。
添加以下代碼到 summaryView 代碼之后:
let titleView =UIView(frame: .zero)titleView.configureLayout { (layout)inlayout.isEnabled =truelayout.flexDirection = .row? layout.padding =self.padding}let titleEpisodeLabel =? showLabelFor(text: selectedShowSeriesLabel,? ? ? ? ? ? ? font:UIFont.boldSystemFont(ofSize:16.0))titleView.addSubview(titleEpisodeLabel)let titleFullLabel =UILabel(frame: .zero)titleFullLabel.text = show.titletitleFullLabel.font =UIFont.boldSystemFont(ofSize:16.0)titleFullLabel.textColor = .lightGraytitleFullLabel.configureLayout { (layout)inlayout.isEnabled =truelayout.marginLeft =20.0layout.marginBottom =5.0}titleView.addSubview(titleFullLabel)contentView.addSubview(titleView)
這段代碼設(shè)置了節(jié)目的標(biāo)題 titleView 做為一個(gè)容器持有兩個(gè)項(xiàng)目。
編譯(build)并運(yùn)行(run)app 查看標(biāo)題:?
need-to-insert-img
接著添加下面的代碼:
let descriptionView =UIView(frame: .zero)descriptionView.configureLayout { (layout)inlayout.isEnabled =truelayout.paddingHorizontal =self.paddingHorizontal}let descriptionLabel =UILabel(frame: .zero)descriptionLabel.font =UIFont.systemFont(ofSize:14.0)descriptionLabel.numberOfLines =3descriptionLabel.textColor = .lightGraydescriptionLabel.text = show.detaildescriptionLabel.configureLayout { (layout)inlayout.isEnabled =truelayout.marginBottom =5.0}descriptionView.addSubview(descriptionLabel)
這里創(chuàng)建了一個(gè)使用水平間距的容器視圖,然后添加了一個(gè)子元素用來(lái)展示節(jié)目的詳情。
現(xiàn)在,添加下面的代碼:
letcastText ="Cast:\(showCast)";letcastLabel = showLabelFor(text: castText,? ? ? ? ? ? ? ? ? ? ? ? ? ? font:UIFont.boldSystemFont(ofSize:14.0))descriptionView.addSubview(castLabel)letcreatorText ="Creators:\(showCreators)"letcreatorLabel = showLabelFor(text: creatorText,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? font:UIFont.boldSystemFont(ofSize:14.0))descriptionView.addSubview(creatorLabel)contentView.addSubview(descriptionView)
這里添加了兩個(gè)項(xiàng)目到 descriptionView,顯示更多節(jié)目詳情。
編譯(build)并運(yùn)行(run)app 查看完整描述:?
need-to-insert-img
接下來(lái),你將添加節(jié)目的操作視圖。
在 ViewController 的擴(kuò)展里添加一個(gè)私有幫助方法:
funcshowActionViewFor(imageName: String, text: String)->UIView{letactionView =UIView(frame: .zero)? actionView.configureLayout { (layout)inlayout.isEnabled =truelayout.alignItems = .center? ? layout.marginRight =20.0}letactionButton =UIButton(type: .custom)? actionButton.setImage(UIImage(named: imageName),for: .normal)? actionButton.configureLayout{ (layout)inlayout.isEnabled =truelayout.padding =10.0}? actionView.addSubview(actionButton)letactionLabel = showLabelFor(text: text)? actionView.addSubview(actionLabel)returnactionView}
這里使用一張圖片和一個(gè)標(biāo)簽設(shè)置了一個(gè)水平居中對(duì)齊的容器視圖。
現(xiàn)在,添加以下代碼到 viewDidLoad() 里的 descriptionView 代碼段之后:
letactionsView =UIView(frame: .zero)actionsView.configureLayout { (layout)inlayout.isEnabled =truelayout.flexDirection = .row? layout.padding =self.padding}letaddActionView =? showActionViewFor(imageName:"add", text:"My List")actionsView.addSubview(addActionView)letshareActionView =? showActionViewFor(imageName:"share", text:"Share")actionsView.addSubview(shareActionView)contentView.addSubview(actionsView)
這里創(chuàng)建了一個(gè)容器,持有兩個(gè)使用 showActionViewFor(imageName:text) 創(chuàng)建的項(xiàng)目。
編譯(build)并運(yùn)行(run)app 查看操作視圖。?
need-to-insert-img
是時(shí)候布置一些標(biāo)簽了。
在 ViewController 的擴(kuò)展里加入一個(gè)新方法:
funcshowTabBarFor(text: String, selected: Bool)->UIView{// 1lettabView =UIView(frame: .zero)? tabView.configureLayout { (layout)inlayout.isEnabled =truelayout.alignItems = .center? ? layout.marginRight =20.0}// 2lettabLabelFont = selected ?UIFont.boldSystemFont(ofSize:14.0) :UIFont.systemFont(ofSize:14.0)letfontSize:CGSize= text.size(attributes: [NSFontAttributeName: tabLabelFont])// 3? lettabSelectionView =UIView(frame:CGRect(x:0, y:0, width: fontSize.width, height:3))ifselected {? ? tabSelectionView.backgroundColor = .red? }? tabSelectionView.configureLayout { (layout)inlayout.isEnabled =truelayout.marginBottom =5.0}? tabView.addSubview(tabSelectionView)// 4lettabLabel = showLabelFor(text: text, font: tabLabelFont)? tabView.addSubview(tabLabel)returntabView}
一步步進(jìn)入代碼:
創(chuàng)建一個(gè)容器,設(shè)置內(nèi)部項(xiàng)目水平居中對(duì)齊。
根據(jù)標(biāo)簽選中與否來(lái)計(jì)算期望的字體信息。
創(chuàng)建一個(gè)視圖來(lái)標(biāo)明標(biāo)簽的選中。
創(chuàng)建一個(gè)用來(lái)表示標(biāo)簽標(biāo)題的 label。
添加下面的代碼到 actionsView 被加入到 contentView 之后(在 viewDidLoad 里):
lettabsView =UIView(frame: .zero)tabsView.configureLayout { (layout)inlayout.isEnabled =truelayout.flexDirection = .row? layout.padding =self.padding}letepisodesTabView = showTabBarFor(text:"EPISODES", selected:true)tabsView.addSubview(episodesTabView)letmoreTabView = showTabBarFor(text:"MORE LIKE THIS", selected:false)tabsView.addSubview(moreTabView)contentView.addSubview(tabsView)
這設(shè)置了標(biāo)簽容器視圖,并將標(biāo)簽項(xiàng)目添加到容器。
編譯(build)并運(yùn)行(run)app 看看你的新標(biāo)簽:?
need-to-insert-img
在這個(gè)簡(jiǎn)單的 app 里,標(biāo)簽選項(xiàng)沒(méi)有功能。如果你有興趣稍后添加,大多數(shù)勾子都已到位。
就快完成了。你只需將列表視圖加入到最后。
添加以下代碼到 tabView 加入到 contentView 之后:
letshowsTableView =UITableView()showsTableView.delegate =selfshowsTableView.dataSource =selfshowsTableView.backgroundColor = backgroundColorshowsTableView.register(ShowTableViewCell.self,? ? ? ? ? ? ? ? ? ? ? ? forCellReuseIdentifier: showCellIdentifier)showsTableView.configureLayout{ (layout)inlayout.isEnabled =truelayout.flexGrow =1.0}contentView.addSubview(showsTableView)
這段代碼創(chuàng)建并配置了一個(gè)列表視圖。布局配置信息設(shè)置 flexGrow 屬性為 1,允許列表視圖拉伸以填充其余的空間。
編譯(build)并運(yùn)行(run)app。你應(yīng)該看到在視圖里有一個(gè)劇集列表:?
繼續(xù)學(xué)習(xí)之路
恭喜!如果你已經(jīng)做到了這一點(diǎn),你幾乎是一個(gè) Yoga 專家。推出你的墊子,抓住額外的特殊拉伸褲,屏住呼吸。你可以在這里下載最終的教程工程。
查看?Yoga 文檔以獲得更多未涵蓋屬性的細(xì)節(jié),例如對(duì)從右到左的支持。
Flexbox 規(guī)范是一個(gè)獲得更多 Flexbox 背景知識(shí)的好資源。Flexbox 學(xué)習(xí)?資源是一個(gè)十分便利的,瀏覽不同 Flexbox 屬性的向?qū)А?/p>
我希望你享受閱讀這份 Yoga 教程。如果你有關(guān)于這份手冊(cè)的任何建議或問(wèn)題,請(qǐng)加入下面的論壇進(jìn)行討論!