①⓪③ 透過 present 顯示 iOS SDK內建的 Controller(1)

SFSafariViewController, UIActivityViewController, UIAlertController, AVPlayerViewController & UIReferenceLibraryViewController

Min
彼得潘的 Swift iOS / Flutter App 開發教室
18 min readDec 12, 2023

--

練習目標:

一句話說明今天的練習:

做出平常在 App 裡能夠顯示網頁、分享照片等等的功能!

什麼是 Present?

Presents a view controller modally. 官方的定義是:用 modally 的方式呈現一個 view controller。

modally 是使用 storyboard 來 segue controller 時,沒有用 navigation controller 控制大家會呈現的樣式。

PS. 使用 present 時,通常輸入完希望呈現的 controller 與需要動畫效果之後,會看到 completion 後面有(() -> Void?

如果沒有其他要做的事情,僅僅呈現 controller 就好,這邊輸入 nil 即可。否則直接在此看下 enter 鍵,將自動創建大括號{}讓你輸入其餘的程式。

今天將練習使用 present 以下 controller:

  1. SFSafariViewController 顯示網頁
  2. UIActivityViewController 分享圖片與文字
  3. UIAlertController 警告訊息
  4. AVPlayerViewController 播放影片
  5. UIReferenceLibraryViewController 查詢單字

其他 controller 要搭配 data source 或 delegate 才能實現完整的功能,之後在 part 2 練習。

SFSafariViewController

因為看到 Safari,可知是顯示網頁的 controller。

viewDidAppear

為了實驗一打開 App 就出現網頁(到底誰會這樣做?)將程式寫在 viewDidAppear 方法中:

import UIKit
import SafariServices // 顯示網頁必輸入 SafariServices

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// view 已載入並顯示後的方法
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

if let url = URL(string: "https://medium.com/@lebonthe") {
let controller = SFSafariViewController(url: url)
present(controller, animated: true, completion: nil)
}
}
}
可以看到 view 一載完就會跳到網頁

button

點一下按鈕出現網址:

  @IBOutlet weak var button: UIButton!
@IBAction func buttonTapped(_ sender: UIButton) {
if let url = URL(string: "https://medium.com/@lebonthe"){
let controller = SFSafariViewController(url: url)
present(controller, animated: true, completion: nil)
}
}

UIActivityViewController

可以分享圖片與文字。(實機中才會出現 FB 或 LINE 選項)

程式

    @IBAction func buttonTapped(_ sender: UIButton) {
let controller = UIActivityViewController(activityItems: ["Min 的 Medium", UIImage(named: "cat_koubakozuwari_brown")!, URL(string: "https://medium.com/@lebonthe")!], applicationActivities: nil)
present(controller, animated: true, completion: nil)
}

在 activityItems: 後面的陣列,決定分享的內容格式:

“” 是文字

UIImage(name: ) 是圖片

URL(string: ) 是網址

注意

在實驗之前,先看有兩件事情要注意:

  • 第一是將圖片存到手機相簿時,需在 App 的 Info 頁面設定 Privacy NSPhotoLibraryAddUsageDescription:
點+號後選擇 Privacy — Photo Library Additions Usage Description

接著在 value 欄輸入給使用者的訊息文字:

參考:

  • 第二是 iPad 要注意的。當在 iPad present UIActivityViewController 時,如果沒有設定彈出框顯示的位置,會造成閃退。因此要設定 sourceView 來指定顯示位置:
@IBAction func share(_ sender: UIButton) {

let controller = UIActivityViewController(activityItems: ["Min 的 Medium", UIImage(named: "cat_koubakozuwari_brown")!, URL(string: "https://medium.com/@lebonthe")!], applicationActivities: nil)
controller.popoverPresentationController?.sourceView = sender
present(controller, animated: true, completion: nil)
}

參考:

接著使用剛剛的程式,陣列裡包含字串、圖片與網址,看看分享的結果:

分享圖片後儲存:

因為設定過 Privacy,所以有跳出顯示的中文

分享文字與網址:

Copy 後貼上會看到文字與網址

將網址存到 Reading List:

由以上三種方式可知,即便 array 中有三種不同的格式,在分享後會依據分享的方式,留下可分享的內容。

iPad 實測 — 這是加了 sourceView 的,沒有加 controller.popoverPresentationController?.sourceView = sender 的話的確什麼都顯示不出來:

UIAlertController

UIAlertController 可跳出警告、提示,並且可以做成選單。多種玩法請往下觀看:

參考:

alert style 顯示提示-畫面中間彈出的警告訊息

