[譯]純代碼建立 UIView

[譯]純代碼建立 UIViewios

翻譯自:swift

https://medium.com/written-code/creating-uiviews-programmatically-in-swift-55f5d14502ae/設計模式

讀完這篇文章,你能獲得啥?

  • 瞭解 iOS 的屏幕構成
  • 視圖關係
  • 什麼時候適合使用代碼方式構建視圖
  • MVC 模式下,該如何組織代碼
  • 自定義 UIView
  • 一個 Twitter iOS App 做爲示例
  • 避免構建 Massive View Controller
  • 使用 PureLayout 構建約束

瞭解 iOS 屏幕

iOS App 由許多視圖組成。視圖的顯示依賴四個值:x,y,width,height。bash

視圖的基本構成

三種方式構建視圖:StoryboardsNib files編碼實現app

UIKit 包含許多標準組件,從簡單的按鈕,到複雜的表格。他們用處普遍,如 UILabel 對象繪製文本字符串,UIImageView 對象繪製圖像。iview

視圖能夠被嵌入到其餘視圖,從而在視圖之間產生父子視圖關係,一個視圖的父視圖被稱爲superview,子視圖被稱爲subviewide

視圖關係

視圖關係

如何組織你的視圖關係着你的應用程序的視覺效果和事件行爲。舉一個例子,有兩個視圖,他們的父子關係決定了如何捕獲事件及響應事件的順序。相似的,當手機方向發生變化,視圖的父子關係也決定了他們作如何修改。佈局

什麼時候使用代碼形式構建視圖

如下情景,一般都是適合使用代碼構建視圖的狀況:

  • 動態佈局
  • 視圖須要實現一些效果,如圓角,陰影這類
  • 任何你感受使用 Storyboard 實現會複雜的時候

如何組織代碼(MVC 模式)

Model-View-Controller 是最經常使用的設計模式。然而在 iOS App 開發過程當中,一般要面臨一個問題:視圖控制器經常變得過於龐大,修改和重構都很痛苦。因此 MVC 也被戲稱爲Massive View Controllerui

遵循此模式,咱們應該儘可能確保項目中的每一個類都是Controller、Model或者View。這能有效避免代碼失控。咱們也能夠建立其餘的分組和類,但 App 的核心部分應該是這三種組成。編碼

目錄組織

準備

建立項目的時候,Xcode 會自動爲咱們增長一個 storyboard。爲了展現自定義視圖,咱們幹掉他先。

而後,建立兩個文件:ProfileView 繼承自 UIView,放到 View 分類中。ProfileViewController,繼承自 UIViewController,放在 Controller 分類中。

Auto Layout

Auto Layout 決定了屏幕上視圖的 frame。每一個視圖都包含約束條件,經過這些條件來計算出視圖的 width,height,x,y。直接編寫 Auto Layout 代碼並不容易,這裏咱們使用 PureLayout,它提供了功能強大,使用友好的接口來幫助咱們編寫 Auto Layout

首先添加 PureLayout 到你的項目中。我使用 CocoaPods 進行包管理,它依賴 Podfile 文件:

platform :ios, '8.0'
use_frameworks!
pod 'PureLayout', '~> 2.0.5'
複製代碼

執行代碼以安裝依賴:

pod install
複製代碼

這條命令將建立一個以.xcworkspace爲擴展名的新的工程文件。如今,使用 Xcode 打開它。

Building custom classes

ProfileView.swift 文件當前是一個自定義 UIView 類的模板:

import UIKit
class ProfileView: UIView {

}
複製代碼

咱們須要初始化它。初始化在 Swift 中是值得重視的事,你將在這裏瞭解到更多關於它的事。如今,咱們只須要知道有一個主要的初始化器負責初始化當前類全部的屬性。這是一個典型的實現:

import UIKit
import PureLayout

class ProfileView: UIView {
  var shouldSetupConstraints = true
    
  override init(frame: CGRect) {
    super.init(frame: frame)
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }
    
  override func updateConstraints() {
    if(shouldSetupConstraints) {
      // AutoLayout constraints
      shouldSetupConstraints = false
    }
    super.updateConstraints()
  }
}
複製代碼

給自定義的視圖添加約束以前,咱們要覆蓋 updateConstraints 方法。這個方法在運行期間可能會被調用屢次。爲了不屢次添加約束給視圖,咱們須要立一個 flag(shouldSetupContraints)來標示是否已經添加過約束。這個方法的最後,咱們也必須調用父類中的同名方法。(若是你在約束髮生改變以前就調用,可能會 crash)。

Example

咱們來仿寫一個 Twitter iOS app 的我的信息視圖。在下面的圖片中,能夠看到頂部視圖包括一個 Banner 圖,用戶頭像,以及用戶信息。而後下方是全部推展現在列表中以及tabbar。全部的 UIView 元素都拿紫色標註了起來。接下來咱們聚焦在 header view,橙色區域。

