가끔 Dynamic Type을 적용할 경우 레이아웃이 깨지는 경우가 발생할 수 있습니다.
글자의 크기가 너무 커져서 글자가 잘리거나, 객체들이 서로의 영역을 침범하는 현상이 생기기도 하죠.
오늘은 Dynamic Type으로 인해 객체의 크기가 커질 때 레이아웃을 어떻게 변경할 수 있으며, 어떤 형태로 변형해야 하는지에 대해 설명드리겠습니다.
위의 사례에서 확인할 수 있듯, SwiftUI와 UIKit의 기본 개행 제한은 다릅니다.
SwiftUI의 Text는 기본적으로 무제한으로 개행이 가능하지만, UIKit의 UILabel은 기본적으로 한 줄로 제한됩니다.
이때 각각 lineLimit(_:)
와 numberOfLines
를 설정하면 개행 수를 변경할 수 있습니다.
Text | UILabel | |
---|---|---|
Framework | SwiftUI | UIKit |
방식 | view modifier | property |
문법 | lineLimit | numberOfLines |
default | nil (무제한) | 1 |
무제한으로 만드는 법 | nil 대입 | 0 대입 |
텍스트의 줄 바꿈 수가 제한된 경우, 표시할 수 없는 글자는 ...으로 표시됩니다.
이때 어떤 글자를 우선적으로 보여줄지 선택할 수 있습니다.
SwiftUI에서는 truncationMode(_:)
, UIKit에서는 lineBreakMode
를 설정하여 이를 조정할 수 있습니다.
SwiftUI에서는 Dynamic Type에 따라 이미지 크기가 자동으로 변경됩니다.
하지만 UIKit에서는 별도로 처리하여 Dynamic Type에 맞춰 이미지 크기를 조정할 수 있습니다.
let configuration = UIImage.SymbolConfiguration(textStyle: .body)
let image = UIImage(systemName: "star", withConfiguration: configuration)
글자와 UI 요소가 함께 있을 때, 글자 크기가 변화함에 따라 둘 중 하나가 전부 표시되지 않을 수 있습니다.
이런 경우, Dynamic Type의 크기에 따라 레이아웃 형태를 변경하여 표현할 수 있습니다.
대표적인 방법으로는 Dynamic Type이 특정 크기 이상일 때 UIStackView의 axis를 변경하는 방법이 있습니다.
stackView.axis = traitCollection.preferredContentSizeCategory < .accessibilityMedium ? .horizontal : .vertical
SwiftUI에서는 if 구문으로 분기하거나 AnyLayout을 사용하여 이를 구현할 수 있습니다.
var layout: AnyLayout {
sizeCategory < .accessibilityMedium ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout())
}
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: .zero) {
Color.clear.overlay {
SwiftUIView()
}
Divider()
Color.clear.overlay {
ViewControllerRepresentable()
}
}
}
}
struct SwiftUIView: View {
@Environment(\.sizeCategory) var sizeCategory
var body: some View {
layout {
Text("테스트용 텍스트")
Image(systemName: "star")
}
.padding()
.border(.black)
}
var layout: AnyLayout {
sizeCategory < .accessibilityMedium ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout())
}
}
final class ViewController: UIViewController {
private var containerView: UIView = .init()
private var stackView: UIStackView = .init()
private var label: UILabel = .init()
private var switchButton: UISwitch = .init()
override func viewDidLoad() {
view.backgroundColor = .systemGray4
view.addSubview(containerView)
containerView.addSubview(stackView)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.backgroundColor = .white
containerView.layer.cornerRadius = 20
containerView.layer.borderWidth = 1
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
stackView.addArrangedSubview(switchButton)
stackView.alignment = .center
label.text = "테스트용 텍스트"
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
label.numberOfLines = 0
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
switchButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
NSLayoutConstraint.activate([
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
containerView.widthAnchor.constraint(equalToConstant: 350),
stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 30),
stackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 15),
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -30),
stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -15)
])
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
stackView.axis = traitCollection.preferredContentSizeCategory < .accessibilityMedium ? .horizontal : .vertical
}
}
struct ViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController { ViewController()}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
}
#Preview {
ContentView()
}
Dynamic Type의 크기가 커져도 콘텐츠를 최대한 모두 보이게 하는 방법이 있습니다.
그 방법은 가능한 많은 화면을 ScrollView로 감싸는 것입니다.
이렇게 하면 글자와 이미지의 크기가 커져도 사용자는 스크롤을 통해 전체 내용을 모두 확인할 수 있습니다.
하지만 이 방법은 상황에 따라 불가능할 수 있으며, 중첩된 스크롤로 인해 UX가 나빠질 수 있음을 고려해야 합니다.
Dynamic Type을 지원하는 앱에서 텍스트 크기 변화에 따른 레이아웃 조정은 중요한 부분입니다.
lineLimit
, numberOfLines
, truncationMode(_:)
등을 활용해 텍스트의 개행을 제어하고,
레이아웃을 가로와 세로로 동적으로 변경하는 방법을 고려할 수 있습니다.
또한, 스크롤을 사용해 콘텐츠를 모두 표시하는 방법도 유용하지만, 중첩된 스크롤로 인한 UX 문제를 주의해야 합니다.
Dynamic Type을 적절히 활용하면 다양한 화면 크기와 글자 크기에 맞춰 유연한 디자인을 제공하고, 접근성을 고려한 사용자 경험을 향상시킬 수 있습니다.