Swift 不使用 Storyboard 的前置作業

Howewu
14 min readDec 13, 2023

--

少年啊,要挑戰一下全代碼編程嗎?

也許會遇到被要求不用 Storyboard 的前提進行編程開發,這裡記錄一下詢問 GPT 大概的流程是什麼,見以下

如果沒有要使用 storyboard,採用全編碼方式編程應該要怎麼做?

GPT :

首先,要從你的AppDelegateSceneDelegate(取決於你的專案設定)開始。在這裡,你需要創建一個窗口(UIWindow),並設置一個根視圖控制器。不像在Storyboard中,一切都要手動設置。

接著,你的視圖控制器(UIViewController)將會是構建使用者介面的主戰場。這裡你可以創建視圖(UIView)、按鈕(UIButton)、標籤(UILabel)等UI元件,並且透過程式碼來管理它們的佈局。Auto Layout是你的好朋友,使用約束(Constraints)來確保你的UI在不同裝置和螢幕尺寸上看起來仍然完美。

最後,別忘了對事件進行處理。在Storyboard中,你可能習慣了拖拉連線來處理按鈕點擊等事件,但在全編碼方式中,你需要使用 addTarget 方法來添加這些事件。

這裡有個簡單的範例代碼,幫助你開始:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
}

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white

let label = UILabel()
label.text = "Hello, World!"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)

NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}

這個例子展示了如何創建一個簡單的視圖控制器,並在其中加入一個標籤。當然,這只是冰山一角,真正的挑戰在於如何將這種方式應用於更複雜的界面設計中。

我需要把什麼東西刪掉嗎?

GPT :

當你決定不使用Storyboard,並完全採用編碼方式時,的確有一些事情需要做來調整你的專案設定。首先,你需要移除現有的Storyboard文件,然後更新專案設定,以確保應用啟動時不會尋找或加載Storyboard。來,跟著我做:

刪除Storyboard文件:

  • 打開你的Xcode專案,找到Main.storyboard(和LaunchScreen.storyboard,如果你也想用代碼來處理啟動畫面的話),右鍵它們,然後選擇"Move to Trash"(移到垃圾桶)。

更新專案設定:

  • 打開你的專案設定,選擇你的應用目標(Target)。
  • 在”General”標籤頁下,尋找”Main Interface”,並清空這個欄位(這會告訴Xcode你的應用不會使用Storyboard來加載初始視圖控制器)。
  • 如果你刪除了LaunchScreen.storyboard,也在"App Icons and Launch Images"下的"Launch Screen File"欄位清空。

修改Info.plist文件:

  • 如果Info.plist中有參考到Storyboard的話(如UIMainStoryboardFile),你需要移除這些參考。
  • 如果你移除了LaunchScreen.storyboard,也確保從Info.plist中刪除有關啟動畫面的設定。

更新應用代理(AppDelegate):

  • 確保在AppDelegateapplication(_:didFinishLaunchingWithOptions:)方法中創建一個UIWindow實例,並設置一個根視圖控制器,如我之前提供的範例代碼所示。

最後的檢查:

  • 清理(Clean)你的專案。
  • 建構(Build)並運行(Run),看看是否一切運作正常。

完成這些步驟後,你的專案應該已經完全從Storyboard轉換為編碼方式了。這是一個重要的變更,但只要細心地跟著步驟來,就不會有太大問題。當然,從現在開始,你的UI設計和佈局都將依賴於Swift代碼了。

基於以上條件,如要使用 UITabbarController 需要保留 SceneDelegate 嗎?

GPT :

當你採用全編碼方式開發iOS應用,並且打算使用UITabBarController時,你確實需要保留SceneDelegate(如果你的專案是在iOS 13或更高版本上建立的)。在iOS 13及以後的版本中,SceneDelegate負責管理應用的視圖和生命週期,這與之前的版本(只用AppDelegate)有所不同。

這裡要強調的是,SceneDelegateAppDelegate在應用架構中扮演不同的角色:

  • AppDelegate 負責全局的應用級別事件,例如應用啟動、終止等。
  • SceneDelegate 則更集中於管理特定的UI場景(scene)。在支持多任務的iOS設備上,一個應用可以同時擁有多個活躍的場景。

要在全編碼方式下使用UITabBarController,你可以在SceneDelegate中設置它作為窗口(window)的根視圖控制器。以下是一個簡單的例子:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }

window = UIWindow(windowScene: windowScene)

// 創建 UITabBarController 並設置它的視圖控制器
let tabBarController = UITabBarController()
tabBarController.viewControllers = [
createViewController(title: "首頁", backgroundColor: .white),
createViewController(title: "設定", backgroundColor: .lightGray)
]

window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
}

private func createViewController(title: String, backgroundColor: UIColor) -> UIViewController {
let viewController = UIViewController()
viewController.view.backgroundColor = backgroundColor
viewController.tabBarItem.title = title
return viewController
}
}

這個範例中,我們創建了一個UITabBarController,並為它添加了兩個簡單的視圖控制器作為標籤。你可以根據需求進行擴展,加入更多的功能和自定義。

記住,即使是在全編碼的情況下,SceneDelegate仍然扮演著重要的角色。當你開始搭建你的應用界面時,不要忽略了它。

