먹고 기도하고 코딩하라

[iOS] Dynamic Type을 지원하되, weight는 커스텀하기 본문

앱/Swift

[iOS] Dynamic Type을 지원하되, weight는 커스텀하기

사과먹는사람 2022. 10. 13. 23:54
728x90
728x90

 

요약

UIFont에 커스텀 메소드를 추가해주면 됩니다. 빨간 글씨 나올 때까지 쭉 내려보세요.

 

 

본문

애플 Human Interface Guidelines의 Typography 항목을 보면 Dynamic Type에 대한 내용이 있다. Dynamic Type은 Best practices 첫 번째에 등장할 정도로 중요한 내용이다.

 

Strive to maintain a minimum font size that most people can read easily.

 

여기서 강조하는 것은, 최소 폰트 크기는 대부분의 사람이 쉽게 읽을 수 있을 정도를 유지하라는 것이다. 

Dynamic type의 정의 자체는 iOS, iPadOS, tvOS, watchOS에서 스크린에 나타나는 텍스트 크기를 설정하게 하는 기능인데, 이걸 지원하라는 건 앱의 텍스트 크기를 고정하지 않고 사용자의 서체 크기 설정에 따라 사이즈를 조정하라는 것이다. 

 

Specifications까지 내려보면 Dynamic Type의 스타일과 그 스타일이 크기 설정에 따라 몇 pt로 보이는지, 굵기는 어떤지 보여주는 표를 볼 수 있다. 아래 예시에서는 폰트 설정을 Large로 해둔 사람에게 각 스타일이 어떻게 보일지 보여주고 있다.

좋은 앱이라면 작고 오밀조밀하게 디자인하는 게 보기에는 예쁠 수 있지만, 저시력자나 중노년층을 위해 큰 글씨도 지원하는 것이 좋을 것 같다. 접근성은 애플에서 정말 중요하게 생각하는 가치 중 하나이니 말이다.

 

 

코드

코드를 통해 알아보자.

import UIKit
import SnapKit

class ViewController: UIViewController {
    private let fixedLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 20, weight: .black)
        label.text = "고정 크기 레이블"
        label.numberOfLines = 0
        return label
    }()
    
    private let dynamicLabel: UILabel = {
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .title3)
        // 이게 없으면 앱 사용 도중에 폰트 크기를 바꿨을 때 바로 폰트 크기 변경이 안 된다
        label.adjustsFontForContentSizeCategory = true	         
        label.text = "기본 Dynamic Type 레이블"
        label.numberOfLines = 0
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        [fixedLabel, dynamicLabel].forEach {
            view.addSubview($0)
            $0.snp.makeConstraints {
                $0.leading.trailing.equalToSuperview().inset(24)
            }
        }
        
        fixedLabel.snp.makeConstraints {
            $0.top.equalTo(view.safeAreaLayoutGuide).inset(24)
        }
        
        dynamicLabel.snp.makeConstraints {
            $0.top.equalTo(fixedLabel.snp.bottom).offset(30)
        }
    }
}

아이폰 13 미니 시뮬레이터로 테스트한 결과이다. .title3은 regular이기 때문에 글씨체가 굵게 나오지 않은 반면, "고정 크기 레이블"은 black이라서 굵게 나온 모습이다.

여기서 Xcode > Open Developer Tool > Accessibility Inspector를 선택하면 접근성에 대한 여러 가지 테스트를 할 수 있는 인스펙터 창이 나온다. Simulator를 선택하고, Dynamic Type을 늘려보자.

폰트를 쫙 올려줬더니 고정 크기 레이블은 그대로지만 Dynamic Type 스타일을 적용한 레이블의 크기는 크게 변했다.

 

이제 여기서 "기본 Dynamic Type 레이블"을 아주 얇게 표시하고 싶다면 어떻게 해야 할까?

여기서부터는 UIFont 클래스 자체를 커스텀해야 한다. 새로운 swift 파일을 만들고, 다음과 같이 작성하자.

import UIKit

extension UIFont {
    static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont {
        let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
        let font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight)
        let metrics = UIFontMetrics(forTextStyle: style)
        return metrics.scaledFont(for: font)
    }
}

