標籤:

Xcode + Swift 製作動態原型

歡迎關注公眾號「產品日經」(掃描下面二維碼,或者微信搜索「產品日經」)

為什麼是 Xcode 和 Swift

我們嘗試過的動態原型設計工具,Origami, Form, Hype, FramerJS,Pixate 等,但都不適合。我們想要的是像 Sketch 這樣簡潔好用的工具,目前來看只有 Pixate 比較合適,但 Pixate 還處於早期版本,問題還很多,在第 1 經當中我也曾經提到過。

為什麼是 Xcode 和 Swift? 原因很簡單,我們不想做特效,只是想模擬 iOS 上真實的體驗。Xcode 不是模擬動畫,而像是一個 mini app,能給我們更接近真實的感受。

當然,Xcode 和 Swift 都有一定的學習曲線,但是就跟開始學習 Sketch 一樣,開始階段總是很難,很想放棄,這是人之常情。想想掌握之後的樂趣,會不會有動力一些?

這篇文章,會用 Xcode 和 Swift 做一個簡單的動畫。不會 Swift, 沒用過 Xcode 都沒有關係,我會把內容按照步驟呈現出來,方便你照著做。

準備工作

開始之前,我們需要下載並安裝 Xcode 6. 目前 Xcode 6 還處於 beta 階段,需要從[Xcode - Downloads]下載;

第 1 步: 新建一個 Project

- 打開 Xcode,選擇 Create a new Xcode project

- 模版中選擇 Single View Application

- 填寫相關的信息,注意這裡 Language 選擇 Swift, Device 選擇 iPhone

- 點擊 next 之後,選擇項目的保存位置,然後點擊 Create

- 這樣我們就新建了一個項目。

第 2 步: 在界面中繪製一個矩形

我們想在屏幕上繪製一個藍色的正方形,像這樣:

- 從左側文件導航里,打開 ViewController.swift 這個文件

- 不要被這裡的其他代碼干擾,我們所要做的就是在 viewDidLoad() 這個函數里添加我們的代碼

- 通過 let coloredSquare = UIView() 創建一個正方形。這裡 let 表示創建的是一個常量。UIView 是 UIKit 提供的繪製圖形的一個函數。

- 設置圖形的背景顏色 coloredSquare.backgroundColor = UIColor.blueColor()

- 繪製圖形的位置和大小 coloredSquare.frame = CGRectMake(0, 120, 50, 50) iOS 的坐標系統是從屏幕左上角開始的,也就是說左上角是(0, 0),所以我們繪製的是一個坐標為(0, 120),邊長為 50 的一個矩形

- 將圖形添加到視圖當中 self.view.addSubview(coloredSquare)

- 點擊左上角的三角形按鈕,運行一下。可以看到圖形被繪製到了一個 iPhone 模擬器當中。

代碼如下:

t// Create and add a colored squaren let coloredSquare = UIView()n n // Set background color to bluen coloredSquare.backgroundColor = UIColor.blueColor()n n // Set frame of the squaren coloredSquare.frame = CGRectMake(0, 120, 50, 50)n n // Add the square to the screenn self.view.addSubview(coloredSquare)n

通過這一步,四行代碼我們就繪製出了一個簡單圖形,接下來我們為圖形添加一個從左到右移動的動畫。

第 3 步: 給圖形添加動畫效果

我們想讓這個矩形從左到右移動,背景顏色由藍色變為紅色,可以通過這段代碼實現:

tUIView.animateWithDuration(1.0, animations: {n n // Change the background color when animation endsn coloredSquare.backgroundColor = UIColor.redColor()n n // Change the position of the squaren coloredSquare.frame = CGRectMake(320-50, 120, 50, 50)n n })n

下面做以解釋:

- Apple 在 UIView 這個類當中提供了 animationWithDuration 函數,這個函數需要兩個參數 1) 動畫持續的時間 2) 動畫結束的狀態。其中動畫結束的狀態是通過定義在 block 中實現的,block 可以理解成是把一塊代碼當作參數傳遞給函數。

- 改變顏色的代碼 coloredSquare.backgroundColor = UIColor.redColor()

