Swift - วิธีตรวจจับการเปลี่ยนแปลงทิศทาง


96

ฉันต้องการเพิ่มภาพสองภาพในมุมมองภาพเดียว (เช่นสำหรับภาพแนวนอนภาพหนึ่งและสำหรับภาพแนวตั้งอีกภาพหนึ่ง) แต่ฉันไม่รู้วิธีตรวจจับการเปลี่ยนแปลงทิศทางโดยใช้ภาษาที่รวดเร็ว

ฉันลองใช้คำตอบนี้ แต่ใช้เพียงภาพเดียว

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

ฉันยังใหม่กับการพัฒนา iOS คำแนะนำใด ๆ จะได้รับการชื่นชมอย่างมาก!


1
คุณช่วยแก้ไขคำถามและจัดรูปแบบรหัสของคุณได้ไหม คุณสามารถทำได้ด้วย cmd + k เมื่อคุณเลือกรหัสแล้ว
jbehrens94


พิมพ์แนวนอน / แนวตั้งได้ถูกต้องหรือไม่?
Daniel

ใช่มันพิมพ์ถูกต้อง @simpleBob
Raghuram

คุณควรใช้การวางแนวอินเทอร์เฟซแทนการวางแนวอุปกรณ์ => stackoverflow.com/a/60577486/8780127
Wilfried Josset

คำตอบ:


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)
        }
    }

ขอบคุณที่มันใช้งานได้กับ imageView ถ้าฉันต้องการเพิ่มภาพพื้นหลังใน viewController ล่ะ?
Raghuram

1
ใส่ imageView บนพื้นหลัง กำหนดข้อ จำกัด ให้กับ imageView สำหรับมุมมองด้านบนด้านล่างนำหน้าต่อท้ายไปยังมุมมองหลักของ ViewController เพื่อให้ครอบคลุมพื้นหลังทั้งหมด
Rutvik Kanbargi

4
อย่าลืมเรียก super.viewWillTransitionToSize ภายในวิธีการแทนที่ของคุณ
Brian Sachetta

คุณรู้ไหมว่าทำไมฉันไม่สามารถแทนที่viewWillTransitionเมื่อคลาสย่อยได้UIView?
Xcoder

2
มีประเภทการวางแนวอื่น: .isFlat. ถ้าคุณเพียงต้องการที่จะจับผมขอแนะนำให้คุณเปลี่ยนข้ออื่นportrait else if UIDevice.current.orientation.isPortraitหมายเหตุ: .isFlatสามารถเกิดขึ้นได้ทั้งในแนวตั้งและแนวนอนและอื่น ๆ {} ค่าเริ่มต้นจะอยู่ที่นั่นเสมอ (แม้ว่าจะเป็นแลนสเคปแบบแบนก็ตาม)
PMT

78

ใช้NotificationCenterและUIDevice 'sbeginGeneratingDeviceOrientationNotifications

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")
    }
}

สวิฟต์ 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
แน่นอน - แนวทางของฉันต้องการรหัสเพิ่มเติม แต่ "ตรวจพบการเปลี่ยนแปลงการวางแนว" จริงๆ
maslovsa

3
@ ไมเคิลเนื่องจากviewWillTransition:คาดว่าการเปลี่ยนแปลงจะเกิดขึ้นในขณะที่ใช้UIDeviceOrientationDidChangeเรียกว่าเมื่อการเปลี่ยนแปลงเสร็จสมบูรณ์
Lyndsey Scott

1
@LyndseyScott ฉันยังคงเชื่อว่าพฤติกรรมที่อธิบายไว้สามารถทำได้โดยไม่ต้องใช้ NSNotificationCenter ที่อาจเป็นอันตราย โปรดตรวจสอบคำตอบต่อไปนี้และแจ้งให้เราทราบความคิดของคุณstackoverflow.com/a/26944087/1306884
Michael

4
สิ่งที่น่าสนใจในคำตอบนี้ก็คือว่านี้จะให้การปฐมนิเทศปัจจุบันแม้ในขณะที่ตัวควบคุมมุมมองที่มีการตั้งค่าshouldAutorotate falseสิ่งนี้มีประโยชน์สำหรับหน้าจอเช่นหน้าจอหลักของแอพกล้อง - การวางแนวถูกล็อค แต่ปุ่มสามารถหมุนได้
yoninja

1
ใน iOS 9 คุณไม่จำเป็นต้องโทรไปที่ deinit อย่างชัดเจน สามารถอ่านเพิ่มเติมได้ที่นี่useyourloaf.com/blog/…
James Jordan Taylor

