SWIFTUI/Others

SwiftUI 3.0의 새로운 기능! [ iOS 15, Xcode 13 ]

서근 2021. 6. 12. 14:36
반응형
본 게시글은 Anupam Chugh 님의 게시글을 번역한 자료 입니다.

 

SwiftUI는 Apple의 선언적 UI 프레임 워크입니다. 이번 WWDC 2021에서는 흥미로운 새로운 개선 사항, 일부 기능의 사용 중단 발표가 있었고, 개발자들에게 다시 한번 환영을 받았습니다.

 

SwiftUI 3.0iOS 15, iPadOS 15, macOS 12watchOS 12에서 사용할 수 있습니다.

 

시작하기 전에 주목할 사항이 있습니다. Info.plist 파일은 기본적으로 Xcode 13 Project 구조에서 더 이상 표시 되지 않습니다. 대신 프로젝트 내비게이터 탭에서 액세스 해야 합니다. 다음 몇 섹션에서는 iOS 15SwiftUI의 새로운 기능을 살펴볼 것입니다. (대부분 다른 플랫폼에서도 일부 다른 형식으로 작동 할것입니다.)

 


Markdown 지원 및 새로운 AttributedString API

Markdown은 서식 있는 텍스트를 작성하기 위한 공통 언어입니다. GitHub Readme 문서에서 많은 것을 보았을 것입니다. iOS 15부터 Apple은 Foundation 프레임 워크 및 SwiftUIMarkdown 지원을 제공합니다. 따라서 아래와 같이 SwiftUI Text에서 Markdown 구문으로 문자열을 추가할 수 있습니다.

Text("**Connect** on [Twitter](url_here)!")

Screengrab by the author — SwiftUI Text Markdown

위의 문자열에서 문자 범위를 사용자 정의하려면 SwiftUI용 새로운 AttributeString APIUIKit용 향상된 NSAttributedString을 사용할 수 있습니다.

do {
    let thankYouString = try AttributedString(
        markdown:"**Welcome** to [website](https://example.com)")
} catch {
    print("Couldn't parse: \(error)")
}

SwiftUI Text에서 위의 AttributeString을 전달하거나, 새로운 AttributeScopeSwiftUI Attributes를 사용하여 사용자 지정할 수 있습니다.

 

SwiftUI에서 속성 문자열을 향상시키는 방법에 대한 Zheng의 게시글을 참고할 수 있습니다.

 


새로운 버튼 스타일

SwiftUIButton은 이제 훨씬 더 강력 해졌습니다. 새로운 rolesstyling modifier가 있습니다.

ButtonRole을 사용하면 버튼의 종류를 확인할 수 있습니다.

  • cancel
  • destructive
  • none
  • some() Publisher

마찬가지로 이제 SwiftUI 버튼 스타일을 지정할 수 있습니다. buttonStyle View 수정자를 사용하여 BorderedButtonStyle, BorderlessButtonStyle, PlainButtonStyle 또는 DefaultButtonStyle을 적용할 수 있습니다.

TIP
 
 

참고 : 이러한 스타일은 열거형 스타일로 대체할 수도 있습니다.

BorderedButtons의 경우 capsule, roundedRectangle또는 automatic중 어느 것을 원하는지를 설명하는 새로운  BorderedShape가 있습니다.

 

다음은 iOS 15에서 SwiftUI Button에 적용할 수 있는 다양한 스타일 및 역할 유형에 대한 간략한 설명입니다.

VStack{
            
      Button("Plain", role: .none, action: {})
      .buttonStyle(PlainButtonStyle())

      Button("Automatic", role: .none, action: {})
      .buttonStyle(.automatic)

      Button("Log out", role: .cancel, action: {})
      .buttonStyle(BorderedButtonStyle())
      .tint(.yellow)

      // with controlSize
      Button("Cancel", role: .cancel, action: {})
      .buttonStyle(.borderless)
      .controlSize(.small)
      .tint(.yellow)

      Button("Delete", role: .destructive, action: {})
      .buttonStyle(.bordered)
      .controlSize(.regular)

      // with controlProminence
      Button(role: .destructive, action: {}, label: {
          Text("Exit").frame(maxWidth: .infinity)

      })
      .buttonStyle(.bordered)
      .controlSize(.large)
      .controlProminence(.increased)

      //with BorderedShape
      Button(role: .destructive, action: {}, label: {
          Text("Wow shape").frame(maxWidth: .infinity)
      })
      .buttonStyle(BorderedButtonStyle(shape: .capsule))
      .controlSize(.large)
      .controlProminence(.increased)
      .tint(.purple)
       
}