- 改變正方形的位置 coloredSquare.frame = CGRectMake(320-50, 120, 50, 50) 這裡 x 坐標為什麼是 320 - 50?因為 iPhone 5s 橫坐標有 320 points,矩形的寬為 50 points,為了完整顯示矩形我們要讓它橫坐標為 320-50.

- 點擊左上角運行按鈕,可以看到 iOS 模擬器出現了這個小動畫。

可以看到,我們只定義了初始狀態和結束狀態,以及過渡時間, iOS 幫助我們做了一個圓滑的過渡。

接下來我們要說明一些概念,只是為了更好地理解,可以選擇跳過

第 4 步: 了解 animationWithDuration 方法

什麼可以被動畫

t我們上面只對背景顏色和位置做了動畫,UIView 還提供了對更多屬性做動畫的方法:

t1. frame. 可以改變視圖的大小和相對位置;

t2. transform. 改變屬性為 旋轉,放大,或者相對於中點的位置;

t3. alpha. 改變視圖的透明度;

t4. backgroundColor. 改變視圖背景顏色;

t5. contentStretch. 改變視圖內容填充到整個空間中。

更多動畫方法

Apple 給了我們四個動畫函數,上面我們只是用了最簡單的一個。下面是對這四個函數的簡要說明:

1. 簡單動畫函數

tUIView.animateWithDuration(duration, {n })n

這也是我們上面所使用的,只有兩個變數:

* duration. 動畫所需要的時間(秒)

* animation. 定義動畫

2. 包含結束方法的動畫方法

UIView.animateWithDuration(duration, animations: {tt n }, completion: { finished inn })n

增加了一個變數completion,它定義了當動畫完成時運行的代碼。

3. 包含 options 和 completion 的動畫方法

UIView.animateWithDuration(duration, delay: delay, options: options, animations: {tn }, completion: { finished inn })n

增加了兩個參數:

* delay: 動畫開始前需要等待的時間(秒);

* options: 更多可以改變動畫效果的選擇

4. 包含彈簧物理特性的動畫方法

t這是 iOS 7 中新增的,允許我們創建一個基於動畫對物理引擎。

UIView.animateWithDuration(duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: options, animations: {n }, completion: { finished int n })n

增加了兩個參數:

* dampingRatio. 定義振幅大小(0=無窮;1=沒有)

* initialVelocity. 動畫開始有多快(1=正常)

** UIViewAnimationOptions **

Apple 在動畫方法中給出了許多可選項。比如,我們傳遞給參數 options 一個 Repeat 值:

tlet options = UIViewAnimationOptions.Repeatn UIView.animateWithDuration(1, delay: 0.0, options: options, animations: {n coloredSquare.backgroundColor = UIColor.redColor()n coloredSquare.frame = CGRectMake(320-50, 120, 50, 50)n }, completion: nil)n

運行之後,可以看到,動畫不斷重複。如果將 options 的值改為 Autoreverse呢

方形動畫結束之後,按照相反的順序會再執行一遍。這裡有一個比較奇怪的地方,就是反方向執行之後,方形會自動回到右邊,變成紅色。這是因為我們設置的結束動畫就是這樣。

接下來我們使用 『|』分割線將Autoreverse, Repeat 和 CurveEaseInOut 結合到一起。

let options = UIViewAnimationOptions.Autoreverse | UIViewAnimationOptions.Repeat | UIViewAnimationOptions.CurveEaseInOutn

運行之後可以看到,這些可選項被結合到一起了。

第 5 步: 觸發動畫

每次改完代碼都要重新運行一次很討厭,所以我們想要加上一個這樣的 button,每次點擊之後會觸發動畫,而不是重新運行。

- 從 ViewController.swift 切換到 Main.storyboard,我們不使用 auto layout所以需要從右上角 File Inspector中關閉 Use Auto Layout

- 從右下角拖拽一個 Button 到故事版中,修改label 為 「Animate」

- 從右上角打開 Assistant Editor,允許我們同事看到storyboard 和對應的類文件

- 按住control鍵的同時,拖拽Button到ViewController.swift文件里的viewDidLoad()函數下

- 上一步打開了一個popover允許我們建立一個連接。默認連接方式是 outlet,我們需要的是 Action, Name 填為 animateButtonPressed,然後點擊 Connect 按鈕

