Swift - Cách phát hiện thay đổi hướng


96

Tôi muốn thêm hai hình ảnh vào chế độ xem hình ảnh đơn lẻ (tức là cho một hình ảnh ngang và cho một hình ảnh khác dọc theo chiều dọc) nhưng tôi không biết cách phát hiện các thay đổi hướng bằng ngôn ngữ nhanh.

Tôi đã thử câu trả lời này nhưng nó chỉ có một hình ảnh

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.currentDevice().orientation.isLandscape.boolValue {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

Tôi là người mới phát triển iOS, mọi lời khuyên sẽ được đánh giá cao!


1
Bạn có thể vui lòng chỉnh sửa câu hỏi và định dạng mã của bạn không? Bạn có thể làm điều đó với cmd + k khi bạn đã chọn mã của mình.
jbehrens94

2
hãy xem điều này qua stackoverflow.com/questions/25666269/…
DSAjit,

Nó có in phong cảnh / chân dung chính xác không?
Daniel

vâng nó in một cách chính xác @simpleBob
Raghuram

Bạn nên sử dụng hướng giao diện thay vì hướng thiết bị => stackoverflow.com/a/60577486/8780127
Wilfried Josset

Câu trả lời:


121
let const = "Background" //image name
let const2 = "GreyBackground" // image name
    @IBOutlet weak var imageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()

        imageView.image = UIImage(named: const)
        // Do any additional setup after loading the view.
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        if UIDevice.current.orientation.isLandscape {
            print("Landscape")
            imageView.image = UIImage(named: const2)
        } else {
            print("Portrait")
            imageView.image = UIImage(named: const)
        }
    }

Cảm ơn bạn, nó hoạt động với imageView, nếu tôi muốn thêm hình nền vào viewController thì sao ??
Raghuram

1
Đặt một imageView trên nền. Cung cấp ràng buộc cho imageView đối với trên cùng, dưới cùng, đầu, cuối cho chế độ xem chính ViewController để bao phủ tất cả nền.
Rutvik Kanbargi

4
Đừng quên gọi super.viewWillTransitionToSize trong phương pháp ghi đè của bạn
Brian Sachetta

Bạn có biết tại sao tôi không thể ghi đè viewWillTransitionkhi phân lớp con UIViewkhông?
Xcoder

2
Còn có một loại định hướng: .isFlat. Nếu bạn chỉ muốn nắm bắt, portraittôi khuyên bạn nên thay đổi mệnh đề else thành else if UIDevice.current.orientation.isPortrait. Lưu ý: .isFlatcó thể xảy ra ở cả hai, dọc và ngang, và chỉ với khác {}, nó sẽ luôn mặc định ở đó (ngay cả khi đó là bố cục phẳng).
PMT

78

Sử dụng NotificationCenterUIDevice củabeginGeneratingDeviceOrientationNotifications

Swift 4.2+

override func viewDidLoad() {
    super.viewDidLoad()        

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}

deinit {
   NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)         
}

func rotated() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

Swift 3

override func viewDidLoad() {
    super.viewDidLoad()        

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}

deinit {
     NotificationCenter.default.removeObserver(self)
}

func rotated() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

1
Chắc chắn - cách tiếp cận của tôi cần một số mã bổ sung. Nhưng nó thực sự "phát hiện những thay đổi định hướng".
maslovsa

3
@Michael Bởi vì viewWillTransition:dự đoán thay đổi sẽ xảy ra trong khi sử dụng UIDeviceOrientationDidChangeđược gọi sau khi thay đổi hoàn tất.
Lyndsey Scott

1
@LyndseyScott Tôi vẫn tin rằng hành vi được mô tả có thể đạt được mà không cần sử dụng NSNotificationCenter tiềm ẩn nguy hiểm. Vui lòng xem lại câu trả lời sau và cho tôi biết suy nghĩ của bạn: stackoverflow.com/a/26944087/1306884
Michael

4
Điều thú vị trong câu trả lời này là điều này sẽ cung cấp hướng hiện tại ngay cả khi bộ điều khiển chế độ xem shouldAutorotateđược đặt thành false. Điều này rất tiện lợi cho các màn hình như màn hình chính của ứng dụng Máy ảnh - hướng bị khóa nhưng các nút có thể xoay.
yoninja

1
Kể từ iOS 9, bạn thậm chí không cần phải gọi deinit một cách rõ ràng. Bạn có thể đọc thêm về nó ở đây: useyourloaf.com/blog/…
James Jordan Taylor

63

Swift 3 Đã cập nhật mã trên:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