위의 네 번째 코드를 보면 borderless buttontint color를 따르지 않고 automatic 스타일이 현재 OS 시스템을 따르고 있는 것을 알 수 있습니다.

TIP
 
 

group container (위 사진에서는 VStack를 가정함)에 스타일 수정자를 설정하면 모든 Button 컨트롤에 적용됩니다.

 

위 이미지에서 설명하지 않은 두 가지 속성은 controlSizecontrolProminence 입니다.

  • controlSize : 사용 가능한 몇 가지 표준 옵션에서 버튼의 크기를 선택합니다. 
  • controlProminence : 불투명도를 정의합니다. (이미지 맨 아래 두 개의 버튼)

 

SwiftUI Button 컨트롤의 많은 변경 사항 외에도 Apple은 LocationButton으로 알려진 SwiftUICoreLocationUI 버튼도 공개했습니다. 표준화된 현재 위치 UI를 제공하고 위치 좌표를 빠르게 가져오는 데 도움이 됩니다.

LocationButton(.sendMyCurrentLocation) {
// Fetch location with Core Location.
}.labelStyle(.titleAndIcon)

 

 


URL에서 이미지를 로드하기 위한 SwiftUI AsyncImage

이전에는 이미지에 원격 URL을 표시하려면 로더를 설정하고 asynchronous(비동기) 작업을 수행해야 했습니다. 다양한 로딩 상태를 다루는 것은 계속 증가하는 상용구 코드에만 추가되었습니다.

 

고맙게도 SwiftUI 3.0은 AsyncImage를 사용하여 전체 프로세스를 추상화합니다.

 

작동하는 방법은 :

AsyncImage(url: URL(String: <some_url>)!)

Xcode에서 사용해보겠습니다.

import SwiftUI

struct AsyncImageView: View {
    let str = "https://unsplash.com/photos/random"
    
    var body: some View {
        
        VStack {
            AsyncImageView(url: URL(string: str)!, content: { image in
                            image}, placeholder: {
                                Image(systemName: "pencil.and.outline")
                                    .renderingMode(.original)
                                    .font(.system(size: 50))
                            })
        }
    }
}

content blockAsyncImagePhase 열거 형을 사용하여 결과의 ​​다양한 상태를 처리할 수도 있습니다.

