일본어 공부를 하다보면 짧은 단어를 학습할 때도 있고 긴 단어를 학습할 때도 있습니다. 짧은 단어를 학습할 때는 아이폰을 세로로 그리고 긴 문장을 학습할 때는 아이폰을 가로로 놓고 학습을 하면 좋을 것 같습니다.
하지만 이런 기능을 구현하기 위해서는 화면이 회전할 때마다, 즉 orientation이 바뀔 때마다 화면의 가로 길이를 계산해서 단어 Cell의 크기를 조정해주어야 합니다. 만약에 이렇게 하지 않는다면 세로에서 가로로 회전한다고 해도 Cell 크기는 그대로 있기 때문에 긴 문장을 보기 불편할 것입니다.
상수들을 Constant라는 구조체 안에 선언해두겠습니다.
먼저 iOS에서 화면의 크기를 가져오는 방법입니다. UIScreen 객체가 바로 하드웨어의 디스플레이를 정의해놓은 객체입니다. 그 객체의 bounds.width를 읽어오면 너비 bounds.height를 읽어오면 높이를 읽어오는 방식입니다.
다음은 당장은 필요 없지만 MacOS에서 화면의 크기를 가져오는 방법입니다. iOS의 경우 대부분의 앱이 전체 화면을 사용하지만 MacOS의 경우 앱이 전체 화면을 사용하는 경우도 있지만 화면의 일부만 사용하는 경우도 많습니다. 따라서 하드웨어의 디스플레이의 크기를 읽어오지 않고 NSApplication (앱 자체를 정의한 객체)가 사용하고 있는 화면(NSWindows) 중에서 가장 앞 화면의 크기를 가지고 옵니다. (물론 우리의 앱은 당장에는 window를 하나만 사용하므로 windows에는 하나의 객체 밖에 없을 것입니다.)
import CoreGraphics
#if os(iOS)
import UIKit
#elseif os(macOS)
import AppKit
#endif
// TODO: multiplatform 에서 화면 크기 얻는 방법 정리하기
// TODO: 윈도우 크기 바뀌면 이 값도 따라서 바뀌어야 함
enum Constants {
enum Size {
static var deviceWidth: CGFloat {
#if os(iOS)
UIScreen.main.bounds.width
#elseif os(macOS)
NSApplication.shared.windows.first?.frame.width ?? 300
#endif
}
static var deviceHeight: CGFloat {
#if os(iOS)
UIScreen.main.bounds.height
#elseif os(macOS)
NSApplication.shared.windows.first?.frame.height ?? 300
#endif
}
}
}
위에서 구한 화면 크기를 활용해서 Cell의 너비를 정의하겠습니다.
@State private var deviceWidth: CGFloat
WordCell(wordBook: viewModel.wordBook, word: $viewModel.words[index])
.frame(width: deviceWidth * 0.9, height: viewModel.words[index].hasImage ? 200 : 100)
OS가 orientation을 바뀌면 특정 코드가 실행되도록 해야합니다. 여기서는 NotificationCenter를 활용하도록 하겠습니다. NotificationCenter는 어떤 이벤트가 발생했을 때 등록된 Observer들에게 해당 이벤트의 발생을 broadcast해주는 객체입니다. 우리의 경우에는 orientation이 바뀌는 이벤트를 인지해야하므로 UIDevice.orientationDidChangeNotification 이벤트가 발생하는 경우 broadcast를 받겠다고 등록해둡니다.
예전에는 addObserver 메소드를 활용해서 특정 seletor를 실행시키는 방법을 주로 사용했지만 우리는 SwiftUI를 사용하는만큼 .onReceive()를 사용해보겠습니다.
.onReceive() 메소드는 publisher를 인자로 받습니다. publisher가 발행을 했을 때 정의된 코드를 실행해줍니다. 그래서 NotificationCenter를 그대로 전달하면 안되고 publisher로 바꾸어서 전달해줍니다.
실행할 코드는 아주 간단합니다. 화면의 크기를 다시 읽어오는 코드입니다. 이렇게 하면 oritentaion이 바뀌었을 때 deviceWidth를 다시 읽어와서 @State 속성의 변수에 다시 할당을 하고 @State 변수가 바뀌었으므로 Cell도 다시 랜더링되면서 Cell의 너비가 바뀌게 됩니다.
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
resetDeviceWidth()
}
@State private var deviceWidth: CGFloat
private func resetDeviceWidth() {
self.deviceWidth = Constants.Size.deviceWidth
}