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

2
이것을 확인하십시오 stackoverflow.com/questions/25666269/…
DSAjit

가로 / 세로가 올바르게 인쇄됩니까?
Daniel

네, 그것은 @simpleBob 제대로 인쇄
라구 람

장치 방향 대신 인터페이스 방향을 사용해야합니다. => 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를 넣으십시오. 모든 배경을 덮도록 ViewController 메인 뷰에 top, bottom, leading, trailing에 대한 imageView에 제약을줍니다.
Rutvik Kanbargi

4
당신의 재정의 메서드 내에서 super.viewWillTransitionToSize를 호출하는 것을 잊지 마세요
브라이언 Sachetta에게

내가 viewWillTransition서브 클래 싱 할 때 재정의 할 수없는 이유를 아십니까 UIView?
Xcoder

2
다른 방향 유형이 있습니다 : .isFlat. 잡으려 portrait는 경우 else 절을 else if UIDevice.current.orientation.isPortrait. 참고 : .isFlat세로 및 가로 모두에서 발생할 수 있으며 {} 만 있으면 항상 기본값이됩니다 (평평한 가로 모드 인 경우에도).
PMT

78

사용 NotificationCenterUIDevice 의를beginGeneratingDeviceOrientationNotifications

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
@Michael 변경이 완료 되면를 viewWillTransition:사용하는 동안 변경이 발생할 것으로 예상 되기 때문 UIDeviceOrientationDidChange입니다.
Lyndsey Scott

1
@LyndseyScott 잠재적으로 위험한 NSNotificationCenter를 사용하지 않고도 설명 된 동작을 달성 할 수 있다고 여전히 믿습니다. 다음 답변을 검토하고 귀하의 생각을 알려주십시오. stackoverflow.com/a/26944087/1306884
Michael

4
이 답변에서 흥미로운 점은 뷰 컨트롤러가 shouldAutorotate로 설정되어 있어도 현재 방향을 제공한다는 것 false입니다. 이것은 카메라 앱의 메인 화면과 같은 화면에서 편리합니다. 방향은 잠겨 있지만 버튼은 회전 할 수 있습니다.
yoninja

1
iOS 9부터는 명시 적으로 deinit를 호출 할 필요조차 없습니다. 당신은 여기에 대한 자세한 내용을보실 수 있습니다 : useyourloaf.com/blog/...
제임스 요르단 테일러

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
UIView를 서브 클래 싱 할 때 viewWillTransition을 재정의 할 수없는 이유를 알고 있습니까?
Xcoder

컨트롤러에서 뷰를 참조하려고하면 충돌이 발생합니다.
James Jordan Taylor

@JamesJordanTaylor가 왜 충돌하는지 설명해 주시겠습니까?
orium

내가 주석을 달았을 때 6 개월 전이지만 Xcode가 시도했을 때 준 이유는 뷰 자체가 아직 인스턴스화되지 않았기 때문에 viewController 만했기 때문에 nil 포인터 예외라고 생각합니다. 그래도 잘못 기억할 수 있습니다.
James Jordan Taylor

@Xcoder 이것은 UIView가 아닌 ​​UIViewController의 함수입니다. 그렇기 때문에 재정의 할 수 없습니다.
LightningStryk

24

⚠️ 장치 방향! = 인터페이스 방향 ⚠️

Swift 5. * iOS14 이하

정말 다음과 같은 차이를 만들어야합니다.

  • 장치 방향 => 물리적 장치의 방향을 나타냅니다.
  • 인터페이스 방향 => 화면에 표시된 인터페이스의 방향을 나타냅니다.

다음과 같이 두 값이 일치하지 않는 많은 시나리오가 있습니다.

  • 화면 방향을 잠글 때
  • 장치가 평평 할 때

대부분의 경우 인터페이스 방향을 사용하고 싶고 창을 통해 가져올 수 있습니다.

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

이 메서드를 구현하면 인터페이스 방향 변경에 반응 할 수 있습니다. 그러나 앱을 열 때 트리거되지 않으므로에서 인터페이스를 수동으로 업데이트해야합니다 viewWillAppear().

장치 방향과 인터페이스 방향의 차이를 강조하는 샘플 프로젝트를 만들었습니다. 또한 UI를 업데이트하기로 결정한 수명주기 단계에 따라 다른 동작을 이해하는 데 도움이됩니다.

다음 저장소를 자유롭게 복제하고 실행하십시오 : https://github.com/wjosset/ReactToOrientation


iOS 13 이전에는 어떻게해야합니까?
Daniel Springer

1
@DanielSpringer는 귀하의 의견 주셔서 감사합니다, 나는 아이폰 OS (12) 아래에 지원하는 포스트 편집 한
윌 프리드 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'에 유의), 특히 여러 번의 빠른 회전에서. 당신합니다 ... '크기'매개 변수를 사용해야합니다
댄 보드 나르

@ dan-bodnar nativeBounds 매개 변수를 의미합니까?
asetniop

3
@asetniop, 아니. sizeviewWillTransitionToSize에 주입 매개 변수 : withCoordinator : 당신이 위의 작성 방법. 이것은 전환 후 뷰가 갖게 될 정확한 크기를 반영합니다.
댄 보드 나르

자식 뷰 컨트롤러를 사용하는 경우 이는 좋은 해결책이 아닙니다. 컨테이너의 크기는 방향에 관계없이 항상 정사각형 일 수 있습니다.
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)

스위프트 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:)하고 NotificationCenterUIDeviceOrientationDidChange.

viewWillTransition(toSize:)전환 전에 알려줍니다 .

NotificationCenter UIDeviceOrientationDidChange후에 알려줍니다 .

매우 조심해야합니다. 예를 들어, UISplitViewController장치가 특정 방향으로 회전하면 의 배열에서 DetailViewController튀어 나와 마스터의 . 회전이 완료되기 전에 디테일 뷰 컨트롤러를 검색하면 존재하지 않고 충돌 할 수 있습니다.UISplitViewControllerviewcontrollersUINavigationController


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를 사용하는 경우 오른쪽 패널의 Identity Inspector 섹션에서이 UIStackView 객체에 대한 사용자 정의 클래스를 선택하는 것을 잊지 마십시오. 그런 다음 (Interface Builder를 통해) 사용자 정의 UIStackView 인스턴스에 대한 IBOutlet 참조를 만듭니다.

@IBOutlet weak var stackView: MyStackView!

아이디어를 취하고 필요에 맞게 조정하십시오. 이것이 당신을 도울 수 있기를 바랍니다!


슈퍼에 전화하는 것에 대한 좋은 점. 재정의 된 함수에서 super 메서드를 호출하지 않는 것은 정말 엉성한 코딩이며 앱에서 예측할 수없는 동작을 생성 할 수 있습니다. 그리고 추적하기 정말 어려운 잠재적 버그!
Adam Freeman

UIView를 서브 클래 싱 할 때 viewWillTransition을 재정의 할 수없는 이유를 알고 있습니까?
Xcoder

@Xcoder 객체가 오버라이드를 적용하지 않는 이유는 (보통) 런타임 인스턴스가 커스텀 클래스로 생성되지 않고 대신 기본값으로 생성 되었기 때문입니다. Interface Builder를 사용하는 경우 오른쪽 패널의 Identity Inspector 섹션에서이 UIStackView 객체에 대한 사용자 정의 클래스를 선택해야합니다.
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()
}

이것은 앱이 처음 시작할 때와 앱이 실행되는 동안 회전 하는 경우 항상 작동 합니다 .


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.