63

อัปเดตรหัสSwift 3ด้านบน:

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
คุณรู้หรือไม่ว่าเหตุใดฉันจึงไม่สามารถแทนที่ viewWillTransition เมื่อคลาสย่อย UIView ได้
Xcoder

สิ่งนี้จะผิดพลาดหากคุณพยายามอ้างอิงมุมมองใด ๆ ในตัวควบคุม
James Jordan Taylor

@JamesJordanTaylor คุณช่วยอธิบายได้ไหมว่าทำไมมันถึงพัง?
orium

เมื่อ 6 เดือนที่แล้วเมื่อฉันแสดงความคิดเห็น แต่ฉันคิดว่าเหตุผลที่ Xcode ให้เมื่อฉันลองใช้มันเป็นข้อยกเว้นของตัวชี้ศูนย์เนื่องจากมุมมองนั้นยังไม่ได้รับการสร้างอินสแตนซ์เพียงแค่ viewController ฉันอาจจะจำผิด
James Jordan Taylor

@Xcoder นี่เป็นฟังก์ชั่นบน UIViewController ไม่ใช่ UIView - นั่นคือเหตุผลที่คุณไม่สามารถแทนที่ได้
LightningStryk

25

⚠️Device Orientation! = Interface Orientation⚠️

Swift 5. * iOS14 และต่ำกว่า

คุณควรสร้างความแตกต่างระหว่าง:

  • Device Orientation => ระบุการวางแนวของอุปกรณ์จริง
  • Interface Orientation => ระบุการวางแนวของอินเทอร์เฟซที่แสดงบนหน้าจอ

มีหลายสถานการณ์ที่ทั้ง 2 ค่าไม่ตรงกันเช่น:

  • เมื่อคุณล็อกการวางแนวหน้าจอ
  • เมื่อคุณมีอุปกรณ์ของคุณแบน

ในกรณีส่วนใหญ่คุณต้องการใช้การวางแนวอินเทอร์เฟซและคุณสามารถรับได้ทางหน้าต่าง:

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

ในกรณีที่คุณต้องการรองรับ <iOS 13 (เช่น iOS 12) คุณต้องดำเนินการดังต่อไปนี้:

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

ตอนนี้คุณต้องกำหนดตำแหน่งที่จะตอบสนองต่อการเปลี่ยนแปลงการวางแนวของหน้าต่าง มีหลายวิธีที่จะทำ willTransition(to newCollection: UITraitCollectionแต่จะมีทางออกที่ดีที่สุดคือการทำมันภายใน

วิธีการ UIViewController ที่สืบทอดมานี้ซึ่งสามารถแทนที่ได้จะถูกทริกเกอร์ทุกครั้งที่การวางแนวของอินเทอร์เฟซจะเปลี่ยนไป ดังนั้นคุณสามารถทำการปรับเปลี่ยนทั้งหมดได้ในภายหลัง

นี่คือตัวอย่างการแก้ปัญหา :

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
    }
}

เมื่อใช้วิธีนี้คุณจะสามารถตอบสนองต่อการเปลี่ยนแปลงการวางแนวในอินเทอร์เฟซของคุณได้ แต่เก็บไว้ในใจว่ามันจะไม่ถูกเรียกที่เปิดของ app viewWillAppear()เพื่อให้คุณยังจะมีการปรับปรุงอินเตอร์เฟซของคุณด้วยตนเองใน

ฉันได้สร้างโครงการตัวอย่างที่เน้นความแตกต่างระหว่างการวางแนวอุปกรณ์และการวางแนวอินเทอร์เฟซ นอกจากนี้ยังช่วยให้คุณเข้าใจพฤติกรรมที่แตกต่างกันขึ้นอยู่กับขั้นตอนวงจรชีวิตที่คุณตัดสินใจอัปเดต UI ของคุณ

อย่าลังเลที่จะโคลนและเรียกใช้ที่เก็บต่อไปนี้: https://github.com/wjosset/ReactToOrientation


จะทำอย่างไรก่อน iOS 13?
Daniel Springer

1
@DanielSpringer ขอบคุณสำหรับความคิดเห็นของคุณฉันได้แก้ไขโพสต์เพื่อรองรับ iOS 12 และต่ำกว่า
Wilfried Josset

1
ฉันพบว่าคำตอบนี้ดีที่สุดและให้ข้อมูลมากที่สุด แต่การทดสอบบน iPadOS13.5 การใช้งานที่แนะนำfunc willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)ไม่ได้ผล ฉันทำให้มันใช้งานได้โดยใช้func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator).
Andrej

