본문 바로가기

iOS

[iOS] iOS 15 SwiftUI Features

Introduce


본 문서에서는 iOS15 SwiftUI의 주요 변경 Feature들을 간단한 예시 코드와 함께 소개합니다.

 

What’s New in SwiftUI 3.0(iOS 15.0)


 

Markdown 지원 및 새로운 AttributedString API


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

iOS 15 부터는 Apple의 Foundation 프레임워크 및 SwiftUI에서 Markdown 기능을 제공합니다.

 

따라서 아래와 같이 SwiftUI의 Text에서 Markdown 문법을 사용하여 문자열을 추가할 수 있습니다.

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

 

또한 이를 AttributedString에 접목하여 아래와 같이 문자 범위를 사용자가 직접 제어할 수 있습니다.

do {
    let thankYouString = try AttributedString(
        markdown:"**Thank you!** Please visit our [website](<https://example.com>)")
} catch {
    print("Couldn't parse: \\(error)")
}

 

New Button Styles


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

 

SwiftUI의 Button 이 새로운 role 과 styling modifier 가 추가되어 더욱 강력해졌습니다.

 

ButtonRole은 아래와 같은 종류들을 제공합니다.

  • cancel
  • destructive
  • none
  • some() Publisher

 

또한 buttonStyle 이라는 수정자를 통해 버튼 스타일을 지정할 수 있습니다.

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)
       
}

 

SwiftUI AsyncImage for Loading Images From URL


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

 

이전 버전까지는 URL을 통해 이미지를 로드하기 위해서는 로더를 설정하고 비동기 작업을 수행해야 했습니다.

 

SwiftUI 3.0에서는 AsyncImage 를 사용하여 전체 프로세스를 추상화하였습니다.

 

AsyncImage(url: URL(string: "<https://example.com/icon.png>"))
    .frame(width: 200, height: 200)

 

이미지가 로드되기 이전까지는 placeholder 이미지가 보여지며 로드가 완료된 이후에 디스플레이 됩니다.

 

만일 별도의 placeholder 이미지를 사용하고자 한다면 아래와 같이 사용이 가능합니다.

 

AsyncImage(url: URL(string: "<https://example.com/icon.png>")) { image in
    image.resizable()
} placeholder: {
		// PlaceHolder 설정
    ProgressView()
}
.frame(width: 50, height: 50)

 

위 예시에서는 placeholder로 ProgressView를 사용하여 로딩중임을 유저에게 알려줄 수 있습니다.

 

 

SwiftUI TextField Gets Better Keyboard Management


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

 

iOS 15 부터는 현재 활성화된 필드를 코드 기반으로 제어하는 @FocusState 라는 속성이 있습니다.

 

이를 통해 특정 텍스트 필드를 강조할 수 있으므로 로그인 혹은 등록과 같은 입력 기반의 양식에서 매우 유용하게 사용될 수 있을것 같습니다.

 

TextField에 코드 기반으로 포커스를 구현하기 위해서는 focusStated modifier에 @FocusState 를 바인딩해야 합니다.

 

enum CurrentField {
	case field1
	case field2
}

struct TextFieldFocus: View {
	@State var field1 = ""
	@State var field2 = ""

	@FocusState var activateState: CurrentField?

	var body: some View {
		VStack {
			TextField("Email", text: $field1)
				.focused($activateState, equals: .field1)
				.submiltLabel(.conttinue)

			SecureField("Password", text: $field2)
				.focused($activateState, equals: .field2)
				.submitLabel(.send)

			Button("Activate Email Field") {
				// field1으로 포커스 이동
				activateState = .field1
			}

			Button("Activate Password Field") {
				// field2로 포커스 이동
				activateState = .field2
			}
		}
	}
}

 

참고로 iOS15에서 해당 기능은 beta로 제공되었기에 Form 내에서는 작동하지 않습니다.

 

TextField에 대한 focus Management 기능이 향상되었기 때문에 기존에 반환에 대한 onCommit 콜백 지원이 중단되었습니다.

 

 

SwiftUI Lists Are Now Searchable


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

 

SwiftUI의 1.0과 2.0 버전에서는 List 에 대한 검색 기능을 제공하지 않았습니다.

 

Apple은 3.0 버전부터 List 에 searchable 수정자를 제공하며 이를 통해 검색 기능을 지원합니다.

 

searchable 한 List 를 사용하기 위해서는 NavigationView 내부에 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"),
                                               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)
								// SearchColor라는 PlacheHodler를 기재하며 검색 쿼리를 searchQuery에 바인딩합니다.
								// 검색창의 위치는 Apple 플랫폼을 기반으로 결정합니다.**
                .navigationTitle("Colors List")
            }
        }
    
        var searchResults: [Colors] {
            
            if searchQuery.isEmpty{
                return colors
            }
            else{
                return colors.filter {$0.name.lowercased().contains(searchQuery.lowercased())}
            }
        }
}

위와 같이 searchable 을 통해 검색 기능을 지원할 수 있으며 searchQuery 라는 상태 변수에 바인딩을 하여 해당 값을 즉각적으로 받아올 수 있습니다.

 

 

SwiftUI List Pull To Refresh


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

 

