데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      디자인 시스템 #2 : SwiftUI를 활용한 BottomSheet 제작기-1

      seungchann 24.11.06
      3,426 6 1
      DEVOTEE 요약
      본 블로그를 통해 SwiftUI를 사용하여 BottomSheet를 구현한 경험을 공유합니다. BottomSheet는 하단 슬라이드 패널로, 유저에게 추가 정보를 제공하거나 작업을 수행하도록 도와주는 역할을 합니다. 글에서 제공된 방법들은 Apple의 sheet와 presentationDetent 옵션을 활용하여 높이를 동적으로 조절하고, 드래그 제스쳐로 시트를 닫을 수 있도록 구현하는 절차를 설명하고 있습니다.

      들어가며

      안녕하세요. SK텔레콤 AI Assistant iOS팀 김승찬입니다.

      저는 에이닷 서비스 내 활용되는 디자인 시스템을 개발 및 관리하고 있습니다.


      이번에 신규 디자인 시스템을 구축하면서 많은 기술적 고민들이 있었는데요.

      이런 고민들을 해결했던 저만의 방법들을 공유드리기 위해 포스팅을 진행하려고 합니다.


      두번째 주제는 SwiftUI를 활용한 BottomSheet 제작기 입니다.


      일반적으로 iOS 앱에서 Bottom Sheet란 화면의 하단에서 슬라이드로 나타나는 패널을 지칭합니다.

      이 패널을 사용하여 사용자에게 추가적인 정보를 제공하거나 특정 작업을 수행하도록 유도합니다.

      보통 Action Sheet나 Modal과 비슷한 역할을 하지만, 화면 전체 차지하지 않고 필요에 따라 크기를 조절할 수 있습니다.

      사용자가 스와이프로 쉽게 닫거나, 원하는 만큼 드래그하여 더 많은 내용이나 옵션을 확인할 수도 있습니다.


      이러한 Bottom Sheet를 SwfitUI로 구현하려다보니 종종 곤란한 경우들이 있었는데요.

      이 글을 통해 SwiftUI에서 제공하는 옵션들과 여러 제약사항들을 소개드릴 예정입니다. 앞으로 커스텀 Bottom Sheet를 개발하려는 분들께 제 포스팅이 도움이 되었으면 좋겠습니다.


      포스팅은 다음과 같은 순서로 진행될 예정입니다.

      1편

      • 에이닷에 적용된 BottomSheet

      • Apple에서 제공하는 sheet, presentationDetent 옵션

      • BottomSheet 인터페이스 설계

      2편

      • 동적 높이 적용

      • 확장형 시트 구현

      • 백그라운드에 커스텀 컬러 적용

      • 마치며


      에이닷에 적용된 BottomSheet

      bottomSheet.gif


      현재 에이닷 서비스에 적용된 바텀시트 입니다.

      이번 포스팅에서는 시트의 디자인이 아닌, 적용된 인터렉션에 초점을 맞춰 진행 할 예정입니다.


      이번 BottomSheet를 구현하면서 중점적으로 살펴봤던 요건들은 다음과 같습니다.

      1. 시트 내부 뷰의 높이에 맞춰 동적으로 시트의 높이가 결정되어야 합니다.

      2. 시트 전체 영역에서 드래그 제스쳐로 시트를 닫을 수 있어야 합니다.

      3. (내부 컨텐츠 사이즈가 큰 경우) 시트를 위로 스와이프 했을 때 확장형 시트가 제공되어야 합니다.

      4. 시트 내부 백그라운드에 커스텀 컬러가 적용되어야 합니다.


      Apple에서 제공하는 sheet, presentationDetent 옵션

      기존 bottomSheet의 경우, OS에서 제공하는 sheet modifier를 사용하지 않았습니다.

      살펴보니 별도의 커스텀 뷰와 네비게이터를 활용하여 구현이 되어 있었고, SwiftUI에서 제공하는 옵션은 사용되지 않았습니다.


      이번에 새롭게 bottomSheet를 리팩토링하면서, OS에서 제공되는 sheet modifier를 활용하는 것이 효율적이라고 판단했습니다.

      1. 드래그 제스쳐 & 확장형 시트를 옵션으로 제공합니다.

      2. 커스텀 네비게이터에 비해 자연스러운 애니메이션을 제공합니다.

      3. 하단에서 시트가 올라올 때 필요한 공간들을 OS차원에서 관리합니다. (ex. 키보드, safeArea 등)

      4. PresentationDetent 라는 옵션을 활용할 경우, 동적 높이 대응이 가능합니다.

      이런 점들을 고려하여, sheet modifier를 활용한 커스텀 bottomSheet를 제작하게 되었습니다.


      먼저 swiftUI에서 제공되는 sheet modifier를 살펴보겠습니다.

      struct ShowLicenseAgreement: View {
          @State private var isShowingSheet = false
          var body: some View {
              Button(action: {
                  isShowingSheet.toggle()
              }) {
                  Text("Show License Agreement")
              }
              .sheet(isPresented: $isShowingSheet,
                     onDismiss: didDismiss) {
                  VStack {
                      Text("License Agreement")
                          .font(.title)
                          .padding(50)
                      Text("""
                              Terms and conditions go here.
                          """)
                          .padding(50)
                      Button("Dismiss",
                             action: { isShowingSheet.toggle() })
                  }
              }
          }
      }

      SwiftUI에서 제공하는 sheet를 사용하게 되면,

      1. 시트를 띄울지를 결정하는 isPresented, 내부 뷰인 content 만으로 시트를 사용할 수 있습니다.

      2. 기본적으로 large 사이즈로 화면에 표시됩니다.

      3. 스와이프 다운 제스쳐로 시트를 닫을 수 있으며, 시트 상단의 dim 부분을 터치해도 닫을 수 있습니다.

      sheet


      large 사이즈의 경우, 예시 화면처럼 content의 높이와 상관없이 기기 상단 높이에 맞춰 시트가 뜨게 됩니다.

      그래서 높이 조정을 할 수 있는 옵션인 presentationDetent 라는 modifier를 사용하게 되었는데요.


      Apple에서 제공하는 presentationDetent의 옵션들을 살펴보겠습니다.

      1. medium

        • 시트의 높이가 화면 중간 정도 높이까지로 고정됩니다.

      2. large

        • 시트의 높이가 화면 전체를 덮을 정도로 높이 고정됩니다.

      3. custom(D.Type)

        • CustomPresentationDetent 프로토콜을 준수하는 detent를 사용할 수 있습니다.

        • (제공되는 커스텀 옵션에 제약이 많아 자유롭게 사용하기는 어려워 보입니다.)

      4. fraction(CGFloat)

        • (전체 높이 기준) 특정 비율로 시트의 높이를 결정합니다.

      5. height(CGFloat)

        • 특정 높이 값으로 시트의 높이를 결정합니다.

      이러한 옵션들을 활용하여 다음과 같이 구현할 수 있습니다.

      struct ContentView: View {
          @State private var showSettings = false
      
      
          var body: some View {
              Button("View Settings") {
                  showSettings = true
              }
              .sheet(isPresented: $showSettings) {
                  SettingsView()
                      // 스와이프 제스쳐에 따라 시트 높이에 변화가 생김
                      .presentationDetents([.medium, .large, .height(200)])
              }
          }
      }


      BottomSheet 인터페이스 설계

      SwiftUI에서 많은 옵션들을 제공해주고 있지만, 실제 서비스에 적용이 가능한지 고민이 많았는데요.

      공통으로 제공되는만큼, 여러 도메인 서비스에서도 이슈 없이 사용이 가능한지 검토를 진행하며 개발했습니다.

      public enum AdotSheetType {
          case `default`
          case expand
      }
      
      func adotBottomSheet<PresentableView> (
              isPresented: Binding<Bool>,
              type: AdotSheetType = .default,
              background: ColorTheme = .surfaceBgFloating,
              @ViewBuilder content: @escaping () -> PresentableView,
              completion: (() -> Void)? = nil
          ) -> some View where PresentableView: View {
              return modifier(
                  BottomSheetModifier(
                      isPresented: isPresented,
                      type: type,
                      background: background,
                      builder: content,
                      completion: completion
                  )
              )
          }

      adotBottomSheet는 두 가지 타입(일반, 확장형) 을 지원하고 있으며, 커스텀 백그라운드 컬러를 지정할 수 있습니다.

      마치 OS에서 제공되는 modifier처럼 사용해주셨으면 해서, parameter와 네이밍을 SwiftUI와 유사하게 사용했습니다.


      Modifier 내부에서는 각 시트 타입들을 wrapping 할 수 있는 View와 sheet modifier 만을 이용하여 구현했습니다.

      private struct BottomSheetModifier<Builder>: ViewModifier where Builder: View {
          (중략)...
          
          func body(content: Content) -> some View {
              content
                  .sheet(
                      isPresented: $isPresented,
                      onDismiss: completion
                  ) {
                      ZStack {
                          Rectangle()
                              .fill(background)
                              .ignoresSafeArea()
                          
                          // 애니메이션 이슈 방어를 위한 분기처리
                          if type == .default {
                              DynamicSheetView {
                                  builder()
                              }
                          } else {
                              ExpandableSheetView {
                                  builder()
                              }
                          }
                      }
                  }
          }
      }

      각각의 시트 타입을 wrapping하고 있는 DynamicSheetView, ExpandableSheetView는,

      앞으로 이어질 두번째 포스팅 내 동적 높이, 확장형, 커스텀 컬러를 적용해 가는 과정에서 하나하나씩 소개드리려 합니다.

      긴 글 읽어주셔서 감사합니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      seungchann 님의 최신 블로그

      더보기
      동영상 기고하기