我該如何判斷 SceneDelegate 是否要刪掉?

GPT :

判斷是否需要刪除SceneDelegate取決於幾個因素,包括你的應用目標iOS版本、你希望應用支援的功能,以及你的個人偏好。讓我來幫你梳理一下:

目標iOS版本:

  • 如果你的應用僅需支持iOS 13及以上版本,保留SceneDelegate是有意義的,因為它提供了更靈活的多任務和多窗口管理功能。
  • 如果你的應用需要兼容iOS 12或更早版本,你可能會考慮移除SceneDelegate,因為這些版本的iOS並不支持多場景(scene)API。

多任務和多窗口支援:

  • SceneDelegate的出現主要是為了支援iPad上的多任務功能,如分屏和多窗口。如果你的應用需要這些功能,或者你想為iPad用戶提供更佳體驗,則應保留SceneDelegate
  • 如果你的應用主要是為iPhone用戶設計,且不需要多任務或多窗口支援,你可以選擇移除SceneDelegate

個人偏好和應用架構:

  • 有些開發者喜歡保留SceneDelegate,因為它可以使應用架構更清晰,並將UI相關的代碼與其他應用生命週期代碼分離。
  • 如果你偏好一個更簡單的架構,並且不需要SceneDelegate提供的特定功能,那麼刪除它可能會讓你的應用更加精簡。

如果你決定移除SceneDelegate,記得要進行以下步驟:

  • 從你的專案中刪除SceneDelegate.swift文件。
  • 更新Info.plist文件,移除有關UIScene的配置。
  • 修改AppDelegate以管理窗口(UIWindow)的創建和顯示。

最後,無論你選擇保留還是刪除SceneDelegate,都要確保你的決定符合你的應用需求和目標。

假設我將 SceneDelegate 刪掉的話,UITabBarController 該如何實現?

GPT :

如果你決定刪除SceneDelegate並使用UITabBarController,你需要在AppDelegate中處理窗口(UIWindow)的創建和配置根視圖控制器。這意味著所有的界面設置和初始視圖控制器的配置將在AppDelegate中完成。

以下是實現UITabBarController的步驟,假設你已經從專案中移除了SceneDelegate

AppDelegate中創建窗口: 首先,需要在AppDelegate中創建一個新的UIWindow實例,並將其設置為應用的主窗口。

配置UITabBarController: 接著,創建一個UITabBarController實例,並將其設為窗口的根視圖控制器。你可以在這裡添加多個子視圖控制器到標籤欄控制器。

顯示窗口: 最後,需要讓窗口可見,並設置它為主窗口。

以下是一個基本的示例代碼,展示如何在AppDelegate中設置一個包含兩個標籤的UITabBarController

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }

window = UIWindow(windowScene: windowScene)

// 創建 UITabBarController 並設置它的視圖控制器
let tabBarController = UITabBarController()
tabBarController.viewControllers = [
createViewController(title: "首頁", backgroundColor: .white),
createViewController(title: "設定", backgroundColor: .lightGray)
]

window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
}

private func createViewController(title: String, backgroundColor: UIColor) -> UIViewController {
let viewController = UIViewController()
viewController.view.backgroundColor = backgroundColor
viewController.tabBarItem.title = title
return viewController
}
}

在這個例子中,setupTabBarController函數創建了一個UITabBarController,並為它添加了兩個簡單的視圖控制器作為標籤。你可以根據自己的需求自定義視圖控制器。

記住,一旦移除SceneDelegate,所有與窗口和視圖控制器相關的配置都應該在AppDelegate中完成。這樣做的好處是結構更簡單、更直接,尤其是對於不需要iOS 13的多場景特性的應用來說。

開頭的 @UIApplicationMain 跟 @main 有什麼差別嗎?

GPT :

@UIApplicationMain@main都用於標記一個Swift類別作為應用的入口點,但它們在使用和語義上有一些差異:

@UIApplicationMain

  • 這是較舊的標籤,用於iOS應用開發。
  • 當你使用@UIApplicationMain標記一個類別時,這個類別需要實現UIApplicationDelegate協議。
  • 這個標記會讓Swift自動生成應用的主函數(main函數)。在這種情況下,你不需要手動寫main.swift文件。
  • 它特別針對UIApplication,是iOS開發中的特定用法。

@main

  • @main是一個更現代、更通用的入口點標記,引入於Swift 5.3。
  • 它可以用於任何Swift應用或程序,不僅限於iOS。這意味著你可以在其他類型的Swift項目中,比如命令行工具或者其他平台的應用中使用它。
  • @main標記的類別同樣是程序的入口點,但這個類別不一定要實現特定的協議。相反,它通常有一個無參數的靜態方法main(),作為程序開始執行的地方。

在大多數iOS應用案例中,@UIApplicationMain@main可以互換使用。然而,如果你在開發非iOS平台的Swift應用或想要更多的靈活性,@main是一個更通用的選擇。舉個例子,如果你想要自己管理main函數的行為,或者在一個Swift程序中有多個類別並希望指定其中一個作為入口點,那麼@main會是更合適的選擇。

總的來說,@UIApplicationMain更適用於標準的iOS應用開發流程,而@main提供了更多的通用性和靈活性,適用於各種Swift程序。

--

--