AsyncImage(url: URL(string: str)!, scale: 1.0){
    content in

    switch content{
    case .empty:
    case .success(let image):
    case .failure(let error):
}

AsyncImage를 사용하면 애니메이션을 전달할 수 있는 transcation을 지정할 수 있습니다. 

 

크기를 제어할 수 있을 뿐만 아니라 확대 및 축소에도 편리한 또 다른 scale 매개 변수가 있습니다. 

AsyncImage는 URLSession을 사용하기 때문에 캐싱에 대한 기본 지원을 제공합니다 (현재는 사용자 지정 캐시를 작성할 수 없음).

 


SwiftUI TextField, 키보드 관리 향상

iOS 15에서는 현재 활성 필드를 프로그래밍 방식으로 관리하는 새로운 속성 래퍼 (@FocusState) 가 있습니다. 이것은 개발자가 이제 특정 텍스트 필드를 강조 표시할 수 있으므로 로그인 및 등록 양식에서 매우 유용할 것입니다.

 

SwiftUI에서 TextField에 프로그래밍 방식으로 포커스를 구현하려면 focusStated modifier에 @FocusState를 바인딩해야 합니다. FocusState가 동일한 뷰에 바인딩되었는지 확인하기 위해 boolean check를 수행합니다.

 

예제를 한번 살펴보죠.

import SwiftUI

enum CurrentField{
    case field1
    case field2
}

struct TextFieldFocus: View {
    
    @State var field1 = ""
    @State var field2 = ""
    
    @FocusState var activeState: CurrentField?
    
    var body: some View {
        VStack{
            
            TextField("Email", text: $field1)
                .focused($activeState, equals: .field1)
                .submitLabel(.continue)
            
            SecureField("Password", text: $field2)
                .focused($activeState, equals: .field2)
                .submitLabel(.send)
            
            Button("Goto Email") {
                activeState = .field1
            }
            Button("Goto Password") {
                activeState = .field2
            }
            
        }
        .padding()
        .textFieldStyle(.roundedBorder)
    }
}

submitLabel View 수정자를 확인하십시오. 이렇게 하면 여러 다른 옵션으로 keyboard return button을 설정할 수 있습니다.

 

현재 SwiftUI TextField FocusState는 첫 번째 iOS 15 beta에서 의도 한대로 Form 내에서는 작동하지 않습니다.

 

새로운 onSubmit view modifier / onCommit 지원 중단

TextField에 대한 focus management 가 향상되었기 때문에, 결과를 처리하는 새로운 방법을 갖게 된 것은 놀라운 일이 아닙니다. 반환 키에 대한 onCommit 콜백은 지원 중단되었으며, 새로운 onSubmit view modifier가 있습니다. 이 modifier는 view 계층 구조에서 텍스트 필드 결과를 처리하기 위한 것입니다.

 

예제를 한번 살펴보죠.

Form{

  TextField("Email", text: $field1)
  SecureField("Password", text: $field2)
  TextField("Name", text: $field3)
      .submitScope(false) //true로 설정하면 값이 제출되지 않습니다.
}
.onSubmit {
    print(field1)
    print(field2)
    print(field3)
}

TIP
 
 

참고
선택적으로 .onSubmit(of :)를 사용하여 view type을 지정할 수 있습니다.
또한 submitScope를 true로 설정하면 해당 계층의 결과가 리턴되지 않습니다.

 


SwiftUI List 검색 기능 추가

SwiftUI 1.0 과 2.0List에서는 검색 기능이 없었습니다. iOS 15에서 Apple은 Listsearchable수정자를 제공합니다. 기억해야 할 핵심 사항은 Listsearchable으로 표시하려면 NavigationView 안에 래핑 해야 합니다.

 

search view가 있는 SwiftUI List를 먼저 살펴보겠습니다.

struct Colors : Identifiable{
    var id = UUID()
    var name : String
}

struct SearchingLists: View {
        @State private var searchQuery: String = ""
        @State private var colors: [Colors] = [Colors(name: "Blue"),
                                              Colors(name: "Red"),
                                              Colors(name: "Green"),
                                              Colors(name: "Yellow"),
                                              Colors(name: "Pink"),
                                              Colors(name: "Purple"),
                                              Colors(name: "White"),]

        var body: some View {
            NavigationView {
                List($colors) { $color in
                    Text(color.name)
                }
                .searchable("Search color", text: $searchQuery, placement: .automatic){
                    Text("re").searchCompletion("red")
                    Text("b")
                }
                .navigationTitle("Colors List")
            }
        }
}

 

고려해야 할 몇 가지 사항이 있습니다.

  • searchable을 사용하면 여러 검색 제안을 지정할 수 있습니다. 탭 하면 검색 창에 searchCompletion텍스트가 표시됩니다.
  • Automatic placement는 Apple 플랫폼을 기반으로 검색 창의 위치를 결정합니다.
  • 이제 List에서 직접 Binding배열을 전달하고 각 행에 대한 Binding값을 가져올 수 있습니다. SwiftUI TextField에 바인딩 값이 필요합니다. 이전에는 List에서 TextField를 설정하면 전체 뷰 계층 구조가 다시 로드되는 경우가 많았습니다. 고맙게도 ListBinding배열 지원은 이러한 문제를 없애줍니다.

 

검색 결과를 표시하려면 위에서 설명한 onSubmit 수정자를 사용하거나 아래와 같이 실시간으로 필터링하는 계산된 속성을 설정할 수 있습니다.

import SwiftUI

struct Colors : Identifiable{
    var id = UUID()
    var name : String
}

struct SearchingLists: View {
        @State private var searchQuery: String = ""
        @State private var colors: [Colors] = [Colors(name: "blue"),
                                               Colors(name: "Red"),
                                               Colors(name: "Green"),
                                               Colors(name: "Yellow"),
                                               Colors(name: "Pink"),
                                               Colors(name: "Purple"),
                                               Colors(name: "White"),
                                               Colors(name: "Orange"),
                                               Colors(name: "Black"),]

        var body: some View {
            NavigationView {
                List {
                    ForEach (searchResults, id:\.id){ color in
                        Text(color.name)
                    }
                }
                .searchable("Search color", text: $searchQuery,
                            placement: .automatic)
                .navigationTitle("Colors List")
            }
        }
    
        var searchResults: [Colors] {
            
            if searchQuery.isEmpty{
                return colors
            }
            else{
                return colors.filter {$0.name.lowercased().contains(searchQuery.lowercased())}
            }
        }
}

사용자가 search view와 상호 작용하는지 추적하기 위한 환경 값 isSearching도 있습니다. overlay 또는 다른 보기에서 검색 결과를 표시하거나 숨기는 데 사용할 수 있습니다.

 

중첩된 SwiftUI List와 함께 onSubmit 수정자를 사용하는 것이 아주 흥미로울 것입니다.

 


SwiftUI List Refresh

위에서 아래로 당겨서 새로고침 하는 기능도 이전에는 존재하지 않았습니다. 때문에 UIViewRepresentable이라는 해결 방법을 사용해야 했습니다. 하지만 iOS 15에서는 아래와 같이 refreshable수정자를 사용하여 몇 줄의 코드로 통합할 수 있습니다.

var body: some View {
    NavigationView {
        List($colors){ $color in

            Text(color.name)
        }
        .refreshable {
            self.colors.append(Colors(name: "Newly added"))
        }
        .navigationTitle("Colors List")
    }
}

refreshable 수정자 내에서 async / await 와 같은 네트워크 요청을 가져오고 결과를 표시할 수 있습니다.

 

현재 기본 SwiftUI Pull to Refresh는 SwiftUI Gird 또는 Scroll View에서 작동하지 않습니다. 그러나 어느 정도 사용자 지정이 가능합니다. 새로 고침을 수동으로 트리거하는 RefreshAction 환경 값이 있습니다.

 


SwiftUI Lists Swipe Actions

Swipe하여 작업을 수행하기 위한 또 다른 기능이 요청되었었고, 올해 새로운 swipeActions수정자가 추가되었습니다.

 

이번에 추가된 새로운 button style을 사용한 swipe작업을 살펴보겠습니다.

List {
      ForEach (colors, id:\.id){ color in
          Text(color.name)
              .swipeActions{
                  Button(role: .destructive){
                      //do something
                  }label: {
                      Label("Delete", systemImage: "xmark.bin")
                  }

                  Button{
                      //do something
                  }label: {
                      Label("Pin", systemImage: "pin")
                  }
              }
      }
}

기본적으로 swipe동작은 화면의 뒤쪽 가장자리에서 표시됩니다. 하지만 앞쪽에서 작동하도록 구성할 수도 있습니다. 사용방법은 아래와 같습니다.

.swipeActions(edge: .leading)

여러 수정자를 연결하여 양쪽에서 swipe동작을 지원할 수도 있습니다.

 

기본적으로 첫 번째 swipe동작은 전체 swipe에서 트리거 됩니다. 그러나 allowFullSwipefalse로 설정하여 변경할 수 있습니다.

 

현재 swipe동작은 Array List 바인딩에서 작동하지 않습니다.

 


Lists, Tabs, Alerts, and Sheets에 대한 추가 Modifiers

SwiftUI List는 의심할 여지없이 올해 가장 큰 업그레이드를 받았습니다. search view, refresh, siwpe작업만으로도 충분하지 않다면 List를 사용자화 할 수 있는 다양한 방법이 있습니다.

 

  • 구분선을 표시하거나 숨기는 listRowSeparator 및 listSectionSeparator
  • listRowSeparatorTintlistSectionSeparatorTint를 사용하여 각 행과 섹션에 각각 색조를 추가합니다. 
  • macOS의 경우 .insetStyle (alternatesRowBackgrounds:)사용.
  • badge는 SwiftUI List row 의 오른쪽에 설정됩니다. iOS 15의 SwiftUI TabViews에서도 사용할 수 있습니다.

 

SwiftUI Alert view는 이제 더 이상 사용되지 않습니다. 대신 새로운 .alert수정자가 있습니다. .alert는 또한 error dialogs에 대한 변형을 제공합니다.

 

formsheet를 바로 닫을 수 없도록 개발자가 선택할 수 있는 interactiveDismissDisabled함수가 있습니다. 간단한 예는 아래와 같습니다.

import SwiftUI

struct PressentingView: View {
    
    @State var showTerms: Bool = false
    
    var body: some View {
        Button("Show Sheet") {
            showTerms = true
            
        }
        .sheet(isPresented: $showTerms) {
            Sheet()
        }
    }
}
struct Sheet: View {
    @State private var acceptedTherms = false
    var body: some View {
        Form {
            Button("Accept Terms") {
                acceptedTherms = true
            }
        }
        .interactiveDismissDisabled(!acceptedTherms)
    }
}

interactiveDismissDisabled 사용 전
interactiveDismissDisabled 사용 후

 


동시성을 위한 Task Modifier

이전에는 View에 대한 데이터를 가져올 때 많은 SwiftUI개발자가 onAppear에 의존했지만, iOS 15에서 Apple은 데이터를 비동기 적으로 로드할 수 있는 완전히 새로운 Task 수정자를 제공합니다.

 

첨부된 SwiftUI View가 삭제될 때 자동으로 작업을 취소하는 기능은 Task 수정자를 매우 편리한 도구로 만들어줍니다.

 

요청 시 더 많은 데이터를 로드하여 끝없는 스크롤 목록을 만드는 것은 Task 수정자의 완벽한 사용방법(사례)입니다. 또한 NavigationLink destination views(아쉽게도 이부분은 개선되지 않았습니다😭)를 무거운 작업을 수행하는 데 유용합니다.

 


새로운 SwiftUI Material Struct

Material struct는 뷰에 반투명과 생동감을 추가하여 전경 요소를 background와 혼합하는 데 사용됩니다. 다음과 같은 방식으로 사용합니다.

.background(.regularMaterial)
.background(.thinMaterial)
.background(.ultraThinMaterial)
.background(.thickMaterial)
.background(.ultraThicknMaterial)

.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20))