12

Swift 4+: ฉันใช้สิ่งนี้เพื่อการออกแบบแป้นพิมพ์ที่นุ่มนวลและด้วยเหตุผลบางอย่างUIDevice.current.orientation.isLandscapeวิธีการก็บอกฉันว่าเป็นPortraitดังนั้นนี่คือสิ่งที่ฉันใช้แทน:

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
    }
}

ฉันมีปัญหาคล้ายกันในแอพ iMessage ของฉัน มันไม่แปลกจริงๆเหรอ? และแม้วิธีแก้ปัญหาของคุณจะทำงานตรงกันข้าม !! ¯_ (ツ) _ / ¯
Shyam

อย่าใช้ UIScreen.main.bounds เนื่องจากอาจทำให้คุณมีขอบเขตของหน้าจอก่อนการเปลี่ยนแปลง (โปรดคำนึงถึง 'Will' ในวิธีการนี้) โดยเฉพาะอย่างยิ่งในการหมุนเร็วหลายครั้ง คุณควรใช้พารามิเตอร์ 'size' ...
Dan Bodnar

@ dan-bodnar คุณหมายถึงพารามิเตอร์ nativeBounds หรือไม่?
asetniop

3
@asetniop เลขที่ sizeพารามิเตอร์ที่ถูกฉีดใน viewWillTransitionToSize: withCoordinator: วิธีการที่คุณเขียนข้างต้น ซึ่งจะแสดงขนาดที่แน่นอนที่มุมมองของคุณจะมีหลังจากการเปลี่ยนแปลง
Dan Bodnar

นี่ไม่ใช่วิธีแก้ปัญหาที่ดีหากคุณใช้ตัวควบคุมมุมมองเด็กขนาดของคอนเทนเนอร์อาจเป็นสี่เหลี่ยมจัตุรัสเสมอโดยไม่คำนึงถึงการวางแนว
bojan

8

Swift 4.2, RxSwift

หากเราต้องการโหลด collectionView

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

Swift 4, RxSwift

หากเราต้องการโหลด collectionView

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

5

ผมเชื่อว่าคำตอบที่ถูกต้องเป็นจริงการรวมกันของทั้งสองวิธี: viewWIllTransition(toSize:)และ'sNotificationCenterUIDeviceOrientationDidChange

viewWillTransition(toSize:)แจ้งให้คุณทราบก่อนการเปลี่ยนแปลง

NotificationCenter UIDeviceOrientationDidChangeจะแจ้งให้ทราบหลังจากที่

คุณต้องระวังให้มาก ยกตัวอย่างเช่นในUISplitViewControllerเมื่อหมุนอุปกรณ์ลงในทิศทางบางอย่างDetailViewControllerได้รับการโผล่ออกUISplitViewController's อาร์เรย์และผลักลงบนของต้นแบบviewcontrollers UINavigationControllerหากคุณไปค้นหาตัวควบคุมมุมมองรายละเอียดก่อนที่การหมุนจะเสร็จสิ้นอาจไม่มีอยู่และขัดข้อง


5

หากคุณใช้ Swift เวอร์ชัน> = 3.0 มีการอัปเดตโค้ดบางอย่างที่คุณต้องใช้ตามที่คนอื่นพูดไปแล้ว อย่าลืมโทรหา super:

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

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

   // YOUR CODE OR FUNCTIONS CALL HERE

}

หากคุณคิดจะใช้ StackView สำหรับภาพของคุณโปรดทราบว่าคุณสามารถทำสิ่งต่อไปนี้:

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

}

หากคุณกำลังใช้ Interface Builder อย่าลืมเลือกคลาสที่กำหนดเองสำหรับอ็อบเจ็กต์ UIStackView นี้ในส่วน Identity Inspector ที่แผงด้านขวา จากนั้นสร้าง (ผ่านตัวสร้างส่วนต่อประสาน) การอ้างอิง IBOutlet ไปยังอินสแตนซ์ UIStackView ที่กำหนดเอง:

@IBOutlet weak var stackView: MyStackView!

รับแนวคิดและปรับให้เข้ากับความต้องการของคุณ หวังว่านี่จะช่วยคุณได้!


จุดดีเกี่ยวกับการโทรหาซุปเปอร์ การเข้ารหัสที่ไม่สามารถเรียกใช้ super method ในฟังก์ชันที่ถูกลบล้างและสามารถสร้างพฤติกรรมที่ไม่สามารถคาดเดาได้ในแอป และอาจเป็นจุดบกพร่องที่ยากต่อการติดตาม!
Adam Freeman

