본문 바로가기

IOS

Swift - ViewModifier 를 이용해 View를 Custom 해보자

 

https://moonggi-dev-story.tistory.com/74 

 

App Store, TestFlight 배포후 앱이 죽는 현상

App Store, TestFlight 배포후에 iOS14, iOS15 과의 실행 결과가 달랐던 이슈가 생겼다. 분명히.. Xcode 상에서 테스트 했을땐 두버전 다 정상이였는데 Release 버전 배포후에 알수 있었다. iOS 14 : 앱크러쉬 발.

moonggi-dev-story.tistory.com

해당 게시글에 이어서 App Store, TestFlight 배포하고 앱이 죽는 현상에 대해 알아볼껀데... 왜 제목이 이거냐구요?

이번엔 특정 기능에서 오류가 발생해서 앱이 죽었기 때문에 그에대한 해결법을 구현하다 보니 ViewModifier 였습니다


1. 어떤오류??

  현재 SwiftUI - View Body 상에서 List 의 라인을 지우는 옵션을 사용 했었음 (iOS 15, iOS 14) 분기 처리후

  처음엔 SwiftUI 뷰의 옵션에 대한 if(Extension) 이 잘못인줄 알았으나

  Body 상에서 사용하고 있는 #available 이 문제였음 (아래의 링크 참고)

 

https://developer.apple.com/forums/thread/691559

https://developers.apple.com/forums/thread/650818

https://developer.apple.com/forums/thread/696856?answerId=698722022#698722022

https://developer.apple.com/forums/thread/697070 

해당 링크상에서 수정할수 있는 방법은 3가지를 제시 하였다

  1. Xcode 13.1 다운그레이드

  2. Xcode 13.2 사용중일경우 프로젝트 -> Build Settings -> Swift Compiler - Code Generation - Optimization Level(Release) -Onone 설정 변경

  3. 해당 옵션에 대해 ViewModifier(Custom) 를 이용해 재 구성 하는것

빌드 방식을 바꾸긴 싫었고, ViewModifier 로 결정 하였음

2. ViewModifier 란?

뷰에는 여러가지 옵션들이 존재한다.

예시로 font, background, padding 등등, ViewModifier는 해당 옵션들을 하나로 묶어 공통으로 사용할수 있게 해준다.

지금 진행하는 예시는 List 의 Separator를 제거 하는 옵션을 만들고자 한다.

3. 소스코드

먼저 iOS14 에서 Separator를 감출 소스를 준비한다.

import Foundation
import SwiftUI

struct HideRowSeparatorModifier: ViewModifier {
  static let defaultListRowHeight: CGFloat = 44
  var insets: EdgeInsets
  var background: Color
  init(insets: EdgeInsets, background: Color) {
    self.insets = insets
    var alpha: CGFloat = 0
    if #available(iOS 14.0, *) {
        UIColor(background).getWhite(nil, alpha: &alpha)
    } else {
        background.uiColor().getWhite(nil, alpha: &alpha)
    }
    self.background = background
  }
    
  func body(content: Content) -> some View {
    content
      .padding(insets)
      .frame(
        minWidth: 0, maxWidth: .infinity,
        minHeight: Self.defaultListRowHeight,
        alignment: .leading
      )
      .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
      .background(background)
  }
}

extension EdgeInsets {
  static let defaultListRowInsets = Self(top: 0, leading: 0, bottom: 0, trailing: 0)
}

extension View {
  func hideRowSeparator(
    insets: EdgeInsets = .defaultListRowInsets,
    background: Color = .white
  ) -> some View {
    modifier(HideRowSeparatorModifier(
      insets: insets,
      background: background
    ))
  }
}

다음은 ViewModifier 에서 iOS14, iOS15 분기를 태워서 라인을 지워본다.

import SwiftUI

extension View {
    
    @ViewBuilder
    func hideLine() -> some View {
        if #available(iOS 15, *) {
            self.modifier(HideLineFor15())
        } else {
            self.modifier(HideLineFor14())
        }
    }
}

struct HideLineFor14: ViewModifier {
    
    func body(content: Content) -> some View {
        content.hideRowSeparator()
    }
}

@available(iOS 15, *)
struct HideLineFor15: ViewModifier {
    
    func body(content: Content) -> some View {
        content.listRowSeparator(.hidden)
    }
}

따로 구조체를 안만들고 hideLine 내에서 처리 할라 했으나, Release 버전에서 앱이 또죽어 다음과 같이 구조체를 생성 하였다.

 

적용은 다음과 같이하면 끝이다

let items = ["a","b","c","d","e"]

List {
	ForEach(items, id:\.self) {
    	Text("아이템 값 : \($0)")
        	.hideLine()
    }
}

다음과 같이 적용하여 앱 크러쉬를 막을수 있었음


참고

https://developer.apple.com/forums/thread/696947

 

swift_getOpaqueTypeMetadata crash … | Apple Developer Forums

I managed to fix this crash by using custom ViewModifier public extension View {   @ViewBuilder   func searchableFor15(text: Binding ) -> some View {     if #available(iOS 15, *) {         self .modifier(SearchModifier(text: text))     } else {

developer.apple.com