首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

iOS 8+ 強制單一 View Controller 橫向顯示:仿射變換動畫實作指南

     編輯:程式設計綜合問答

背景:iOS 8 後官方 API 限制

iOS 8 起,Apple 收緊 UIDevice.setValue(_:forKey: "orientation") 等私有 API,且 supportedInterfaceOrientations、shouldAutorotate 等機制在導航控制器、分頁控制器嵌套場景下行為不一致,導致「單頁強制橫向」變得棘手。

主流解決方案:仿射變換 + 狀態同步

核心思路:不改裝置方向,只旋轉畫面內容。優酷、愛奇藝等主流視訊 App 採用此架構。

完整實作步驟

1. 定義旋轉協議與基底類別

protocol RotatableViewController: AnyObject {
    var isLandscape: Bool { get set }
    func rotateToLandscape(animated: Bool)
    func rotateToPortrait(animated: Bool)
}

extension RotatableViewController where Self: UIViewController {
    func rotateToLandscape(animated: Bool = true) {
        guard !isLandscape else { return }
        isLandscape = true
        
        let transform = CGAffineTransform(rotationAngle: .pi / 2)
            .concatenating(CGAffineTransform(translationX: 0, y: 0))
        
        let animations = {
            self.view.transform = transform
            self.view.bounds = UIScreen.main.bounds // 關鍵:重設 bounds 適配新方向
        }
        
        if animated {
            UIView.animate(withDuration: 0.3, animations: animations)
        } else {
            animations()
        }
    }
    
    func rotateToPortrait(animated: Bool = true) {
        guard isLandscape else { return }
        isLandscape = false
        
        let animations = {
            self.view.transform = .identity
            self.view.bounds = UIScreen.main.bounds
        }
        
        if animated {
            UIView.animate(withDuration: 0.3, animations: animations)
        } else {
            animations()
        }
    }
}

2. 目標 View Controller 實作

class VideoPlayerViewController: UIViewController, RotatableViewController {
    var isLandscape = false
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        rotateToLandscape() // 進入即強制橫向
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        rotateToPortrait() // 離開恢復直向
    }
    
    // 支援裝置實際旋轉時的同步
    override var shouldAutorotate: Bool { false } // 禁用系統自動旋轉
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask { .portrait }
    override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { .portrait }
}

3. AppDelegate 全域方向控制

func application(_ application: UIApplication, 
                 supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    // 只有特定 VC 允許橫向
    if let topVC = topViewController(),
       topVC is RotatableViewController,
       (topVC as! RotatableViewController).isLandscape {
        return .landscapeRight
    }
    return .portrait
}

關鍵技術細節

問題解決方案
狀態列不旋轉重寫 prefersStatusBarHidden、preferredStatusBarStyle,動畫同步更新
子視圖佈局錯亂使用 Auto Layout + view.bounds 重設,避免硬編碼 frame
轉場動畫衝突在 viewWillDisappear 完成旋轉後再執行 pop/dismiss
iPad 多任務分割視圖檢查 traitCollection.horizontalSizeClass,分割模式下禁用強制旋轉
安全區域適配旋轉後呼叫 view.setNeedsLayout()、view.layoutIfNeeded()

進階:支援互動式旋轉手勢

// 滑動邊緣觸發旋轉(類似控制中心)
let edgePan = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleEdgePan(_:)))
edgePan.edges = .right
view.addGestureRecognizer(edgePan)

@objc func handleEdgePan(_ recognizer: UIScreenEdgePanGestureRecognizer) {
    let progress = recognizer.translation(in: view).x / view.bounds.width
    switch recognizer.state {
    case .changed: updateRotationProgress(progress)
    case .ended: progress > 0.5 ? commitRotation() : cancelRotation()
    default: break
    }
}

方案對比總結

方案優點缺點適用場景
仿射變換動畫相容性佳、動畫流暢、不影響其他頁面需手動處理狀態列、安全區域、轉場協調視訊播放、遊戲、特定功能頁
私有 API (setValue)程式碼極少App Store 審核風險、iOS 版本易失效不推薦
獨立 UIWindow + rootVC完全隔離、系統級旋轉記憶體開銷大、狀態同步複雜全螢幕沉浸式體驗
UINavigationController 子類別集中管理推疊頁面方向侵入性強、耦合度高整個導航棧需統一方向

結語

iOS 8+ 單頁強制橫向的工業級標準做法是 仿射變換動畫 + 狀態機同步。雖需處理狀態列、安全區域、轉場協調等細節,但提供最穩定的跨版本相容性與使用者體驗。建議封裝為可復用的 RotatableViewController 協議與基底類別,專案內統一管理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved