호댕의 iOS 개발

[TWL] 21.12.13 ~ 21.12.17 (Accessibility, Deployment target) 본문

Software Engineering/TIL

[TWL] 21.12.13 ~ 21.12.17 (Accessibility, Deployment target)

호르댕댕댕 2021. 12. 18. 15:23

저번 주와 이번 주 프로젝트를 진행하고 활동학습을 하며 정말 많은 내용을 배울 수 있었다. 

일단 Accessibility와 동시성 프로그래밍에 대해 배웠고, 이외에도 팀원들과 함께 프로젝트를 진행하며 평소 코드를 작성하며 생각하지 못했던 부분까지 고민해볼 수 있었다. 

 

그럼 이번 주는 뭘 배웠는지 정리해보도록 하자. 

 

Accessibility


새롭게 알게 된 내용

  • VoiceOver로 나오는 언어는 맥북 언어에 따라 달라진다. (왜 시뮬레이터 언어를 따라가지 않는 것일까?)
  • 이미지의 경우 isAccessbilityElement를 true로 설정해줘야 accessibility에 접근할 수 있다.
  • Hit area is too small로 버튼이 너무 작다는 warning이 나오면 button에 Image Inset을 조정하면 된다.

WWDC 세션 (Accessibility Inspector, Writing Great Accessibility Labels)

활동학습을 듣고 다시 들으니 확실히 이해가 더 잘 된다!

Accessibility Inspector Accessibility Inspector를 통해 Accessibility 이슈를 찾고 검사하고 고칠 수 있다. xcode → open developer tool → Accessibility Inspector

Accessibility Inspector 탭의 종류

  • Inspector
  • Audit -> Run audit을 하면 Accessibility를 해치는 요인을 찾을 수 있다.
  • Setting

Audit에서 help 버튼을 누르면 어떻게 수정하면 좋을 지에 대해 보여준다.

Voice Over를 사용할 경우 어떻게 사용자에게 들릴지도 고려를 해야 함. (만약 버튼이 무엇을 하는지 정확히 알려주지 않는다면 사용자 경험을 해치게 된다)

그래서 이 때 사용할 수 있는 것이 accessibilityLabel이다.

Accessibility 이슈를 수정하면 앱의 사용성을 올릴 수 있다!! 잘 고려해보자!! (국내에선 법으로 Accessibility에 대응할 수 있도록 규제를 하고 있다. 따라서 현업에서도 잘 준수해야 한다)

Writing Great Accessibility Labels Accessibility는 애플에서 디자인한 하드웨어와 소프트웨어에 모두 들어가있다. Accessibility는 인권이자 애플의 중요 가치 중 하나이다.

그렇다면 Accessibility Label은 뭘까?

바로 localized 된 String으로 Accessibility 요소를 명확하게 인식할 수 있게 해주는 것. 이 때 사람이 읽을 수 있고, 사람이 이해할 수 있는 label로 앱의 문맥과 의미를 전달해야 한다.

⚠️ Accessibility Label을 정해줄 때 주의할 점!

  1. 요소의 타입을 Label에 포함하지 마라. (ex: button.accessibilityLabel = "Add button" (X))
  2. 충분한 설명을 해줘라. (ex: 'Plus' 버튼이 많다면 어떤 것을 추가하는 것인지 제공해라)
  3. 장황한 설명은 피해라. (ex: Music 앱이라면 play / previous / next 뒤에 song이 없어도 충분히 알 수 있다)
  4. 하지만 적절한 상황에선 장황하게 설명하는 것이 앱의 사용성을 높일 수 있다.
  • 즉 Accessibility Label을 작성할 때에는 문맥과 상황에 맞춰서 충분한 설명은 하되, 너무 장황할 필요는 없다.

Deployment target을 낮췄을 때 대처

contentConfigure의 경우 iOS 14부터 새롭게 생긴 것으로 Deployment target을 낮췄을 때에는 컴파일 오류가 발생하게 된다.

'' is only available in iOS 14.0 or newer

따라서 OS version에 따라 분기를 해서 사용을 해줘야 한다.

if #available(iOS 14.0, *) {
    print("ios 14이상 메서드 불림")
    var content = cell.defaultContentConfiguration()

    content.image = UIImage(named: expositionItem.imageName)
    content.text = expositionItem.name
    content.secondaryText = expositionItem.shortDescription

    cell.contentConfiguration = content

    return cell
} else {
    print("ios 13이하 메서드 불림")
    cell.textLabel?.text = expositionItem.name

    return cell
}