.preferredFont라는 새로운 메소드를 만들었다. 스타일은 기존에 쓰는 .title3, .headline 같은 TextStyle을 받고, weight는 Weight를 따로 받는 메소드이다.

 

UIFontDescriptor는 폰트를 기술하는 속성들의 컬렉션이다. UIFont 객체를 생성하거나 수정하는 데 사용된다. preferredFontDescriptor(withTextStyle: UIFont.TextStyle)은 특정 텍스트 스타일과 사용자가 선택한 콘텐츠 사이즈를 포함한 font descriptor를 반환하는 메소드이다. 여기서 우리가 만들 커스텀 폰트의 description을 만든다고 생각하면 된다.

 

그 다음에는 systemFont로 font를 만드는데, 이 때 사이즈는 description의 pointSize(CGFloat 타입으로, font descriptor의 포인트 사이즈를 나타냄), weight는 지정한 weight로 font를 만들게 된다. systemFont 메소드 사용법은 일반적으로 systemFont를 사용해서 텍스트를 표시하는 것과 같으므로 설명을 생략한다.

 

그 다음, UIFontMetrics로 스타일에 대한 fontMetrics를 만든다. UIFontMetrics는 Dynamic Type을 지원하는 커스텀 폰트를 제공하는 유틸리티 객체인데, 그냥 크기를 조정할 수 있는 커스텀 폰트라고 생각하면 된다. 커스텀 폰트라고 해서 꼭 다른 서체를 써야 할 필요는 없고 그냥 SF Pro 그대로 사용 가능하다. 

 

마지막으로, 이 FontMetrics의 scaledFont 메소드를 사용해 UIFont를 반환한다. scaledFont(for: UIFont)는 현재 주어진 font metrics에 적응된 버전의 특정 폰트를 돌려준다...고 직역할 수 있는데, 풀어쓰자면 아까 정한 desc의 pointSize만큼 scaling한 폰트를 돌려주는 것이다.

 

 

자! 소기의 목적을 달성했으므로 새로운 레이블을 만들어서 테스트해보겠다. 뷰 컨트롤러에 다음 코드를 추가하고, 뷰에 addSubView 해준 다음 constraints도 적절히 맞춰준다. (전체 코드는 맨 아래 repo에서 확인할 수 있다)

    private let myLabel: UILabel = {
        let label = UILabel()
        label.font = .preferredFont(for: .title3, weight: .ultraLight)
        label.adjustsFontForContentSizeCategory = true
        label.text = "커스텀 Dynamic Type 레이블"
        label.numberOfLines = 0
        return label
    }()

.title3이지만 regular가 아니라 ultraLight weight가 잘 적용한 모습이다.

 

 

Tip

접근성 폰트 크기로 넘어가게 되면 글씨가 너무 커져서 레이아웃이 깨지는 현상이 발생할 수 있다. 이에 최대 폰트 크기를 설정할 수도 있다. UIFont 커스텀했던 파일로 넘어가서 조금 수정해주면 된다. 여기서는 폰트 최대 크기를 36으로 제한해보겠다.

extension UIFont {
    static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont {
        let metrics = UIFontMetrics(forTextStyle: style)
        let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
        let font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight)
        return metrics.scaledFont(for: font, maximumPointSize: 36)
    }
}

폰트 설정을 최대한으로 늘려도 커스텀 레이블은 36으로 고정되어 더 커지지 않는 모습이다.

물론 레이아웃을 새로 짤 수 있다면 그렇게 하는 게 제일 좋겠지만, 그럴 여건이 안 된다거나 당장 할 수 없다면 이런 방법을 쓰는 것도 좋겠다.

 

전체 코드와 프로젝트는 아래 링크에서 확인할 수 있다.

 

GitHub - dev-dain/ios-blog: 블로그에 올릴 iOS 포스트 예제 프로젝트를 올리는 저장소입니다.

블로그에 올릴 iOS 포스트 예제 프로젝트를 올리는 저장소입니다. Contribute to dev-dain/ios-blog development by creating an account on GitHub.

github.com

 

 

728x90
반응형
Comments