1
Bạn có biết tại sao tôi không thể ghi đè viewWillTransition khi phân lớp phụ UIView không?
Xcoder

Điều này sẽ bị lỗi nếu bạn cố gắng tham chiếu bất kỳ chế độ xem nào trong bộ điều khiển.
James Jordan Taylor

@JamesJordanTaylor, bạn có thể vui lòng giải thích tại sao nó sẽ bị lỗi không?
orium

Cách đây 6 tháng khi tôi nhận xét, nhưng tôi nghĩ lý do Xcode đưa ra khi tôi thử nó là một ngoại lệ con trỏ nil vì bản thân chế độ xem vẫn chưa được khởi tạo, chỉ là viewController. Mặc dù vậy, tôi có thể đánh số sai.
James Jordan Taylor

@Xcoder đây là một hàm trên UIViewController không phải UIView - đó là lý do tại sao bạn không thể ghi đè nó.
LightningStryk

25

⚠️Device Orientation! = Định hướng giao diện⚠️

Swift 5. * iOS14 trở xuống

Bạn thực sự nên tạo ra sự khác biệt giữa:

  • Hướng thiết bị => Cho biết hướng của thiết bị vật lý
  • Hướng giao diện => Cho biết hướng của giao diện hiển thị trên màn hình

Có nhiều trường hợp trong đó 2 giá trị đó không khớp, chẳng hạn như:

  • Khi bạn khóa hướng màn hình của mình
  • Khi bạn đặt thiết bị của mình phẳng

Trong hầu hết các trường hợp, bạn muốn sử dụng hướng giao diện và bạn có thể lấy nó qua cửa sổ:

private var windowInterfaceOrientation: UIInterfaceOrientation? {
    return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
}

Trong trường hợp bạn cũng muốn hỗ trợ <iOS 13 (chẳng hạn như iOS 12), bạn sẽ làm như sau:

private var windowInterfaceOrientation: UIInterfaceOrientation? {
    if #available(iOS 13.0, *) {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
    } else {
        return UIApplication.shared.statusBarOrientation
    }
}

Bây giờ bạn cần xác định nơi phản ứng với sự thay đổi hướng giao diện cửa sổ. Có nhiều cách để làm điều đó nhưng giải pháp tối ưu là thực hiện nó bên trong willTransition(to newCollection: UITraitCollection.

Phương thức UIViewController kế thừa này có thể bị ghi đè sẽ được kích hoạt mỗi khi hướng giao diện thay đổi. Do đó, bạn có thể thực hiện tất cả các sửa đổi của mình trong phần sau.

Đây là một ví dụ giải pháp :

class ViewController: UIViewController {
    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)
        
        coordinator.animate(alongsideTransition: { (context) in
            guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }
            
            if windowInterfaceOrientation.isLandscape {
                // activate landscape changes
            } else {
                // activate portrait changes
            }
        })
    }
    
    private var windowInterfaceOrientation: UIInterfaceOrientation? {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
    }
}

Bằng cách triển khai phương pháp này, bạn sẽ có thể phản ứng với bất kỳ sự thay đổi hướng nào đối với giao diện của mình. Nhưng hãy nhớ rằng nó sẽ không được kích hoạt khi mở ứng dụng, vì vậy bạn cũng sẽ phải cập nhật giao diện của mình theo cách thủ công viewWillAppear().

Tôi đã tạo một dự án mẫu nhấn mạnh sự khác biệt giữa hướng thiết bị và hướng giao diện. Ngoài ra, nó sẽ giúp bạn hiểu các hành vi khác nhau tùy thuộc vào bước vòng đời nào bạn quyết định cập nhật giao diện người dùng của mình.

Hãy sao chép và chạy kho lưu trữ sau: https://github.com/wjosset/ReactToOrientation


làm thế nào để làm điều này trước iOS 13?
Daniel Springer

1
@DanielSpringer cảm ơn bạn đã nhận xét, tôi đã chỉnh sửa bài đăng để hỗ trợ iOS 12 trở xuống
Wilfried Josset

1
Tôi thấy câu trả lời này là tốt nhất và nhiều thông tin nhất. Nhưng thử nghiệm trên iPadOS13.5, việc sử dụng gợi ý func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)không hoạt động. Tôi đã làm cho nó hoạt động bằng cách sử dụng func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator).
Andrej

12

Swift 4+: Tôi đang sử dụng cái này cho thiết kế bàn phím mềm và vì một số lý do mà UIDevice.current.orientation.isLandscapephương pháp này liên tục cho tôi biết nó Portraitnhư vậy, vì vậy đây là những gì tôi đã sử dụng thay thế:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    if(size.width > self.view.frame.size.width){
        //Landscape
    }
    else{
        //Portrait
    }
}