분기를 할 때에는 #available(iOS 14.0, *)을 조건으로 사용해서 분기를 해주었다.

  • systemVersion: 버전이 현재 어떤지 알 수 있다. (ex: 14.0)
  • systemName: 현재 OS가 어떤 OS인지 알 수 있다. (ex: watchOS, iOS 등)

simulator의 버전을 낮추는 방법은 블로그에 작성하여 링크로 대체한다. (링크: https://ho8487.tistory.com/29)

 

adjustsFontForContentSizeCategory

디바이스의 Content size category가 변경될 때 Font를 자동으로 업데이트하는 지에 대해 나타내는 Boolean 값이다.

  • 반드시 preferredFont(forTextStyle:) 혹은 preferredFont(forTextStyle:compatibleWith:) 메서드를 유효한 Text style과 사용해야 한다.
extension UILabel {
    func setDynamicType(textStyle: UIFont.TextStyle) {
        self.adjustsFontForContentSizeCategory = true
        self.font = .preferredFont(forTextStyle: textStyle)
    }
}

프로젝트에선 위 코드처럼 UILabel의 extension을 활용해 setDynamicType이라는 메서드를 생성함.

#available vs @available

  • #available : 특정 코드를 특정 플랫폼이나 버전에서 실행할 때 사용함. (if문이나 guard문에서 사용함)
  • @available : class나 메서드 등의 앞에서 사용해 OS 및 버전을 제한하며 컴파일 타임에 오류나 경고를 발생시킨다.
if #available(iOS 14.0, *) {
    descriptionLabel?.lineBreakStrategy = .hangulWordPriority
} else {
    descriptionLabel?.lineBreakMode = .byWordWrapping
}

아래 코드처럼 사용할 수도 있다. #if os(systemName) → 특정 OS만 사용할 수 있도록 #if swift(≥ ) → 스위프트 버전에 따라 분기

enumeration associated Value

케이스의 이름 뒤에 선언하며, 선언 시점이 아닌 새로운 열거형을 생성할 때 값을 저장하게 된다. 이 때 튜플을 활용해 하나의 케이스에 여러 값을 저장할 수 있다.

enum TypeOfViewController {
    case expositionItemTable
    case expositionItem(data: ExpositionItem)
}

이렇게 사용해서 expositionItem이 매개변수처럼 데이터를 받을 수 있도록 함.

Interface Orientation

이 사진을 보면 Device Orientation에 Upside Down이라는 항목을 볼 수 있다. 하지만 이걸 체크해줘도 Info.plist에는 변경이 되지만 실제로 화면을 뒤집었을 때에는 화면이 Portrait으로 나오지 않고, Landscape로 나왔다.

All iPadOS devices support the portraitUpsideDown orientation. It’s best practice to enable it for the iPad idiom. iOS devices without a Home button, such as iPhone 12, don’t support this orientation. You should disable it entirely for the iPhone idiom.

이는 공식 문서에서 답을 찾을 수 있었다. supportedInterfaceOrientations 공식문서를 보면 Note 부분에 홈버튼이 없는 iOS 디바이스의 경우 PortraitUpsideDown을 지원하지 않는다는 것을 볼 수 있다.

따라서 홈버튼이 있는 iPhone 8로 테스트를 했을 때에는 정상적으로 뒤집었을 때 화면이 잘 보였다.

lineBreakStrategy = .hangulWordPriority VS lineBreakMode = .byWordWrapping

왼쪽 시뮬레이터가

.hangulWordPriority

, 오른쪽 시뮬레이터가

.byWordWrapping

으로 설정했을 때이다.

.hangulWordPriority

는 한글 단어를 잘 인식해서 끊어주는 반면

.byWordWrapping

은 한 음절을 기준으로 줄바꿈을 해주었다.

따라서 iOS 14에서 .hangulWordPriority가 추가된 것 같다.

 

 

객체 간 메시지를 보내는 방법에는 어떤 방법이 있을까?

가장 기본적으로 객체 간에 메시지를 보내는 방법에는 메서드를 호출하는 방법이 있다. 메서드를 호출하여 특정 타입이 일을 하도록 일을 시키는 것이다. 이외에도 연산 프로퍼티를 활용해서도 객체 간에 메시지를 호출할 수 있다.