คุณรู้หรือไม่ว่าเหตุใดฉันจึงไม่สามารถแทนที่ viewWillTransition เมื่อคลาสย่อย UIView ได้
Xcoder

@Xcoder เหตุผล (โดยปกติ) ของออบเจ็กต์ที่ไม่ใช้การแทนที่อยู่ในอินสแตนซ์รันไทม์ไม่ได้สร้างขึ้นด้วยคลาสที่กำหนดเอง แต่ใช้ค่าดีฟอลต์แทน หากคุณกำลังใช้ Interface Builder อย่าลืมเลือกคลาสแบบกำหนดเองสำหรับอ็อบเจ็กต์ UIStackView นี้ในส่วน Identity Inspector ที่แผงด้านขวา
WeiseRatel

5

สวิฟต์ 4

ฉันมีปัญหาเล็กน้อยเมื่ออัปเดตมุมมอง ViewControllers โดยใช้UIDevice.current.orientationเช่นการอัปเดตข้อ จำกัด ของเซลล์มุมมองตารางระหว่างการหมุนหรือภาพเคลื่อนไหวของมุมมองย่อย

แทนที่จะใช้วิธีการข้างต้นฉันกำลังเปรียบเทียบขนาดการเปลี่ยนแปลงกับขนาดมุมมองตัวควบคุมมุมมอง ดูเหมือนจะเป็นวิธีที่เหมาะสมเนื่องจากมีการเข้าถึงทั้งสองอย่างในรหัสนี้:

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()
        }
    }


}

เอาต์พุตบน 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)

ฉันควรเปิดการสนับสนุนการวางแนวจากระดับโครงการหรือไม่
Ericpoon

3

การมีส่วนร่วมทั้งหมดก่อนหน้านี้ทำได้ดี แต่หมายเหตุเล็กน้อย:

a) หากตั้งค่าการวางแนวใน plist เฉพาะแนวตั้งหรือตัวอย่างคุณจะไม่ได้รับแจ้งผ่าน viewWillTransition

b) หากเราจำเป็นต้องทราบว่าผู้ใช้หมุนอุปกรณ์หรือไม่ (เช่นเกมหรือสิ่งที่คล้ายกัน .. ) เราสามารถใช้ได้เฉพาะ:

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

ทดสอบบน Xcode8, iOS11


2

คุณสามารถใช้viewWillTransition(to:with:)และแตะanimate(alongsideTransition:completion:)เพื่อดูการวางแนวอินเทอร์เฟซหลังจากการเปลี่ยนแปลงเสร็จสมบูรณ์ คุณต้องกำหนดและใช้โปรโตคอลที่คล้ายกันนี้เพื่อที่จะเข้าร่วมกิจกรรม โปรดทราบว่ารหัสนี้ใช้สำหรับเกม SpriteKit และการใช้งานเฉพาะของคุณอาจแตกต่างออกไป

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)
    }

คุณสามารถตั้งค่าเบรกพอยต์ในฟังก์ชันเหล่านี้และสังเกตว่าinterfaceOrientationChanged(to orientation: UIInterfaceOrientation)จะถูกเรียกตามviewWillTransition(to size: CGSize)ด้วยการวางแนวที่อัปเดตเสมอ


1

เพื่อให้ได้การวางแนวที่ถูกต้องในการเริ่มต้นแอปคุณต้องตรวจสอบในviewDidLayoutSubviews()คุณจะต้องตรวจสอบในวิธีอื่น ๆ ที่อธิบายไว้ที่นี่ใช้ไม่ได้

นี่คือตัวอย่างวิธีการ:

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()
}

นี้มักจะทำงานบนแอปเริ่มต้นครั้งแรกและถ้าหมุนในขณะที่ app ทำงาน


0

อีกวิธีหนึ่งในการตรวจจับการวางแนวอุปกรณ์คือการใช้ฟังก์ชัน traitCollectionDidChange (_ :) ระบบเรียกวิธีนี้เมื่อสภาพแวดล้อมอินเทอร์เฟซ iOS เปลี่ยนไป

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

นอกจากนี้คุณสามารถใช้ฟังก์ชัน willTransition (to: with :) (ซึ่งเรียกก่อน traitCollectionDidChange (_ :)) เพื่อรับข้อมูลก่อนนำการวางแนวไปใช้

 override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    //...
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.