tôi cũng gặp sự cố tương tự trong ứng dụng iMessage của mình. Nó không thực sự kỳ lạ phải không? Và, ngay cả giải pháp của bạn cũng hoạt động ngược lại !! ¯_ (ツ) _ / ¯
Shyam

Không sử dụng UIScreen.main.bounds, vì nó có thể cung cấp cho bạn các giới hạn màn hình trước khi chuyển đổi (lưu ý đến 'Ý chí' trong phương pháp), đặc biệt là trên nhiều góc quay nhanh. Bạn nên sử dụng các tham số 'kích thước' ...
Dan Bodnar

@ dan-bodnar Ý bạn là tham số nativeBounds?
asetniop

3
@asetniop, không. Các sizetham số được tiêm trong viewWillTransitionToSize: withCoordinator: phương pháp mà bạn viết ở trên. Điều này sẽ phản ánh kích thước chính xác mà chế độ xem của bạn sẽ có sau khi chuyển đổi.
Dan Bodnar

Đây không phải là một giải pháp tốt nếu bạn đang sử dụng bộ điều khiển chế độ xem con, kích thước của vùng chứa có thể luôn là hình vuông, bất kể hướng nào.
bojan

8

Swift 4.2, RxSwift

Nếu chúng ta cần tải lại collectionView.

NotificationCenter.default.rx.notification(UIDevice.orientationDidChangeNotification)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)

Swift 4, RxSwift

Nếu chúng ta cần tải lại collectionView.

NotificationCenter.default.rx.notification(NSNotification.Name.UIDeviceOrientationDidChange)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)

5

Tôi tin rằng câu trả lời đúng thực sự là sự kết hợp của cả hai cách tiếp cận: viewWIllTransition(toSize:)NotificationCenter's UIDeviceOrientationDidChange.

viewWillTransition(toSize:)thông báo cho bạn trước khi chuyển đổi.

NotificationCenter UIDeviceOrientationDidChangethông báo cho bạn sau .

Bạn phải rất cẩn thận. Ví dụ, trong UISplitViewControllerkhi xoay thiết bị vào định hướng nhất định, DetailViewControllerđược popped ra khỏi UISplitViewControllers' viewcontrollersmảng, và đẩy lên thạc sĩ UINavigationController. Nếu bạn tìm kiếm bộ điều khiển chế độ xem chi tiết trước khi quá trình quay kết thúc, nó có thể không tồn tại và bị lỗi.


5

Nếu bạn đang sử dụng phiên bản Swift> = 3.0, có một số cập nhật mã bạn phải áp dụng như những người khác đã nói. Chỉ cần đừng quên gọi cho siêu:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {

   super.viewWillTransition(to: size, with: coordinator)

   // YOUR CODE OR FUNCTIONS CALL HERE

}

Nếu bạn đang nghĩ đến việc sử dụng StackView cho hình ảnh của mình, hãy lưu ý rằng bạn có thể thực hiện một số việc như sau:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {

   super.viewWillTransition(to: size, with: coordinator)

   if UIDevice.current.orientation.isLandscape {

      stackView.axis = .horizontal

   } else {

      stackView.axis = .vertical

   } // else

}

Nếu bạn đang sử dụng Trình tạo giao diện, đừng quên chọn lớp tùy chỉnh cho đối tượng UIStackView này, trong phần Trình kiểm tra danh tính ở bảng bên phải. Sau đó, chỉ cần tạo (cũng thông qua Trình tạo giao diện) tham chiếu IBOutlet đến cá thể UIStackView tùy chỉnh:

@IBOutlet weak var stackView: MyStackView!

Lên ý tưởng và điều chỉnh nó theo nhu cầu của bạn. Hy vọng điều này có thể giúp bạn!


Điểm tốt về gọi siêu. Việc không gọi phương thức super trong một hàm bị ghi đè sẽ thực sự cẩu thả và nó có thể tạo ra hành vi không thể đoán trước trong ứng dụng. Và những lỗi tiềm ẩn thực sự khó theo dõi!
Adam Freeman

Bạn có biết tại sao tôi không thể ghi đè viewWillTransition khi phân lớp phụ UIView không?
Xcoder