다형성을 적용하기 위한 방법으로 어떤 방법이 존재할까?

  1. override의 경우 기존 superclass에서 선언한 메서드의 동작을 추가하거나 수정할 수 있기 때문에 다형성을 갖출 수 있는 방법 중 하나이다.
  2. overload의 경우 메서드의 매개변수를 다르게 하여 다양한 타입에 맞게 응답할 수 있도록 하는 것이다. 즉 하나의 메서드를 다양한 타입에 맞게 적용할 수 있기 때문에 다형성을 갖출 수 있다.
  3. protocol을 채택하면 protocol에서 정의한 메서드를 사용해야 하지만, 메서드 내부 구현의 경우 프로토콜을 채택한 타입마다 다르게 작성할 수 있다. 따라서 프로토콜도 다형성을 적용할 수 있는 좋은 방안 중 하나라고 생각한다.

accessibilityElements

accessibilityElements는 Accessiblity 요소들을 담아놓는 배열을 의미한다. 만약 Voice Over의 순서를 변경하고 싶다면 accessibilityElements에 원하는 순서대로 담아놓으면, 그 순서대로 Voice Over가 실행된다.

다만 만약 isAccessibilityElements가 false로 되어 있다면 Accessiblity Inspector가 인식을 하지 못한다. 이 때는 isAccessibilityElements를 true로 주고 accessibilityElements에 넣어줘야 한다.

그런데 만약 isAccessibilityElements를 true로 줬더라도 accessibilityElements에 넣지 않는다면 해당 UI의 경우 Accessiblity Inspector가 인식을 하지 않는다.

Larger Accessibility Sizes 속성에 따른 레이아웃 변경

레이블을 각각 2개씩 놓고 방문객, 개최지, 개최 기간을 작성해주었더니 Larger Accessibility Sizes를 활성화하고 글씨를 키우면 레이블의 배열이 이상하게 되는 문제가 있었다.

그래서 일단 Larger Accessibility Sizes를 활성화하고 글씨를 키우는 것을 조건으로 코드를 분기해주기 위해 조건을 찾았다. 그래서 찾은 것은 다음과 같다.

traitCollection.preferredContentSizeCategory.isAccessibilityCategory

의 경우 accessibilityMedium, accessibilityLarge, accessibilityExtraLarge, accessibilityExtraExtraLarge, and accessibilityExtraExtraExtraLarge 에서 true를 반환하게 된다. 즉, 아래 그림에 표시된 부분을 의미한다.

이는 Larger Accessibility Sizes를 활성화하지 않고 가장 큰 글씨로 놔뒀을 때를 기준으로 이것보다 큰 글씨를 의미한다.

단 이것보다 글씨가 커지게 되면 폰트 스타일 간 글씨 크기의 차이는 작아지게 된다. 이는 HIG Typography에서 확인할 수 있다.

또한 Larger Accessibility Sizes를 활성화했는지 알 수 있도록 Notification을 통해 알림을 받아야 한다.

NotificationCenter.default.addObserver(self,
                                       selector: #selector(setLayoutByDynamicType),
                                       name: UIContentSizeCategory.didChangeNotification,
                                       object: nil)

해당 Notification을 주는 NotificationCenter의 이름은 UIContentSizeCategory.didChangeNotification이다.

Accessory View

어제부터 Accessory View인 disclosure indicator에 접근을 못해서 오토레이아웃을 잡는데 어려움을 겪었고, 결국 방법을 찾지 못해 직접 버튼을 구현했었다.

하지만 오늘 직접 핸드폰으로 Voice over를 해봤더니 실제로 disclosure indicator는 단순히 button으로 읽어줬다. 즉, 기존에 Accessibility에서 읽어주던 방식이 맞았던 것이다... 여기선 직지심체요절, 설명~~, Button으로 되어 있었는데 기본으로 설정하면 그렇게 읽어주는 것이 맞았다.

삽질을 굉장히 많이 했지만 그래도 이를 통해 버튼을 처음으로 코드로 만들어봤고 많이 배울 수 있었다! 하지만 앞으로 실제 애플 기본 앱에선 어떻게 구현이 되어 있는지 확인도 해봐야겠다.

constraint(equalToSystemSpacingBelow:multiplier:)

equalToSystemSpacingBelow에 넣어준 기준을 시작으로 메서드를 사용한 곳에서의 거리에 multiplier를 곱한 값을 떨어뜨려주는 메서드이다.

WWDC 2017 Building Apps with Dynamic Type에 보면 해당 메서드를 통해 글씨가 커지게 되면 레이블을 바로 아래로 내릴 수 있도록 하는 코드를 예시로 보여주고 있는데 이 때 봤던 메서드이다.

Comments