이전 버전까지는 UIVIewRepresentable 을 통해 제공했던 Pull To Refresh 를 iOS 15부터 refreshable 이라는 수정자를 통해 Native로 지원하게 되었습니다.

 

var body: some View {
	NavigationView {
		List($colors) {
			Text(color.name)
		}
		.refreshable {
			self.colors.append(Colors(name: "새로운 색상 추가")
		}
		.navigationTitle("Colors List")
	}
}

 

refreshable 수정자를 통해 해당 블록 내부에서 async/await 과 같은 네트워크 요청을 가져오거나 결과를 표시하는것 또한 가능합니다.

 

 

SwiftUI Lists Swipe Actions


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

 

List에서의 Swipe 작업 수행을 위한 기능은 이번 버전부터 swipeActions 라는 수정자로 제공되었습니다.

 

var body: some View {
	NavigationView {
		List {
			ForEach(colors, id: \\.id){ color in
				Text(color.name)
					.swipeAction {
						Button(role: .destructive) {
							// do something.
						} label: {
								Label("삭제", systemImage: "xmark.bin")
						}
						Button {
							// do something.
						} label: {
								Label("고정", systemImage: "pin")
						}
					}**
			}
		}
	}
}

 

기본적으로 swipe 동작에 대한 결과는 edge: .trailing 으로 뒷쪽 가장자리에서 표시되지만 .swipeActions(edge: .leading) 을 통해 앞쪽에서 작동하도록 설정하여 변경할 수 있습니다.

 

 

More Modifiers for Lists, Tabs


 

SwiftUI에서 List가 가장 많은 업그레이드(searchView, refreshable, swipeAction ..etc) 가 진행되었습니다.

 

위와 같이 다양한 기능이 제공되었으나 만일 이것으로도 작업이 충분하지 않다면 List를 커스텀 할 수 있는 다양한 방법이 있습니다.

  • listRowSeparator 혹은 listSectionSeparator 을 통해 Separator Line을 제공할 수 있습니다.
  • listRowSeparatorTint 또는 listSectionSeparatorTint 를 통해 Separator Line에 색상을 추가할 수 있습니다.
  • badge 는 SwiftUI List Row의 오른쪽에 위치하며 TabView 에서도 사용이 가능합니다.

A Task Modifier for Concurrency


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

 

이전 버전까지는 View 에 보여줄 데이터를 가져올때 onAppear 수정자를 많이 사용했으나 IOS15 부터는 Apple에서 비동기로 데이터를 로드할 수 있는 Task 수정자를 제공합니다. (네트워킹과 같은 작업을 수행하기 딱 좋은것 같아요 🙂)

 

onAppear에 비하여 좋은 점은 task 가 modifer로 추가된 view의 수명과 일치하는 수명을 가진다는 점 입니다.

 

추가한 SwiftUI View 가 삭제될 때 자동으로 해당 작업 또한 취소하도록 지원하며 이를 통해 보다 간편한 데이터 로드가 가능해졌습니다.

 

let url = URL(string: "<https://example.com>")!
@State private var message = "Loading..."

var body: some View {
    Text(message)
        **.task {
				// task Modifier를 통해 비동기로 데이터를 가져옵니다.
            do {
                var receivedLines = [String]()
                for try await line in url.lines {
                    receivedLines.append(line)
                    message = "Received \\(receivedLines.count) lines"
                }
            } catch {
                message = "Failed to load"
            }
        }**
}

 

 

New Canvas and Timeline Views


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

 

Apple은 SwiftUI에서 drawRect 대신 Canvas API 형태로 해당 기능을 제공합니다.

 

Canvas API 는 drawing paths 혹은 shapes 에 대한 기본적인 기능을 제공합니다.

 

이를 통해 앱에서 mask를 설정하거나 필터 변환 및 적용등을 통해 보다 풍부한 그래픽을 만들 수 있습니다.

 

TimelineView 는 실시간으로 업데이트 되는 동적 뷰를 구성하는 기능을 제공합니다.

 

TimelineView 는 TimelineSchedule 이라는 매개변수를 받아 생성되며 해당 Schedule에 따라서 View가 업데이트 됩니다.

 

TimelineView(.animation) {
	let time = $0.date.timeIntervalSince1970
	Canvas { context, size in
		let metrics = gridMetrics(in: size)
		let focalPoint = focalPoint(at: time, in: size)
		for (idx, symbol) in symbols.enumerated() {
			let rect = metrics[idx]
			let (sRect, opacity) = rect.fishEyeTransform(around: focalPoint, at: time)
			
			context.opacity = opacity
			let image = context.resolve(symbol.image)
			context.draw(image, in: sRect.fit(image.size))
		}
	}
}

 

시간 혹은 특정 이벤트에 따라 자동으로 넘어가는 배너와 같은것을 만들고자 할 때 유용하게 사용할 수 있을것 같습니다.

 

'iOS' 카테고리의 다른 글

[iOS] Clean Architecture + MVVM Example  (0) 2022.05.07
[iOS] Clean Architecture  (0) 2022.05.07
[iOS] iOS 14 SwiftUI Features  (0) 2022.02.05
[iOS] iOS 13 SwiftUI Features  (0) 2022.02.04
[iOS] Using AVAudioEngine to Record Audio on iOS  (0) 2021.12.11