@Xcoder Lý do (thường) của một đối tượng không áp dụng ghi đè nằm ở thể hiện thời gian chạy không được tạo bằng lớp tùy chỉnh mà thay vào đó là lớp mặc định. Nếu bạn đang sử dụng Trình tạo giao diện, hãy chắc chắn chọn lớp tùy chỉnh cho đối tượng UIStackView này, trong phần Trình kiểm tra danh tính ở bảng bên phải.
WeiseRatel

5

Swift 4

Tôi đã gặp một số vấn đề nhỏ khi cập nhật chế độ xem ViewControllers bằng cách sử dụng UIDevice.current.orientation, chẳng hạn như cập nhật các ràng buộc của ô chế độ xem bảng trong quá trình xoay hoặc hoạt ảnh của các chế độ xem phụ .

Thay vì các phương pháp trên, tôi hiện đang so sánh kích thước chuyển tiếp với kích thước chế độ xem bộ điều khiển chế độ xem. Đây có vẻ là cách thích hợp để đi vì người ta có quyền truy cập vào cả hai tại điểm này trong mã:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    print("Will Transition to size \(size) from super view size \(self.view.frame.size)")

    if (size.width > self.view.frame.size.width) {
        print("Landscape")
    } else {
        print("Portrait")
    }

    if (size.width != self.view.frame.size.width) {
        // Reload TableView to update cell's constraints.
    // Ensuring no dequeued cells have old constraints.
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }


}

Đầu ra trên iPhone 6:

Will Transition to size (667.0, 375.0) from super view size (375.0, 667.0) 
Will Transition to size (375.0, 667.0) from super view size (667.0, 375.0)

Tôi có nên bật hỗ trợ định hướng từ cấp dự án không?
Ericpoon

3

Tất cả các đóng góp trước đó đều ổn, nhưng một lưu ý nhỏ:

a) nếu hướng được đặt trong plist, chỉ dọc hoặc ví dụ, Bạn sẽ không được thông báo qua viewWillTransition

b) nếu chúng tôi vẫn cần biết liệu người dùng có xoay thiết bị hay không, (ví dụ: một trò chơi hoặc tương tự ..), chúng tôi chỉ có thể sử dụng:

NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

đã thử nghiệm trên Xcode8, iOS11


2

Bạn có thể sử dụng viewWillTransition(to:with:)và nhấn vào animate(alongsideTransition:completion:)để lấy hướng giao diện SAU KHI quá trình chuyển đổi hoàn tất. Bạn chỉ cần xác định và triển khai một giao thức tương tự như thế này để tham gia vào sự kiện. Lưu ý rằng mã này được sử dụng cho trò chơi SpriteKit và cách triển khai cụ thể của bạn có thể khác.

protocol CanReceiveTransitionEvents {
    func viewWillTransition(to size: CGSize)
    func interfaceOrientationChanged(to orientation: UIInterfaceOrientation)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        guard
            let skView = self.view as? SKView,
            let canReceiveRotationEvents = skView.scene as? CanReceiveTransitionEvents else { return }

        coordinator.animate(alongsideTransition: nil) { _ in
            if let interfaceOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation {
                canReceiveRotationEvents.interfaceOrientationChanged(to: interfaceOrientation)
            }
        }

        canReceiveRotationEvents.viewWillTransition(to: size)
    }

Bạn có thể đặt các điểm ngắt trong các hàm này và quan sát điểm đó interfaceOrientationChanged(to orientation: UIInterfaceOrientation)luôn được gọi sau viewWillTransition(to size: CGSize)với hướng cập nhật.


1

Để có được định hướng chính xác khi khởi động ứng dụng, bạn phải kiểm tra nó viewDidLayoutSubviews(). Các phương pháp khác được mô tả ở đây sẽ không hoạt động.

Dưới đây là một ví dụ về cách thực hiện:

var mFirstStart = true

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if (mFirstStart) {
        mFirstStart = false
        detectOrientation()
    }
}

func detectOrientation() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
        // do your stuff here for landscape
    } else {
        print("Portrait")
        // do your stuff here for portrait
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    detectOrientation()
}

Điều này sẽ luôn hoạt động, khi ứng dụng bắt đầu lần đầu tiên và nếu xoay trong khi ứng dụng đang chạy .


0

Một cách khác để phát hiện hướng thiết bị là sử dụng hàm traitCollectionDidChange (_ :). Hệ thống gọi phương thức này khi môi trường giao diện iOS thay đổi.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
    super.traitCollectionDidChange(previousTraitCollection)
    //...
}

Hơn nữa, bạn có thể sử dụng hàm willTransition (to: with :) (được gọi trước traitCollectionDidChange (_ :)), để lấy thông tin ngay trước khi định hướng được áp dụng.

 override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    //...
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.