咱們先看主要的三部分。banner 和 用戶頭像使用 UIImageViews 進行展現,button 區域使用 UISegmentedControl。

首先,咱們定義三個元素在咱們的 ProfileView 類。bannerView,profileView 和 segmentedControl。

//ProfileView.swift
import UIKit
import PureLayout

class ProfileView: UIView {
  var shouldSetupConstraints = true
    
  var bannerView: UIImageView!
  var profileView: UIImageView!
  var segmentedControl: UISegmentedControl!
    
  override init(frame: CGRect){
    super.init(frame: frame)
    
  }
  
  ...
複製代碼

在 init 方法中,咱們初始化這些視圖元素的屬性。背景顏色、邊框顏色和其餘基本的視覺屬性。初始化他們的 frame 爲 zero,AutoLayout 會自動調整大小和位置。

將這些視圖元素添加爲 ProfileView 的子視圖使用 addSubview 方法。這個方法將被操做的視圖放在其餘子元素的最上面。代碼以下:

//ProfileView.swift
import UIKit
import PureLayout

class ProfileView: UIView {
  var shouldSetupConstraints = true
    
  var bannerView: UIImageView!
  var profileView: UIImageView!
  var segmentedControl: UISegmentedControl!
    
  let screenSize = UIScreen.main.bounds
  
  override init(frame: CGRect){
    super.init(frame: frame)
        
    bannerView = UIImageView(frame: CGRect.zero)
    bannerView.backgroundColor = UIColor.gray
        
    bannerView.autoSetDimension(.height, toSize: screenSize.width / 3)
    
    self.addSubview(bannerView)
        
    profileView = UIImageView(frame: CGRect.zero)
    profileView.backgroundColor = UIColor.gray
    profileView.layer.borderColor = UIColor.white.cgColor
    profileView.layer.borderWidth = 1.0
    profileView.layer.cornerRadius = 5.0
        
    profileView.autoSetDimension(.width, toSize: 124.0)
    profileView.autoSetDimension(.height, toSize: 124.0)
    
    self.addSubview(profileView)
        
    segmentedControl = UISegmentedControl(items: ["Tweets", "Media", "Likes"])
        
    self.addSubview(segmentedControl)
  }
  ...
複製代碼

使用 PureLayout 設置約束

佈局約束

佈局約束用來描述視圖與其餘視圖的關係和屬性。經過 NSLayoutConstraint 類來使用。

約束有如下幾種:

  • 尺寸約束 - 如描述一個圖片的寬爲200px
  • 對齊約束 - 如描述一個 label 垂直居中在屏幕
  • 間隙約束 - 如描述兩個元素之間的間隙

Attributes

PureLayout 定義了用來建立約束的視圖屬性,見圖:

開始搞事吧!

屏幕上有三個巨星元素,咱們要把他們的位置大小調整如 Twitter 我的頁面。

//ProfileView.swift
...
  override func updateConstraints() {
    if(shouldSetupConstraints) {

      let edgesInset: CGFloat = 10.0
      let centerOffset: CGFloat = 62.0
            
      bannerView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets.zero, excludingEdge: .bottom)
            
      profileView.autoPinEdge(toSuperviewEdge: .left, withInset: edgesInset)
      // 👇🏽 profileView.autoAlignAxis(.horizontal, toSameAxisOf: bannerView, withOffset: centerOffset)
      profileView.autoPinEdge(.bottom, to: .bottom, of: bannerView, withOffset: centerOffset)
            
      segmentedControl.autoPinEdge(toSuperviewEdge: .bottom, withInset: edgesInset)
      segmentedControl.autoPinEdge(toSuperviewEdge: .left, withInset: edgesInset)
      segmentedControl.autoPinEdge(toSuperviewEdge: .right, withInset: edgesInset)
            
      shouldSetupConstraints = false
    }
    
    super.updateConstraints()
...
}
複製代碼

避免 MVC 成爲 Massive View Controllers

下面進行完成 ProfileView 的最後一步。咱們須要在咱們的 Controller(ProfileViewController)中調用。Xcode 已經建立了一個 Controller 模板,幷包含 viewDidLoad: 方法。這個方法會在 Controller 顯示前進行回調。接下來咱們須要實例化咱們的 ProfileView 並展現它。

//ProfileViewController.swift
import UIKit

class ViewController: UIViewController {
  var profile: ProfileView!
  
  override func viewDidLoad() {
    super.viewDidLoad()

    profile = ProfileView(frame: CGRect.zero)
    self.view.addSubview(profile)
    
    // AutoLayout
    profile.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets.zero)
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }
}
複製代碼

(最後:本文中的代碼用於展現 Auto Layout。你須要補充其餘代碼才能使其成爲一個完整的項目,加油!💃🏽👋)

相關文章
相關標籤/搜索