- 此時,Xcode 幫我們生成了一個函數

t@IBAction func animateButtonPressed(sender: AnyObject) {n }n

t這個函數與storyboard中的button建立了連接,每次被點擊都會調用這裡的函數。

- 最後,剪切viewDidLoad()中的代碼到我們這個函數裡面,這樣每次點擊button就會呈現對應的動畫

點擊運行按鈕,通過點擊 Animate Button來觸發動畫。不幸的是,每次運行完之後,還是停留在最終狀態,不能夠重新回到開始狀態。這裡 completion 參數就派上用場了。

ttUIView.animateWithDuration(1.0, animations: {n coloredSquare.backgroundColor = UIColor.redColor()n coloredSquare.frame = CGRect(x: 320-50, y: 120, width: 50, height: 50)n }, completion{ animationFinished inn coloredSquare.removeFromSuperview()n })n

這樣,我們就實現了通過點擊button來觸發動畫。

第 6 步: 重構代碼

通過上面的步驟,我們能夠實現一個簡單的動畫。考慮到代碼越寫越多,為了維護方便和代碼重用,我們現在重構以上的代碼。

首先,我們將 hardcode 的部分

tlet coloredSquare = UIView()n coloredSquare.backgroundColor = UIColor.blueColor()n coloredSquare.frame = CGRectMake(0, 120, 50, 50)n self.view.addSubview(coloredSquare)n

變為更為通用的變數:

tlet size: CGFloat = 50n let yPosition: CGFloat = 120n let coloredSquare = UIView()n coloredSquare.backgroundColor = UIColor.blueColor()n coloredSquare.frame = CGRectMake(0, yPosition, size, size)n self.view.addSubview(coloredSquare)n

然後,重構動畫部分的代碼

tlet duration = 1.0n let delay = 0.0n let options = UIViewAnimationOptions.CurveLinearn UIView.animateWithDuration(duration, delay: delay, options: options, animations: {n coloredSquare.backgroundColor = UIColor.redColor()n coloredSquare.frame = CGRectMake(320-size, yPosition, size, size)n }, completion: { animationFinished inn coloredSquare.removeFromSuperview()n })n

點擊左上角運行之後,跟之前動畫效果一樣。

第 7 步: 使用循環

上面幾步,我們都是對一個方形進行動畫,我們接下來隨機生成多個對象,並且每個對象的大小隨機,可以使用 rand()方法生成一個隨機數:

t let size: CGFloat = CGFloat(Int(rand()) % 40 + 20.0)n let yPosition: CGFloat = CGFloat(Int(rand()) % 200 + 20.0)n

現在,我們點擊一次就會生成一個方形。接下來我們把這些放在一個循環里,每點擊一次就會出現多個圖形。Swift 中的循環長這樣:

ttfor loopNumber in 0...10 {n }n

把我們動畫代碼放到循環里,就實現了我們的目的。

第 8 步: 模擬小魚游來游去

到現在,我們掌握了基本的動畫多方法。接下來我們將上面的代碼改造到更有意義一些,模擬一群小魚在屏幕上游來游去。

- 使用 Sketch 畫一隻小魚,導出為 fish.png。將fish.png直接拖拽到 Xcode里,images.xcassets文件夾下;

- 回到 ViewController.swift 文件下,將之前的矩陣修改為小魚:

t let fish = UIImageView()n fish.image = UIImage(named: "fish")n fish.frame = CGRectMake(0, yPosition, size, size)n self.view.addSubview(fish)n

同時,對動畫這裡的代碼也做修改:

tttUIView.animateWithDuration(duration, delay: delay, options: options, animations: {n fish.frame = CGRectMake(320-size, yPosition, size, size)n }, completion: { animationFinished inn fish.removeFromSuperview()n })n

重新運行,可以看到出現了10條魚。每一個大小和y軸的位置都不一樣。

這裡有3個問題,1) 每次小魚直接尾巴就出現; 2)頭剛碰到屏幕右邊就會消失; 3)小魚都是同時出現同時消失。第一個問題我們可以修改將小魚的初始位置由 0 變為 0-size

fish.frame = CGRectMake(0-size, yPosition, size, size)n