기본적으로 material type은 background type이 전경 요소를 통과하는 정도를 나타냅니다. SwiftUI view를 시각적으로 흐리게 하는 좋은 대안입니다.

 


새로운 Canvas 및 타임 라인 View

SwiftUI의 drawRect는 2 년 동안 사라졌었습니다. 마지막으로 Apple은 이를 Canvas API 등의 형태로 제공하고 있습니다.

 

Canvas APIdrawing pathsshapes대한 기본 지원을 제공합니다. 따라서 SwiftUI앱에서 mask를 설정하고 필터를 변환 및 적용하여 풍부한 그래픽을 만들 수 있습니다. 제일 좋은 부분은 바로 CPU 가속입니다.

 

반면 TimelineView는 실시간으로 업데이트되는 동적 보기를 구성하는 기능을 도입합니다. 우리는 작년에 SwiftUI Text에서 Timer 게시자를 엿볼 수 있었습니다.

 

그러나 TimelineView는 SwiftUI view를 래핑하고 TimelineSchedule을 통해 특정 시간에 정기적 또는 구성할 수 있는 예약을 보고 설정합니다. TimelineView는 앱 내부의 위젯과 같습니다.

 

WWDC 2021에서 Apple은 SwiftUI를 사용하여 구축된 다양한 기본 iOS 앱을 선보였습니다. TimelineViews가 이들 중 일부에서 큰 역할을 했다고 확신합니다.

 


디버깅

SwiftUI는 이제 뷰 계층 구조 디버깅을 위한 내장된 지원을 제공합니다. 

 

View 본문 내에서 다음 함수를 호출하면 마지막 다시 로드 시 수정된 SwiftUI view가 인쇄됩니다.

let _ = Self._printChanges()

 


결론

이 게시글에서는 WWDC 2021에서 발표된 주요 SwiftUI 기능 중 일부를 살펴보았습니다. 그러나 macOSSwiftUI Table사용이나 SwiftUI앱에 더 많은 동시성을 추가하기 위한 CoreDataSectionedFetchRequest속성 래퍼 사용과 같이 여전히 기대할 것이 많습니다.

 

읽어주셔서 감사합니다🤟

 

 


SwiftUI 3.0 관련 Youtube 영상

- Kavsoft : What's New in SwiftUI for iOS 15 - WWDC 2021 - Xcode 13

- Paul Hudoson : What's new in SwiftUI for iOS 15 part 1 

Paul Hudoson : What's new in SwiftUI for iOS 15 part 2