#4 研究UI元件|季節桌布App

Chia
彼得潘的 Swift iOS / Flutter App 開發教室
14 min readNov 15, 2021

結合各種UI元件和程式碼做了可以產生客製化桌布的App。
前半段會先介紹各個UI元件對應的功能,程式碼說明則放在後面。

可以產生不同季節的桌布共四種

使用到的UI元件有下面幾種(由上而下)
- Segmented Control 切換季節
- Date Picker 變更日期後會自動改變顯示的桌布季節
- Switch 切換是否顯示上方的季節名稱圖片
- Slider 變更背景圖片的透明度
- Button 按下“Generate”按鈕後產生客製化的全螢幕季節桌布
- Page Control 切換左右頁面切換季節

季節桌布APP操作動畫

所以其實Segmented Control、Date Picker和Page Control都是用來切換季節的,只是想練習每個元件的用法。

這邊先針對每個UI元件詳細說明功能。

Segmented Control

可以選擇四個不同的季節,點選後依照季節更換桌布的樣式,這邊選取時會同步更新最下方的Page Control頁面。

Segmented Control切換季節

Date Picker

選擇日期後會自動判斷指定日期的月份,改變桌布的季節,並同時更新Segmented Control及Page Control的選項。

Date Picker選取日期並自動切換季節

Switch

開關選擇是否顯示上方的季節名稱圖片。

Switch開啟/關閉顯示季節名稱

Slider

滑動改變背景圖片的透明度。

Slider滑動變更背景圖片透明度

Page Control

由左到右四個頁面分別為春夏秋冬,點選可以切換季節,桌布樣式會隨著季節變更,並同步更新最上方Segmented Control的選項。

Page Control左右換頁來切換不同季節

Button

點選”Generate” Button後會移動到另外一個View Controller,全螢幕顯示使用者客製化的桌布。因為沒有Navigation Bar的關係,這邊設計成在全螢幕頁面點選任一處即可回到原本的頁面。為了方便截圖使用,在截圖頁面時會隱藏最上方的Status Bar。

點選Generate Button來產生桌布(移動到全螢幕畫面)

程式碼說明

這個專案由兩個ViewController所構成,因此分兩個部分來寫所用到的程式碼。

Storyboard

主畫面ViewController

先介紹存有各個UI元件選項的主畫面。

建立IBOutlet

建立背景圖片及上方季節名稱圖片的IBOutlet

@IBOutlet var backgroundImageView: UIImageView!
@IBOutlet var seasonNameImageView: UIImageView!

建立各個UI元件的IBOutlet

@IBOutlet var segmentedControl: UISegmentedControl!
@IBOutlet var datePicker: UIDatePicker!
@IBOutlet var labelSwitch: UISwitch!
@IBOutlet var transparencySlider: UISlider!
@IBOutlet var generateButton: UIButton!
@IBOutlet var pageControl: UIPageControl!

定義變數

定義seasons變數,以便之後用index來選擇各個季節,切換各個季節的背景圖片及季節名稱圖片。

let seasons = ["spring", "summer", "autumn", "winter"]

定義產生季節桌布會用到的變數,並給予預設值。預設的季節是秋天,背景透明度為0.5,季節名稱圖片預設為顯示。

var season: String = "autumn"
var transparency: CGFloat = 0.5
var showLabel: Bool = true

viewDidLoad()

畫面讀取後會先用checkSeason()輸入今天日期Date.now確認今天的季節,在checkSeason()中會變更變數season的值來切換季節,再使用seasonChanged()來變換畫面。

override func viewDidLoad() {
super.viewDidLoad()
checkSeason(date: Date.now)
seasonChanged()
}

指定各個UI元件的IBAction

sender.selectedSegmentIndex取得Segmented Control被選擇的index,並將season變更為使用者所選擇的季節,並執行seasonChanged()來變更桌布的季節樣式。

@IBAction func segmentedControlChanged(_ sender: UISegmentedControl) {
season = seasons[sender.selectedSegmentIndex]
seasonChanged()
}

sender.date取得Date Picker的日期後,用checkSeason()來變更季節變數,並執行seasonChanged()來變更桌布的季節樣式。

@IBAction func datePickerChanged(_ sender: UIDatePicker) {
checkSeason(date: sender.date)
seasonChanged()
}

sender.isOn來確認Switch的狀態,如果為ON的話會回傳true,OFF則會回傳false。在Switch為ON狀態時將季節名稱圖片的isHidden設為false來顯示圖片,反之則將isHidden設為true來隱藏圖片。

@IBAction func labelSwitchChanged(_ sender: UISwitch) {
showLabel = sender.isOn
seasonNameImageView.isHidden = showLabel ? false: true
}

sender.value來取得UISlider的值,這邊是要變更背景的透明度,所以將背景的imageView.alpha設定為所取得的透明度值(要先變更為CGFloat型態)

@IBAction func transparencySliderChanged(_ sender: UISlider) {
transparency = CGFloat(sender.value)
backgroundImageView.alpha = transparency
}

sender.currentPage取得Page Control中使用者所選取的頁面index,並將season變數變更為相對應的季節。