第二個問題,原因是我們將動畫結束位置設在了320-size,為了讓小魚能夠完整游出界面,我們修改動畫結束時yPosition為320

fish.frame = CGRectMake(320, yPosition, size, size)n

第三個問題,現在他們都粘在一起,怎樣讓他們分散開來呢?可以通過給x軸一個隨機數;或者給動畫一個延遲。這裡我們使用改變x軸的方法解決:

給x軸一個隨機的初始位置

let xPosition: CGFloat = CGFloat((Int(rand()) % 320)-320)n

同時更改小魚生成時的位置

fish.frame = CGRectMake(xPosition, yPosition, size, size)n

為了讓每條魚在界面上逗留的時間不一樣,我們同時給duration一個隨機值

let duration = NSTimeInterval(Int(rand()) % 3)n

再次運行,就可以看到小魚隨機地游來游去。

這個動畫基本完成了,但是如果我們想在這個界面上動態地更改小魚的數量呢?

第 8 步: 動態更改小魚數量

如果我們想看100隻小魚,1000隻小魚,為了不每次更改代碼重新運行,與button類似,我們可以在界面上增加一個 slider。

- 切換到故事版,拖一個slider過來,再拖一個label

- 選中Slider,從右上角屬性檢查器更改Slider的上限,下限和默認值。這裡我們更改下限為0,上限為100

- 點擊右上角的 Assistant Editor,與之前類似的步驟:按住control,同時拖動 Slider 到viewDidLoad()函數上面的位置

- 在彈出的popover中,保持類型為Outlet,name給一個numberOfFishSlider,Xcode中會生成

@IBOutlet var numberOfFishSlider: UISlider!n

- 現在,我們需要做的就是改變循環次數。我們可以通過 self.numberOfFishSlider.value 拿到當前位置的值,只需要轉換為 Int類型

tlet numberOfFish = Int(self.numberOfFishSlider.value)n for loopNumber in 1...numberOfFish {n

現在,可以從Slider上選擇不同數量的小魚,看效果。

到現在,我們的動畫原型就完成了。下面是完整代碼:

//n// ViewController.swiftn// Animationn//n// Created by John on 9/17/14.n// Copyright (c) 2014 WeChat. All rights reserved.n//nnimport UIKitnnclass ViewController: UIViewController {n n @IBOutlet var numberOfFishSlider: UISlider!n n override func viewDidLoad() {n super.viewDidLoad()nn }nn @IBAction func animateButtonPressed(sender: AnyObject) {n n let numberOfFish = Int(self.numberOfFishSlider.value)n n for loopNumber in 1...numberOfFish {n nn let duration = NSTimeInterval(Int(rand()) % 3)n let delay = 0.0n let options = UIViewAnimationOptions.CurveLinearn n let size: CGFloat = CGFloat(Int(rand()) % 40 + 20.0)n let yPosition: CGFloat = CGFloat(Int(rand()) % 200 + 20.0)n let xPosition: CGFloat = CGFloat((Int(rand()) % 320)-320)n n // Create and add fish imagen n let fish = UIImageView()n fish.image = UIImage(named: "fish")n fish.frame = CGRectMake(xPosition, yPosition, size, size)n self.view.addSubview(fish)n n n // Create Animationn n UIView.animateWithDuration(duration, delay: delay, options: options, animations: {n n fish.frame = CGRectMake(320, yPosition, size, size)n n }, completion: { animationFinished inn fish.removeFromSuperview()n })n }n nn }n override func didReceiveMemoryWarning() {n super.didReceiveMemoryWarning()n // Dispose of any resources that can be recreated.n }nnn}n

文章內容參照Mathew Sanders的博文 [Prototyping Animations in Swift / iOS 8]

歡迎關注公眾號「產品日經」(掃描下面二維碼,或者微信搜索「產品日經」)


推薦閱讀:

誰能從產品經理的角度分析一下現在的家電遙控器有哪些反人類的設計?
Dailycost和Timi時光記賬都好在哪,為何會得到蘋果小編偏愛?
靠佛系甲方,你終究只能是個美工
No.7 金融客戶端設計-讓你的產品會說話

TAG:产品设计 |