使用 UIAlertAction 生成視窗上的按鈕:

    @IBOutlet weak var button: UIButton!
@IBOutlet weak var textField: UITextField!

@IBAction func buttonTapped(_ sender: UIButton) {
if textField.text?.isEmpty == true {
let controller = UIAlertController(title: "怎麼可以忘了!", message: "不知道好歹也要猜一下", preferredStyle: .alert)
let okAction = UIAlertAction(title: "好吧", style: .default)
controller.addAction(okAction)
present(controller, animated: true)
}
}
如果 textField 內沒有東西,按下 button 會出現提示,裡面的文字都可自訂

UIAlertAction 的參數 style 控制按鈕文字的顏色,default 與 .cancel 是藍色,.destructive 是紅色。透過設定 UIAlertController 的 view.tintColor 可以調整 default 與 .cancel 的顏色:

controller.view.tintColor = .orange
變成橘色

不只一次 addAction 可加入多個按鈕,顯示在視窗上的位置依照加入先後的順序而定:

如果只有加入兩個按鈕,則順序為左到右。加入第三個按鈕之後置動變成豎排,上到下。

設定點選按鈕時做的事情

以 UIAlertAction 的 handler 參數傳入 closure 控制點選按鈕後執行的事情:

 let noAction = UIAlertAction(title: "告訴我", style: .destructive) { _ in
self.label.isHidden = false
}
點選告訴我之後, 隱藏的 label 就會顯示出來

如果按鈕設定成 .cacel 的樣式,則自動設定在左邊:

因為告訴我設定為 .cancel 樣式,因此到左邊。三個以上的按鈕時,則跑到最下面

在彈窗中顯示文字輸入框

從 controller.addTextField 的參數 configurationHandler 傳入 closure 設定樣式:

    @IBAction func buttonTapped(_ sender: UIButton) {
let controller = UIAlertController(title: "登入", message: "請輸入帳號密碼", preferredStyle: .alert)
controller.addTextField { textField in
textField.placeholder = "帳號"
textField.keyboardType = UIKeyboardType.emailAddress
}
controller.addTextField { textField in
textField.placeholder = "密碼"
textField.keyboardType = UIKeyboardType.phonePad
textField.isSecureTextEntry = true
}
let okAction = UIAlertAction(title: "OK", style: .default) { [unowned controller] _ in
let account = controller.textFields?[0].text
let password = controller.textFields?[1].text
self.label.text = "\(account!) 成功登入"
}
controller.addAction(okAction)
let cancelAction = UIAlertAction(title: "取消", style: .cancel)
controller.addAction(cancelAction)

present(controller, animated: true)
}
}

actionSheet style — 由下而上彈出的選單視窗

    @IBAction func buttonTapped(_ sender: UIButton) {
let controller = UIAlertController(title: "MLB問題", message: "請問下列哪位選手會出現在2024年的道奇隊", preferredStyle: .actionSheet)
let things = ["大谷翔平","大谷","翔平"]
for thing in things {
let action = UIAlertAction(title: thing, style: .default) { action in
print(action.title ?? 0)
}
controller.addAction(action)
}
let cancelAction = UIAlertAction(title: "以上皆非", style: .cancel)
controller.addAction(cancelAction)

present(controller, animated: true)
}
}
如果選項太多,會自動生成捲軸

同 SFSafariViewController,在 iPad 顯示 action sheet 也要記得設定彈出框顯示的位置。

其他相關:

AVPlayerViewController

AVPlayerView 可播放影片:

先到 apple 的預告片找預告片網址:

目前apple官網似乎沒有可以直接播放的連結了,只好先看簡體的:

    @IBAction func buttonTapped(_ sender: UIButton) {
if let url = URL(string: "https://vfx.mtime.cn/Video/2023/06/30/mp4/230630103537478111.mp4"){
let player = AVPlayer(url: url)
let controller = AVPlayerViewController()
controller.player = player
present(controller, animated: true) {
player.play()
}
}
}

UIReferenceLibraryViewController

這個 controller 可以查詢單字,但用模擬器無法測試,要裝到手機上。

參考:

為了查詢單字,先去 iOS 的 Settings>General>Dictionary 內加入字典。使用模擬器嘗試做這件事果然得到一片空白:

打開手機裡的 Dictionary,發現原來已經有非常多字典了:

下方還有數頁,族繁不及備載
    @IBAction func buttonTapped(_ sender: UIButton) {
let controller = UIReferenceLibraryViewController(term: textField.text!)
present(controller, animated: true)

}

使用 iPhone SE2 測試:

謝謝收看!

--

--