@IBAction func pageControlChanged(_ sender: UIPageControl) {
season = seasons[sender.currentPage]
seasonChanged()
}

以Date來確認季節的checkSeason()

這邊首先取得與日期相對應的英文月份名稱,這邊使用的是DateFormatter()這個方法並指定格式為"LLLL"。取得月份名稱後,使用switch來判斷該個月份是哪個季節,並變更season變數為相對應的季節名稱。

func checkSeason(date: Date) {
// 取得與日期相對應的英文月份名稱
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "LLLL"
let month = dateFormatter.string(from: date)
// 依照不同月份變更season變數
switch month {
case "March", "April", "May": season = "spring"
case "June", "July", "August": season = "summer"
case "September", "October", "November": season = "autumn"
case "December", "January", "February": season = "winter"
default: season = "autumn"
}
}

變更相對應的季節樣式的seasonChanged()

當季節變更時,觸發這個方法。這個方法會:

  • 變更背景圖片和季節名稱圖片
  • 變更UISwitch、UISlider、UIButton及UIPageControl的顏色以符合各個季節的設計
  • 將SegmentedControl及PageControl切換到指定的季節
func seasonChanged() {
// 變更背景圖片及季節名稱圖片
backgroundImageView.image = UIImage(named: "\\(season)-background")
seasonNameImageView.image = UIImage(named: season)
// 變更UI元件顏色為季節相對應的配色
labelSwitch.onTintColor = UIColor(named: season)
transparencySlider.minimumTrackTintColor = UIColor(named: season)
generateButton.backgroundColor = UIColor(named: season)
pageControl.backgroundColor = UIColor(named: season)
// 將SegmentedControl及PageControl切換到指定的季節
let seasonIndex: [String: Int] = ["spring": 0, "summer": 1, "autumn": 2, "winter": 3]
segmentedControl.selectedSegmentIndex = seasons.firstIndex(of: season)!
pageControl.currentPage = seasonIndex[season]!
}

這邊有設定好各個季節名稱的Color Set了所以可以直接用UIColor(named: season)呼叫 。

切換到下一頁用的prepare()

這邊設定Segue來傳遞變數到全螢幕顯示季節桌布的ViewController。傳遞的變數有存放季節名稱的season、決定背景透明度的transparency的數值和是否顯示季節名稱的showLabel(Bool)變數。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as? PhotoViewController
controller?.season = season
controller?.transparency = transparency
controller?.showLabel = showLabel
}

自訂字型

這個專案的UILabel的字型全都直接在Storyboard更改,Segmented Control的字型則使用程式碼變更,在viewDidLoad()加入下列程式碼指定字型。

segmentedControl.setTitleTextAttributes([NSAttributedString.Key.font: UIFont(name: "Mali-Regular", size: 15)!], for: .normal)

顯示季節桌布的PhotoViewController

建立一個顯示客製化完成桌布用的ViewController。

建立IBOutlet

這個畫面其實只由兩張圖片,背景圖片及季節名稱圖片組合而成。

@IBOutlet var backgroundImageView: UIImageView!
@IBOutlet var seasonNameImageView: UIImageView!

設定由主畫面傳遞過來的變數

var season: String = "autumn"
var transparency: CGFloat = 0.5
var showLabel: Bool = true

顯示指定的桌布樣式

由主畫面傳遞過來的變數來指定背景圖片、季節名稱圖片,並設定背景圖片透明度及是否顯示季節名稱圖片。

override func viewDidLoad() {
super.viewDidLoad()
backgroundImageView.image = UIImage(named: "\\(season)-background")
seasonNameImageView.image = UIImage(named: season)
backgroundImageView.alpha = transparency
seasonNameImageView.isHidden = showLabel ? false: true
}

點選螢幕回到上一頁

由於沒有NavigationBar的關係,這邊多設定了一個方法handleTap()的方法來讓使用者可以在點選畫面時回到上一頁。

首先在畫面最上層加入一個背景顏色為clear的View(這邊取名為tapView),目的是要讓使用者點選到這個View時可以觸發回到上一頁的指定。

@IBOutlet var tapView: UIView!

在viewDidLoad()加入下列指令,在畫面被點選時執行handleTap。

let tap = UITapGestureRecognizer(target: self, action: #selector(goBackToPreviousPage(_:)))
tapView.addGestureRecognizer(tap)

當畫面被點選時下面指令會被執行,其中使用到dismiss()的方法來回到上一個畫面。

@objc func goBackToPreviousPage(_ sender: UITapGestureRecognizer) {
if sender.state == .ended {
self.dismiss(animated: true, completion: nil)
}
}

隱藏畫面的Status Bar

由於這個畫面想要顯示使用者設計完成的桌布,並方便截圖下載,因此這邊加入下列程式碼將Status Bar隱藏。

override var prefersStatusBarHidden: Bool {
return true
}

原本只是要練習各種UI元件,後來想說乾脆加上一些功能,雖然只是一個有小小功能的App,但做完之後很有成就感。後來又加上了App Icon及Launch Screen,關於這一部分會寫在下一篇。

附上上一份作業文章 − 研究UI元件

--

--