2021년 1월 1일 금요일

당신이 기다려온 완전한 SwiftUI 문서

 ICHI.PRO

당신이 기다려온 완전한 SwiftUI 문서

Apple의 문서를 넘어서십시오. 코드 예제, 제안 된 모범 사례 및 모든보기, 컨트롤, 레이아웃 등에 대한 설명!

이 문서는 Medium 섹션 링크 제한으로 인해 Chrome에서 가장 잘 볼 수 있습니다.

Unsplash에 Susan Yin의 사진

Apple의 SwiftUI 문서 는 SwiftUI 의 많은 기본 사항을 다룹니다. 하지만 여전히 존재하는 그 격차는 어떻습니까? 우리 모두는 WWDC가 2020 년 6 월에 나올 때 문서가 대규모로 업데이트 될 것이라는 것을 알고 있지만 그렇게 오래 기다릴 수는 없습니다!

다음은 SwiftUI의 기존 문서의 모든 페이지에 대해 배운 모든 것입니다. 나는 애플이 제공 한 것을 반복하지 않을 것이지만 그들이 말하지 않은 것을 추가하려고 노력할 것이다. 일관성을 위해 동일한 범주를 사용할 것입니다. 제 자신을 생각하기에는 너무 게으 르기 때문이 아닙니다.

각 카테고리 제목은 Apple 버전에 직접 연결됩니다.

나는 누구도이 글 전체를 읽을 것이라고 기대하지 않습니다. 그렇게한다면 당신의 헌신에 감탄합니다! 나는 Better Programming 편집자들이 그것을 살펴 봐야한다고 생각하며 그들에게 매우 감사합니다.

지금은이 모든 것이 필요하지 않을 수도 있지만 나중에 필요할 수도 있으므로 책갈피에 추가하고 나중에 지식에 공백이있을 때 다시 돌아 오는 것이 도움이 될 것입니다.

저도 지식에 차이가 있기 때문에 가능한 한 자주이 게시물을 업데이트 할 것을 약속합니다. 더 자세히 설명하거나 다루어야한다고 생각되는 영역이 있으면 알려주세요!

목차

VIEWS AND CONTROLS
- SwiftUI 2 changes
- The View protocol
- Text
- Text ViewModifiers
- Standard text modifiers
- TextField
- TextField ViewModifiers
- SecureTextField
- SecureTextField ViewModifiers
- Font
- Image
- SF Symbols
- Button
- ButtonStyle
- NavigationView and NavigationLink
- EditButton
- MenuButton
- PasteButton
- Toggle
- Creating a custom ToggleStyle
- Picker
- DatePicker
- Slider
- Stepper
VIEW LAYOUT AND PRESENTATION
- HStack, VStack, and ZStack
- List, ScrollView, ForEach, and DynamicViewContent
- Identifiable
- Axis
- Form
- Group
- GroupBox
- Section
- Spacer
- Divider
- TabView
- VSplitView and HSplitView
- Alert
- ActionSheet
- EmptyView
- EquatableView
- AnyView
- TupleView
DRAWING AND ANIMATION
- Animation
- Animatable and AnimatableData
- AnimatablePair
- EmptyAnimatableData
- AnimatableModifier
- withAnimation (Implicit Animation)
- AnyTransition
- InsettableShape
- FillStyle
- ShapeStyle
- GeometryEffect
- Angle
- Edge and EdgeInsets
- Rectangle, RoundedRectangle, Circle, Ellipse, and Capsule
- Path
- ScaledShape, RotatedShape, and OffsetShape
- TransformedShape
- Color
- ImagePaint
- Gradients (Linear/Angular/Radial)
- GeometryReader and GeometryProxy
- CoordinateSpace
FRAMEWORK INTEGRATION
- UIHostingController
- UIViewRepresentable
- UIViewControllerRepresentable
- DigitalCrownRotationalSensitivity
STATE AND DATA FLOW
- State
- Binding
- ObservedObject
- EnvironmentObject
- FetchRequest and FetchedResults
- DynamicProperty
- Environment
- PreferenceKey
- LocalizedStringKey
GESTURES
- Gestures
PREVIEWS
- The PreviewProvider protocol

참고 : SwiftUI 2 변경 사항

View 프로토콜

아직 모르는 경우 SwiftUI는 View프로토콜을 사용하여 재사용 가능한 인터페이스 요소를 만듭니다. 뷰는 그들이 사용하는 수단 값 유형입니다 Struct대신의 Class정의.

이것이 실제로 실제로 무엇을 의미합니까?

구조체는 상속을 허용하지 않습니다. 구조체는 View프로토콜 을 따르지만 ViewApple이 제공 한 기본 클래스에서 상속하지 않습니다 .

이것은 UIViewUIKit의 거의 모든 것이 상속되는. A는 UIView기본적으로 프레임이 할당되지 않고 UIViewController서브 클래스 의 서브 뷰로 추가되지 않으면 볼 수 없습니다 .

당신이 사용은 사용자 인터페이스의 기초로 대신 스토리 보드의 SwiftUI하는 새로운 Xcode 프로젝트를 생성하면 자동으로 SwiftUI의 예를 제공 할 것 View이라고 ContentView.
ContentView구조체 안에 body라는 변수가 있음을 알 수 있습니다 . 이것은 View프로토콜 의 유일한 요구 사항이며 someSwift 5.1에 새로운 키워드를 사용합니다 .

당신은 의지 할 수 스택 오버플로 스레드 더 나은 내가 할 수있는 것보다, 어떤이 키워드 수단을 설명하기 :

"이것을"역 "의 일반적인 자리 표시 자로 생각할 수 있습니다. 호출자가 만족하는 일반 제네릭 플레이스 홀더와는 달리 ... 불투명 한 결과 유형은 구현에 의해 충족되는 암시 적 제네릭 플레이스 홀더입니다. 여기서 빼야 할 중요한 점은 반환하는 함수 some P가 특정 단일 콘크리트의 값을 반환하는 것입니다. 이 부합 함을 선언을하는 입력합니다 P. "

View프로토콜 을 준수하는 Apple이 제공하는 예제보기를 살펴 보겠습니다 .

본문

참조 : 텍스트 (2.0에서 업데이트 됨)

SwiftUI Xcode 프로젝트를 만들 때 얻는 예제 프로젝트에는 아마도 뷰에 대한 가장 간단한 빌딩 블록이 포함되어 있으며 Text.

대부분 String의 경우이 생성자에를 전달 하면 표시되는 내용이됩니다.

다음은 모든 이니셜 라이저의 몇 가지 예입니다 Text.

/*
Separate file called Localizable.strings
"string_key" = "This string is in the default file";
Separate file called Local.strings
"string_key" = "This string is in another file";
*/
import SwiftUI
final class DataModel: ObservableObject {
static let shared = DataModel()
@Published var string = "This is an ObservedObject string"
}
struct ContentView: View {
@ObservedObject var data = DataModel.shared
let substring: Substring = "This is a substring"
let string = "This is a string"
var body: some View {
VStack {
//This is a substring
Text(substring)
//This is a string
Text(string)
//This is an ObservedObject string
Text(data.string)
//string_key
Text(verbatim: "string_key")
//This string in the default file
Text("string_key")
//This string is in another file
Text("string_key", tableName: "Local", bundle: Bundle.main, comment: "Comment")
}
}
}

참고 마지막 초기화하는 데 걸리는 하나 LocalizedStringKeytableNamebundle,과 comment는 사용하는 별도의 파일이 필요합니다 .strings파일 확장자를.

이 initializer에 대한 Apple의 문서 에서 언급했듯이 유일한 필수 매개 변수는 키의 문자열입니다. 이러한 다른 매개 변수가 무엇을 요구하는지 알 수 있도록 대부분 장황한 예제를 제공했습니다.

의 기본 tableNameIS Localizable하는 문자열 파일의 표준 이름. Local이 매개 변수가 필요한 이유를 보여주기 위해 일부러 내 이름을 지정했습니다 .

번들은 기본적으로 기본 번들이므로이 Bundle.main경우 전달 은 중복됩니다.

주석은 상황에 맞는 정보를 제공해야하지만이 예제에서는 문자열 Comment.

Text들 여기에 포함 된 VStack몸 변수의 불투명 결과 유형이 하나 개의 유형으로 설정해야하기 때문이다. 즉, 여러 유형이 포함될 수 있으므로 여러 항목으로 설정할 수 없습니다.

VStack내부에 최대 10 개의 뷰를 포함 할 수 있지만 각 뷰는 , 또는 VStack일 수 있으며 각각 내부에 10 개의 유형을 가질 수 있습니다.HStackGroup

이에 대한 자세한 내용을 보려면 HStack, VStack 및 ZStack으로 스크롤 하십시오.

텍스트보기 수정 자

모든 뷰와 마찬가지로 텍스트는 ViewModifier 프로토콜을 준수하는 구조체로 수정할 수 있습니다.

다음과 같이 표면 아래에서 일어나는 일을 볼 수 있도록 사용자 지정 수정 자의 예를 살펴 보겠습니다.

import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Padding")
.padding(10)
Text("Padding")
.modifier(Padding(value: 10))
Text("Padding")
.pad(10)
}
}
}
struct Padding: ViewModifier {
let value: CGFloat
func body(content: Content) -> some View {
content
.padding(value)
}
}
extension View {
func pad(_ value: CGFloat) -> some View {
self.modifier(Padding(value: value))
}
}

보시다시피 .modifier(YourModifier())ViewModifier를 호출 하기 위해 추가 하는 것이 가능 하지만 View확장 을 사용 하고 깨끗한 호출 사이트를 제공하는 것이 훨씬 더 합리적 입니다.

이것이 표준 수정자가 보이는 방식이므로 View확장을 만들면 수정자가 기본 수정 자와 훨씬 더 비슷해집니다.

ViewModifier"modifier"라는 단어로 수정자를 시작하고 생성자를 호출하면 불필요한 복잡성이 추가되기 때문에 이것이 없으면 기본값보다 작성하기 쉬운를 만들기가 어려울 것입니다.

표준 텍스트 수정 자

기본 글꼴의 모든 크기와 무게

이 예에서는 VStack글꼴 정렬을 유지하는 주위에 빨간색 테두리를 두었습니다 . 이것은 내부 VStack의 Texts가 고정 된 최대 크기를 갖기 때문에 이것의 경계 가 제한 되어 있음을 보여줍니다 .

이것이 없으면 컨테이너가 s 내부 를 수용하도록 확장 되므로 Texts에 대한 정렬은 효과가 없습니다 .VStackText

선행 (왼쪽) 또는 후행 (오른쪽) 가장자리에 맞추려면 선행 또는 후행 가장자리가있을 위치를 정의해야합니다. 이것은 또한 VStack자체 의 너비를 고정하여 얻을 수 있습니다 .

import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Group {
//Standard fonts
Text("Large title")
.font(.largeTitle)
Text("Title")
.font(.title)
Text("Headline")
.font(.headline)
Text("Subheadline")
.font(.subheadline)
Text("Body")
.font(.body)
Text("Callout")
.font(.callout)
Text("Footnote")
.font(.footnote)
Text("Caption")
.font(.caption)
}
Group {
//Font weights
Text("Ultra light")
.fontWeight(.ultraLight)
Text("Thin")
.fontWeight(.thin)
Text("Light")
.fontWeight(.light)
Text("Regular")
.fontWeight(.regular)
Text("Medium")
.fontWeight(.medium)
Text("Semibold")
.fontWeight(.semibold)
Text("Bold")
.fontWeight(.bold)
Text("Heavy")
.fontWeight(.heavy)
Text("Bold")
.fontWeight(.bold)
}
VStack {
//Font alignments
Text("Leading")
.frame(maxWidth: 100, alignment: .leading)
Text("Center")
.frame(maxWidth: 100, alignment: .center)
Text("Trailing")
.frame(maxWidth: 100, alignment: .trailing)
Text("These lines are limited")
.frame(maxWidth: 100)
}
.border(Color.red, width: 1)
}
}
}

이 예제 Text에서 View. 이 경우 수신 된 유형이 a임을 보장 할 수만 있기 때문 Text입니다.

에 해당하는 작업을 수행하는 것이 불가능 greenStrikethrough또는 redUnderline만들어 ViewModifier나 View이러한 일반적인 가지고 있기 때문에 확장을 View을하지 않을 것을 Text.

이제이 사실을 알았 으므로 위에서 언급 한 s Text를 만드는 중간 단계없이 를 사용자 지정하는 사용자 지정 함수를 만들 수 있습니다 ViewModifier.

struct ContentView: View {
var body: some View {
VStack {
Text("Foreground colour")
.foregroundColor(.red)
Text("background colour")
.background(Color.red)
Group {
Text("background colour with padding")
.background(Color.red)
.padding()
}
.border(Color.red, width: 1)
Text("Bold")
.bold()
Text("Italics")
.italic()
Text("Baseline offset")
.baselineOffset(20)
.border(Color.red, width: 1)
Text("Tracking")
.tracking(20)
Text("Kerning")
.kerning(-1)
Group {
Text("Underline")
.underline()
Text("Blue Underline")
.underline(color: .blue)
Text("Custom Underline")
.redUnderline()
}
Group {
Text("Strikethrough")
.strikethrough()
Text("Red Strikethrough")
.strikethrough(color: .red)
Text("Custom Strikethrough")
.greenStrikethrough()
}
}
}
}
extension Text {
func greenStrikethrough() -> some View {
return self.strikethrough(color: .green)
}
func redUnderline() -> some View {
return self.underline(true, color: .red)
}
}

TextField

사용자가 텍스트를 입력하도록하려면 해당 데이터를 저장하기위한 바인딩이 필요합니다. 이 첫 번째 예제는 StateSwiftUI 구조체에 문자열을 로컬로 저장하고 실제로 사용하거나 영구적으로 저장할 수있는 곳에 저장하지 않는 변수를 사용합니다.

데이터를 유지하고 계산 된 값을 제공 할 수 있으려면 ObservableObject. 이것은 본질적으로 데이터를 저장할 일반 Swift 파일을 제공하며, 데이터 값을 수정할 수있는 컨트롤을 추가하기 시작하면 매우 유용합니다.

/* Separate file called Localizable.strings
"localized_string_key" = "This is a localized string";
*/
import SwiftUI
final class DataModel: ObservableObject {
static let shared = DataModel()
@Published var observedString = "" {
didSet {
//RUNS SUCCESSFULLY
print("Observed string changed to \(observedString)")
}
}
}
struct ContentView: View {
@ObservedObject var data = DataModel.shared
@State var localString = "" {
didSet {
//DOES NOT RUN
print("Local string changed to \(localString)")
}
}
@State var localFloat = Float()
let formatter = NumberFormatter()
var body: some View {
List {
TextField("Enter a local string", text: $localString)
Text("@State var localString = \(localString)")
TextField("Enter a observed string", text: $data.observedString)
Text("@Published var observedString = \(data.observedString)")
Text("Other initialisers")
TextField("localized_string_key", text: $localString, onEditingChanged: { isFirstResponder in
}, onCommit: {})
TextField("Placeholder", text: $localString, onEditingChanged: { isFirstResponder in
}, onCommit: {})
TextField("", value: $localFloat, formatter: formatter, onEditingChanged: { isFirstResponder in
}, onCommit: {
})
TextField("Placeholder", value: $localFloat, formatter: formatter, onEditingChanged: { isFirstResponder in
}, onCommit: {
})
}
}
}

didSet저장 한 문자열 값에 대한 클로저 를 제공 할 필요가 없습니다 didSet. 실행 시기를 보여주는 예제 만 제공합니다 . 내 DataModel클래스는 일반 Swift 클래스이므로 didSet클로저가 실행되고 문자열의 새 값을 인쇄합니다.

그러나 SwiftUI 뷰는 동적으로 생성되는 값 유형이므로 didSet콜백은 로컬 State변수 를 수정할 때 콘솔에 아무것도 인쇄하지 않습니다 .

나중에 이니셜 라이저 중 일부가 a Float대신 a 를 사용하는 방법을 알고 계셨습니까 String?

이 이니셜 라이저는 모든 유형을 취하지 만 제공된 Formatter클래스 중 하나를 전달 하거나 직접 작성해야합니다.

내 예에서는 NumberFormatter숫자가 아닌 문자를 입력 할 수없는를 사용합니다. 이렇게하면 Float문자 문자열을 의 값 Float을 저장하는 로 변환 할 수 없기 때문에 앱이 충돌 할 것이라는 걱정없이 쉽게 저장할 수 있습니다 TextField.

다른 이니셜 라이저에도 두 개의 클로저가 있으며 그 중 첫 번째는 onEditingChanged.

이 TextField이니셜 라이저 에 대한 Apple의 문서 에는 클로저 내부의 bool이 나타내는 내용이 언급되어 있지 않지만 테스트에 따르면 TextField포커스가 주어진 것과 관련이있는 것으로 보입니다 .

UIKit에서 전화 resignFirstResponder한 적이 UITextField있습니까?

UITextField더 이상 포커스가 필요하지 않기 때문에 기본적으로 키보드를 닫습니다 . 어느 시점에서 키보드를 다시 가져올 수 있더라도 UITextField다시 첫 번째 응답자로 지정하지 않는 한 텍스트가 삽입되지 않습니다 .

모두가 관련이 있음을 UIResponder추상적 인 인터페이스, 어떤에서 UIViewUIViewController그리고 UIKit 상속의 다른 기본적으로 모든.

SwiftUI 이벤트가 동일한 정도로 처리되는 방식은 알 수 없지만 .NET 을 사용한 모든 사람에게 익숙한 첫 번째 응답자 라는 문구 를 사용하고 UITextField있습니다.

bool in onEditingChanged을 호출 fieldActive하거나 명확하게 표시 할 수있는 다른 항목을 호출 할 수 있습니다 .

중요한 것은 당신이 편집 A를 시작할 때이다 TextFieldonEditingChangedtrue로 설정되는 부울 호출됩니다. 키보드의 리턴 키를 누르면 onCommit블록이 호출 된 후로 onEditingChanged설정된 부울과 함께 호출됩니다 false.

TextField ViewModifiers

ViewModifiers에 대한 자세한 설명은 Text ViewModifiers를 참조하십시오 .

struct ContentView: View {
@State var localString = String()
var body: some View {
VStack {
TextField("Red text here", text: $localString)
.foregroundColor(.red)
TextField("Red background here", text: $localString)
.background(Color.red)
TextField("Red border here", text: $localString)
.border(Color.red, width: 1)
TextField("Padding here", text: $localString)
.padding()
TextField("RoundedBorderTextFieldStyle here", text: $localString)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color.red)
Spacer()
}
}
}

현재에서 자리 표시 자 텍스트의 전경색을 변경할 수 없습니다 TextField. 이 글을 쓰는 시점 에서 s를 TextField표시 할 때 기본값이 아닌 다른 키보드 유형을 사용할 수 없습니다 .TextFieldList

TextFields 를 표시하려고 List하면 서로 겹치는 현상 이 발생했습니다. 다음은 a에 제시되었을 때 잘 작동하는 것처럼 보이는 모든 키보드 유형입니다 VStack.

import SwiftUI
struct ContentView: View {
@State var localString = ""
var body: some View {
VStack {
Group {
TextField(".keyboardType(.asciiCapable)", text: $localString)
.keyboardType(.asciiCapable)
TextField(".keyboardType(.asciiCapableNumberPad)", text: $localString)
.keyboardType(.asciiCapableNumberPad)
TextField(".keyboardType(.decimalPad)", text: $localString)
.keyboardType(.decimalPad)
TextField(".keyboardType(.default)", text: $localString)
.keyboardType(.default)
TextField(".keyboardType(.emailAddress)", text: $localString)
.keyboardType(.emailAddress)
TextField(".keyboardType(.namePhonePad)", text: $localString)
.keyboardType(.namePhonePad)
TextField(".keyboardType(.numberPad)", text: $localString)
.keyboardType(.numberPad)
TextField(".keyboardType(.numbersAndPunctuation)", text: $localString)
.keyboardType(.numbersAndPunctuation)
TextField(".keyboardType(.phonePad)", text: $localString)
.keyboardType(.phonePad)
TextField(".keyboardType(.twitter)", text: $localString)
.keyboardType(.twitter)
}
Group {
TextField(".keyboardType(.URL)", text: $localString)
.keyboardType(.URL)
TextField(".keyboardType(.webSearch)", text: $localString)
.keyboardType(.webSearch)
}
}
}
}

SecureTextField

기본적으로 TextField위와 동일하며 입력 한 문자를 숨기는 추가 이점이있어 암호에 유용합니다. 와 마찬가지로 TextField위에서 만이있는 키보드 종류의 다양한 선택할 수 있습니다 numberPad여기에 표시됩니다.

import SwiftUI
final class DataModel: ObservableObject {
static let shared = DataModel()
@Published var password = ""
@Published var passcode = ""
}
struct ContentView: View {
@ObservedObject var data = DataModel.shared
@State var password = ""
@State var passcode = ""
var body: some View {
VStack {
SecureField("Enter a local password", text: $password)
Text("You entered: \(password)")
SecureField("Enter a local passcode", text: $passcode)
.keyboardType(.numberPad)
Text("You entered: \(passcode)")
SecureField("Enter an ObservedObject password", text: $data.password)
Text("You entered: \(data.password)")
SecureField("Enter a, ObservedObject passcode", text: $data.passcode)
.keyboardType(.numberPad)
Text("You entered: \(data.passcode)")
}
}
}

SecureTextField ViewModifiers

ViewModifiers에 대한 자세한 설명은 Text ViewModifiers를 참조하십시오 .

과 유사하게 TextField전경색 또는 배경색을 변경하고 테두리를 추가하고 다른 TextFieldStyles를 사용할 수 있지만 현재 자리 표시 자 텍스트의 전경색을 변경할 수 없습니다.

폰트

Apple의 문서에 대해 자세히 설명 할 수 없으므로 FontApple의 표준 글꼴과 동일한 방식으로 사용자 지정 글꼴을 사용하는 간단한 방법을 제공했습니다.

/*
Requires dragging a font file (MyCustomFont.ttf in this example) into your Project Navigator (the left panel). Click the file in the left panel, and tick your app under "Target Membership" in the File Inspector (the right panel).
Do not put the font in the Assets.xcassets folder. Create a folder in your info.plist called 'Fonts provided by application', and inside list the string filenames (MyCustomFont.ttf in this example).
Otherwise SwiftUI will not recognise your custom font name.
*/
import SwiftUI
struct FontView: View {
var body: some View {
VStack {
Text("Custom font text")
.font(Font.custom("MyCustomFont", size: 20))
Text("Custom font text")
.font(.myCustomFont())
Text("Custom font text")
.myFont()
}
}
}
extension Font {
static func myCustomFont() -> Font {
return Font.custom("MyCustomFont", size: 20)
}
}
extension Text {
func myFont() -> some View {
return self.font(.myCustomFont())
}
}

나는 모두 확장을했습니다 방법을 공지 Font하고 View. 내가 Font.custom직접 사용할 때 알 수 있듯이 확장을 사용할 필요가 없습니다 . 이러한 모든 메서드는 동일한 결과를 가져 Text오므로 어떤 코드가 가장 깔끔한지는 문제입니다.

작성하기 가장 쉬운 방법은 View확장 기능으로, 함수에 아무것도 전달할 필요가 없습니다.

Font확장은 표준 애플 글꼴은 예를 들어, 할당 방식에 더 일치한다 .font(.headline).

영상

참조 : 이미지 (SwiftUI 2.0에서 업데이트 됨)

SwiftUI의 이미지 는 UIKit보다 훨씬 쉽습니다. 을 만들고 UIImage(named: “Your file name”)할당 할 필요없이 yourUIImageView.imageImage는 Text.

a를 전달하면 String해당 이름의 파일로 설정됩니다. 앱을 실행했는데 해당 이름의 파일이없는 경우 다음과 같은 유용한 콘솔 메시지가 표시됩니다.

No image named ‘Your file name’ found in asset catalog for main bundle.

import SwiftUI
struct ContentView: View {
var body: some View {
List {
Image("Your file name")
Image("Your file name", bundle: Bundle.main)
.resizable()
.frame(width: 100)
Image("Your file name", label: Text("My image label"))
.resizable()
.scaledToFit()
Image(decorative: "Your file name")
.resizable()
.scaledToFill()
Image(systemName: "gamecontroller")
Image(uiImage: UIImage(named: "Your file name")!)
Image(decorative: UIImage(named: "Your file name")!.cgImage!, scale: 0.5, orientation: .rightMirrored)
}
}
}

이미지는 기본적으로 크기를 조정할 수 없습니다.

후속 수정 자에서 크기를 변경하기 전에 .resizable()수정자를 호출해야합니다 Image.

scaledToFit개질제 이미지의 종횡비를 잠그고 화면 너무 큰 않고 할 수있는 최대 크기로 확장한다.

scaledToFill이후, 가능성 늘리거나 사용 가능한 공간에 맞게 이미지를 축소하는 것입니다, 수정은 또한 당신의 이미지를 조정하지만 가로 세로 비율을 고정하지 않고.

SF 기호

익숙하지 않다면 SF Symbols 는 Apple이 초경량에서 검은 색까지 9 가지 가중치로 제공하는 1500 개 이상의 기호 라이브러리입니다.

이러한 이미지를 이미지에 사용하려면 이미지에 String전달하는 레이블 을 systemName. 사용하려는 기호의 시스템 이름을 알 수 있도록 SF Symbols Mac 앱을 다운로드하는 것이 좋습니다.

SF 기호를 사용하면 이러한 무료 기호의 유연성과 접근성으로 인해 향후 몇 년 동안 iOS 생태계를 장악 할 일관된 모양을 앱에 제공 할 수 있습니다.

단추

참조 : 버튼 (2.0에서 업데이트 됨)

Button는 자신의 모습이 없습니다. 즉, 을 준수하는 구체적인 유형 인 Buttona 를 제공해야 합니다 .LabelView

가장 확실한 예는 Text버튼이 무엇을할지에 대한 정보를 제공하는 것입니다. 이 글을 쓰는 시점에서 Apple이 문서에서 지정하는 유일한 사항은 버튼을 만들고 스타일을 지정하는 방법 외에는 운영 체제에 따라 다르게 트리거된다는 것입니다.

iOS에서는 탭하고, tvOS에서는 버튼이 선택 될 때 Enter 키를 누르고, Catalyst가 있거나없는 macOS 앱에서는 Apple이 언급하지 않은 경우 마우스 나 트랙 패드로 클릭합니다.

생성자는 작업을 제공해야합니다. 이것은 빈 중괄호 세트 일 수 있지만, 최소한이 형식으로 있어야합니다.

import SwiftUI
struct ContentView: View {
func buttonAction() {
print("Button function called")
}
var body: some View {
HStack {
Button(action:{print("Button pressed")}) {
Text("Button")
}
Button(action: buttonAction) {
Text("Button")
}
}
}
}

중괄호에 기능을 지정하면 매우 빠르게 상세하게 표시 될 수있을뿐만 아니라 중괄호없이 ()호출 연산자 없이 함수 이름을 지정할 수도 있습니다 이것은 작업을 변수에 바인딩하는 것이 아닙니다. 즉 $, .NET Framework와 같은 바인딩을 사용하는 컨트롤에서 찾을 수 있는 연산자가 필요하지 않습니다 Toggle.

ButtonStyle

일부 컨트롤을 사용하면 ButtonStyle이 경우에 따르는 것과 같은 기존 스타일을 선택할 수 있습니다 . 이는 또한 SwiftUI Lab의 Custom Styling tutorial에서Button 찾을 수있는 방법에 대한 세부 사항에 대한 사용자 지정 스타일을 만들 수 있음을 의미합니다 .

해당 게시물에 대한 댓글에서 볼 수 있듯이 SliderStyle현재 존재하지 않습니다 (Apple 웹 사이트에 문서화되어 있음). 버튼의 기존 스타일을 살펴보고 실제로 작동하는지 살펴 보겠습니다.

import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button(action:{}) {
Text("Button")
}
.buttonStyle(DefaultButtonStyle())
Button(action:{}) {
Text("Button")
}
.buttonStyle(PlainButtonStyle())
Button(action:{}) {
Text("Button")
}
.buttonStyle(BorderlessButtonStyle())
#if os(macOS)
Button(action:{}) {
Text("Button")
}
.buttonStyle(LinkButtonStyle())
Button(action:{}) {
Text("Button")
}
.buttonStyle(BorderedButtonStyle())
#endif
}
}
}

일부는 MacOS에서만 사용할 수 있습니다.

NavigationView 및 NavigationLink

참고 항목 : NavigationView (2.0에서 업데이트 됨)

에 뷰를 포함 NavigationView하면 탐색 제목을 설정하고 다른 뷰에 연결할 수 있습니다. 마찬가지로 에는 기본적으로 프로토콜 을 준수하는 모든 구조체 Button인 a NavigationLink가 필요 합니다.LabelView

대부분의 경우 이는 Text또는 Image일 수 있지만 사용자가 만든 사용자 정의보기 일 수도 있습니다.

View아이폰의 오른쪽, 각각의 연속에서 귀하의 링크 슬라이드를 대상으로 NavigationLink같은 방법으로 슬라이드. 초기으로 돌아 가면 View왼쪽 가장자리에서 스 와이프하거나 탐색 메뉴의 왼쪽 상단에있는 뒤로 버튼을 사용할 수 있습니다.

이 예제는 내 시계 앱 Dog HQ에서 가져온 것입니다. 전체 크기의 강아지 사진 스크롤 목록을 표시하며 각 사진은 확대 된 버전으로 연결됩니다.

이것이 DogView내가 어떤 개가 목적지가되고 싶은지 알 수 있도록 인덱스를 확대 된 생성자에게 전달해야하는 이유 입니다.

List세로로 스크롤하고 원하는 크기로 확장하는를 결합하면 ForEach50 개의 행을 만들고 지정한 이름으로 해당 인덱스를 클로저에 전달할 수 있습니다.

에 대한 반복 ForEach은 배열의 최대 항목 수를 갖는 시퀀스 일 수 있거나 배열이 a에 대한 생성자에 전달되고 List지정한 이름으로 클로저 내부에서 액세스 될 수 있습니다 .

분명히 저는이 배열로 표면을 긁고 있습니다. 배열에는라는 문자열 속성이있는 사용자 정의 클래스 imageName, 숫자 값 또는 점 구문을 사용하여 액세스 할 수있는 다른 클래스의 인스턴스 와 같은 복잡한 유형이 포함될 수 있습니다 .

화면 상단의 탐색 모음에는 선행 및 후행 단추가 포함될 수 있습니다. 이것의 주요 용도 EditButton는 아래에서 자세히 설명 하는를 추가하는 것 같습니다.

EditButton

편집 버튼은 List항목 이 여러 개 있고 일부 항목을 삭제할 수 있도록하려는 경우 매우 유용 합니다. 탭하면 편집 모드 (당연히)로 이동하여 각 행에 수평선이있는 빨간색 원이 표시됩니다.

누르면 편집하는 것은 최종 확인의 역할을 오른쪽 끝의 삭제 버튼을 공개, 왼쪽에 행을 밀어 것입니다.

목록에서 데이터 삭제를 처리하는 함수를 구현해야합니다. 그렇지 않으면 변경 사항이 시각적으로 만 표시되고 데이터가 실제로 예상대로 삭제되지 않습니다.

사용에 대한 자세한 내용 EditButton은 List, ScrollView, ForEach 및 DynamicViewContent를 참조하십시오 .

MenuButton

현재 4 개의 menuButtonStyle 옵션을 사용할 수 있습니다.

MenuButtonmacOS 앱에서만 사용할 수 있으므로 모든 표준 .menuButtonStyle옵션 을 사용하는 Mac 앱 예제를 제공했습니다 .

왼쪽에서 오른쪽으로, 이러한 스타일은 BorderlessButtonMenuButtonStyleBorderlessPullDownMenuButtonStyle하고 PullDownMenuButtonStyle.

맨 오른쪽에있는 것이 옆에있는 것과 매우 비슷해 보이면 DefaultMenuButtonStyle.

기본값 MenuButton은 모양 이므로 PullDownMenuButtonStyle똑같아 보입니다.

//Note that this example will only work in a Mac app project, as MenuButton cannot be used on iOS
import SwiftUI
struct ContentView : View {
@State var selectedOption = "Select an option"
var body : some View {
HStack {
MenuButton(selectedOption) {
Button(action: {self.selectedOption = "Option 1"}) {
Text("Option 1")
}
Button(action: {self.selectedOption = "Option 2"}) {
Text("Option 2")
}
Button(action: {self.selectedOption = "Option 3"}) {
Text("Option 3")
}
}
.menuButtonStyle(BorderlessButtonMenuButtonStyle())
MenuButton(selectedOption) {
Button(action: {self.selectedOption = "Option 1"}) {
Text("Option 1")
}
Button(action: {self.selectedOption = "Option 2"}) {
Text("Option 2")
}
Button(action: {self.selectedOption = "Option 3"}) {
Text("Option 3")
}
}
.menuButtonStyle(BorderlessPullDownMenuButtonStyle())
MenuButton(selectedOption) {
Button(action: {self.selectedOption = "Option 1"}) {
Text("Option 1")
}
Button(action: {self.selectedOption = "Option 2"}) {
Text("Option 2")
}
Button(action: {self.selectedOption = "Option 3"}) {
Text("Option 3")
}
}
.menuButtonStyle(PullDownMenuButtonStyle())
MenuButton(selectedOption) {
Button(action: {self.selectedOption = "Option 1"}) {
Text("Option 1")
}
Button(action: {self.selectedOption = "Option 2"}) {
Text("Option 2")
}
Button(action: {self.selectedOption = "Option 3"}) {
Text("Option 3")
}
}
.menuButtonStyle(DefaultMenuButtonStyle())
}
.padding()
.frame(height: 50)
}
}

PasteButton

참고 항목 : PasteButton (2.0에서 업데이트 됨)

이 컨트롤을 사용하면 MacOS에 정보를 붙여 넣을 수 있지만 iOS에서는 사용할 수 없습니다. UTI 유형으로 표현되는 다양한 데이터 유형을 취할 수 있습니다.

이 버튼을 구현할 때 도움이 될 모든 유형의 UTI 문자열을 찾을 수있는 함수를 예제에 포함했습니다. 필요한 유형 식별자를 결정했으면에서 가져온 데이터를 처리해야합니다 NSItemProvider.

배열의 첫 번째 항목 만 붙여 넣는 예를 보여 드렸지만 다른 데이터 유형과 여러 항목을 처리 할 수있는 방법이 명확 해지기를 바랍니다.

import SwiftUI
struct ContentView: View {
@State var text = String()
var body: some View {
VStack {
Text(text)
PasteButton(supportedTypes: ["public.utf8-plain-text"], payloadAction: { array in
array.first!.loadDataRepresentation(forTypeIdentifier: "public.utf8-plain-text", completionHandler: {
(data, error) in
guard let data = data else {
return
}
let loadedText = String(decoding: data, as: UTF8.self)
self.text = loadedText
//This call just shows how to find print the UTI type of any type conforming to NSItemProviderWriting, "public.utf8-plain-text" in this case
self.getUTITypeString(for: loadedText)
})
})
}
.frame(width: 200, height: 200)
}
func getUTITypeString(for item: Any) {
if let item = item as? NSItemProviderWriting {
let provider = NSItemProvider(object: item)
print(provider)
}
else {
print("This data type cannot be used in an NSItemProvider")
}
}
}

다음 NSItemProviderWriting은에 따라 붙여 넣을 수 있는 형식 목록입니다 PasteButton.

비녀장

참조 : 토글 (2.0에서 업데이트 됨)

ToggleUISwitchUIKit 의 SwiftUI에 해당 합니다. IBActionSwift 코드를 UISwitchStoryboard에 연결 하고 값이 변경 될 때 실행 되는 함수 대신 SwiftUI는 바인딩을 사용합니다.

변수를 State(구조체 내) 또는 Published(에 부합하는 외부 클래스에서 ObservableObject)로 표시하지 않으면 SwiftUI는 View값이 변경 될 때의 내용을 다시 그리지 않습니다 .

이것은 PublishedSwiftUI가 해당 변수의 존재를 인식하는 유일한 방법이기 때문에 바인딩 프로세스의 필수적인 부분입니다. 특히 외부 코드를으로 표시 합니다.

import SwiftUI
final class DataModel: ObservableObject {
static let shared = DataModel()
@Published var toggleOn = false
}
struct ContentView: View {
@State var toggleOn = false
@ObservedObject var data = DataModel.shared
var body: some View {
VStack {
Toggle(isOn: $toggleOn) {
Text("@State Toggle: \(String(toggleOn))")
}
Toggle(isOn: $data.toggleOn) {
Text("@Published Toggle: \(String(data.toggleOn))")
}
}
.padding()
}
}

사용자 지정 ToggleStyle 만들기

에 대한 이니셜 라이저 Toggle가라는 구조체를 취할 수 있다는 것을 알아 차렸고 ToggleStyleConfiguration,이를 직접 구성하는 방법을 알아 내려고 잠시 시간을 보냈습니다.

내가 찾은 것은 SwiftUI Lab의 커스텀 스타일링에 대한 훌륭한 튜토리얼의 많은 도움으로 프로토콜이 ToggleStyle자신 만의 커스텀 스타일을 만드는 기능을 제공한다는 것입니다.

이를 수행 할 수있는 방법 중 일부는 다음 행입니다.

typealias ToggleStyle.Configuration = ToggleStyleConfiguration

typealiashere를 사용하는 것은보다 간결한 로컬 이름으로 구조체를 참조하는 방법 일뿐입니다. 이것은 너무 아마도 makeBody아래와 기능, 유사한 프로토콜과 같은 선언 서명을 할 수 있습니다 ButtonStylePickerStyle그리고 TextFieldStyle:

func makeBody(configuration: Self.Configuration) -> some View

대신 전달 된 레이블을 완전히 무시하고 토글 isOn상태 에 따라 변경되는 두 개의 동적 레이블을 제공하여 레이블 처리 방법을 변경하기로 결정했습니다 .

import SwiftUI
struct ContentView : View {
@State var toggleIsOn = false
var body: some View {
Toggle(isOn: $toggleIsOn) {
Text("This label will never be seen")
}
.toggleStyle(MyToggleStyle(stringWhenOff: "Disabled", stringWhenOn: "Enabled"))
Toggle(isOn: $toggleIsOn) {
Text("This label will never be seen")
}
.myToggleStyle(off: "Off", on: "On")
}
}
extension Toggle {
func myToggleStyle(off: String, on: String) -> some View {
self.toggleStyle(MyToggleStyle(stringWhenOff: off, stringWhenOn: on))
}
}
struct MyToggleStyle: ToggleStyle {
let stringWhenOff: String
let stringWhenOn: String
func makeBody(configuration: Self.Configuration) -> some View {
if configuration.isOn {
return Toggle(isOn: configuration.$isOn, label: {
Text(stringWhenOn)
})
}
else {
return Toggle(isOn: configuration.$isOn, label: {
Text(stringWhenOff)
})
}
}
}

여기에는 두 가지 예가 있지만 정확히 동일하게 보입니다. 하나는 .toggleStyle표준과 마찬가지로 수정자를 사용하여 다소 장황한 형식을 사용합니다 ToggleStyle.

다른 하나는 Toggle이 장황한 형식을 반환 하는 확장을 사용하여 깔끔한 호출 사이트를 제공하지만 표준 ToggleStyle모양 과 일치하지 않게됩니다.

당신이 선호하는 것은 당신에게 달려 있습니다. MyToggleStyle구조체에 지역 변수가 필요하지 않다는 것은 말할 필요도 없습니다.이 변수 가 없으면 생성자에 값을 전달할 필요가 없습니다.

사용자 지정 값을 전달하는 방법을 보여주기 위해서만이 작업을 수행했지만 makeBody함수 의 서명을 변경할 수는 없습니다 .

즉, 매개 변수 makeBody만 사용할 수 있습니다 Self.Configuration. 초기화되지 않은 변수로 구조체를 구성함으로써 isOn바인딩과 함께 생성자 Label로부터 값을 전달하는 또 다른 방법이 Toggle있습니다.

MyToggleStyle우리가 추가 configuration.label한 가치 인는 사용하지 않습니다 Text(“This label will never be seen”). 이 레이블이 Toggle없이 구성 될 수 있으므로이 레이블을 추가 할 필요는 없지만 사용자 정의 ToggleStyle가 원하는 것을 숨기는 방법을 지적 할 가치 가있었습니다.

makeBody반품 이후 some View원하는 것을 반환 할 수 있습니다. 당신은을 반환 할 수 TextButtonImage, 심지어 VStack, 난 당신이 그렇게 할 거라고 왜 아무 생각이 없지만.

이쑤시개

Pickers 의 Hacking With Swift 튜토리얼에서 언급했듯이 Picker내부 a 의 기본 동작은 Form옵션을 선택할 수있는 다른 곳으로 이동하는 것입니다.

iOS에서는을 Form안에 넣어야합니다 NavigationView. 그렇지 않으면이 탐색이 발생하지 않습니다. 외부의 Form의가 DefaultPickerStyle될 것입니다 WheelPickerStyle.

나는 또한 포함했다 SegmentedPickerStyle유사한 모양을 가지고있는 UISegmentedControl의를 UIKit.

import SwiftUI
struct ContentView : View {
var options = ["Option 1", "Option 2", "Option 3", "Option 4"]
@State private var selectedOption = 0
var body: some View {
NavigationView {
Form {
Picker(selection: $selectedOption, label: Text("Select a choice")) {
ForEach(0 ..< options.count) {
Text(self.options[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
Picker(selection: $selectedOption, label: Text("Select a choice")
.frame(minWidth: 100)) {
ForEach(0 ..< options.count) {
Text(self.options[$0])
}
}
.pickerStyle(WheelPickerStyle())
Picker(selection: $selectedOption, label: Text("Select a choice")) {
ForEach(0 ..< options.count) {
Text(self.options[$0])
}
}
.pickerStyle(DefaultPickerStyle())
}
}
}
}

날짜 선택기

참고 항목 : DatePicker (2.0에서 업데이트 됨)

DatePicker과 유사 Picker하지만 모든 스타일이 동일하지는 않습니다. 내부에서 사용하는 경우 Form는 DatePicker단지 한 줄을 차지합니다.

위의 스크린 샷에서 볼 수 있듯이, 기본 DatePickerA의는 Form레이블과 현재 날짜가 있습니다. 탭하면 a DatePicker가 아래로 미끄러 져 나옵니다.

DatePicker이 정확히 같은 체크 아웃 슬라이드 WheelDatePickerStyle실제로 난 그냥 때 지금 표시처럼 보이는 이유입니다, WheelDatePickerStyle아래를.

런타임에 Picker의 형식을 변경하는 방법을 보여주기 위해 다른 날짜 형식을 시도하는 데 사용할 수있는를 추가했습니다 DatePicker.

import SwiftUI
final class DataModel: ObservableObject {
static let shared = DataModel()
@Published var selectedData = Date()
@Published var dateFormatter = DateFormatter()
@Published var format = DateFormatter.Style.long
@Published var selectedOption = 0 {
didSet {
format = DateFormatter.Style(rawValue: UInt(selectedOption)) ?? .long
dateFormatter.dateStyle = format
}
}
init() {
dateFormatter.dateStyle = .long
}
}
struct ContentView : View {
@ObservedObject var data = DataModel.shared
var options = ["none", "short", "medium", "long", "full"]
var body: some View {
NavigationView {
Form {
Picker(selection: $data.selectedOption, label: Text("Select a format")) {
ForEach(0 ..< 5) {
Text(self.options[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
DatePicker(selection: $data.selectedData, in: ...Date(), displayedComponents: .date) {
Text("Select a date")
}
Text("Date is \(data.selectedData, formatter: data.dateFormatter)")
}
}
}
}

슬라이더

슬라이더를 사용하면 최소값과 최대 값 사이에서 엄지 손가락 (흰색 원)을 스 와이프 할 수 있습니다. 이것은 UISliderUIKit에서 와 유사합니다 . 생성 할 때 SwiftUI가 최소값과 최대 값이 무엇인지 알 수 있도록 닫힌 범위를 설정해야합니다.

단계는 임의의 금액으로 설정할 수 있으므로 값이 소수 일 필요가없는 경우 long Float을 로 변환 Int할 필요가 없습니다.

또한 슬라이더 위치가 기록되는 정확도의 양을 늘리거나 줄이는 데 도움이되며, 지정한 단계 양을 지나는 소수 자릿수를 제외하여 계산을 더 쉽게 할 수 있습니다.

import SwiftUI
final class DataModel: ObservableObject {
static let shared = DataModel()
@Published var sliderValue = Double()
}
struct ContentView: View {
@State var sliderValue = Double()
@ObservedObject var data = DataModel.shared
var body: some View {
VStack {
Slider(value: $sliderValue, in: 0...100, step: 0.1) {
_ in
}
Slider(value: $sliderValue, in: 0...100, onEditingChanged: { sliderActive in
}) {
Text("Slider")
}
Slider(value: $sliderValue, in: 0...100, onEditingChanged: { sliderActive in
}, minimumValueLabel: Text("Min"), maximumValueLabel: Text("Max")) {
Text("Slider")
}
Slider(value: $sliderValue, in: 0...100, step: 0.1)
{_ in
print("Value changed")
}
Slider(value: $sliderValue, in: 0...100, step: 0.1, onEditingChanged: { sliderActive in
print("Value changed")
}) {
Text("Slider")
}
Slider(value: $sliderValue, in: 0...100, step: 0.1, onEditingChanged: { sliderActive in
}, minimumValueLabel: Text("Min"), maximumValueLabel: Text("Max")) {
Text("Slider")
}
}
.padding()
}
}

스테퍼

StepperSwiftUI의 A 는 기본적으로 UIStepperin UIKit. 연결된 마이너스 및 플러스 버튼으로 구성됩니다.

모든 이니셜 라이저가 값을 저장하기 위해 바인딩 변수를 설정해야하는 것은 아닙니다. 대부분은 .NET Framework의 값을 감소, 증가 또는 편집 할 때 호출되는 클로저를 사용합니다 Stepper.

import SwiftUI
final class DataModel: ObservableObject {
static let shared = DataModel()
@Published var stepperValue = Double()
}
struct ContentView: View {
@State var stepperValue = Double()
@ObservedObject var data = DataModel.shared
var body: some View {
VStack {
Stepper("localized_string_key", onIncrement: {}, onDecrement: {}, onEditingChanged: { editingActive in
})
Stepper("Stepper", onIncrement: {}, onDecrement: {}, onEditingChanged: {
editingActive in
})
Stepper("localized_string_key", value: $stepperValue, in: 0...100, step: 1, onEditingChanged: {editingActive in })
Stepper("Stepper", value: $data.stepperValue, in: 0...100, step: 1, onEditingChanged: { editingActive in
})
Stepper("localized_string_key", value: $stepperValue, step: 1, onEditingChanged: { editingActive in
})
Stepper("Stepper", value: $data.stepperValue, step: 1, onEditingChanged: { editingActive in
})
Stepper(onIncrement: {}, onDecrement: {}, onEditingChanged: {
editingActive in
}) {
Text("Stepper label")
}
Stepper(value: $stepperValue, step: 1, onEditingChanged: {
editingActive in
}) {
Text("Stepper label")
}
}
}
}

레이아웃 및 프레젠테이션보기

VStack은 수직이고, HStack은 수평이며, ZStack은 최대 10 개의 뷰로 구성된 적층 스택입니다.

HStack, VStack 및 ZStack

항상 세로로 작성되지만 이러한 스택은 자녀를 다른 방향으로 정렬합니다.

VStack 최대 10 명의 자녀 (및 모든 자손)로 전화 화면을 빠르게 채울 수 있으므로 모든 앱에 유용한 시작점입니다.

HStack사용 가능한 가로 공간을 사용하여 세로 방향 전화 화면에 많은 공간을 허용하지 않을 수있는 자식을 레이아웃합니다. 이것은 TextList(아래 참조) 와 같이 컨트롤 옆에 레이블 을 배치하려는 경우에 유용합니다 .

List, ScrollView, ForEach 및 DynamicViewContent

참고 항목 : 목록 (2.0에서 업데이트 됨)
참고 항목 : ForEach 및 DynamicViewContent (2.0에서 업데이트 됨)

에 대한 예제에서 언급했듯이 NavigationLinkList는 동적 행 수를 수용하기 위해 수직으로 확장되는 스크롤보기입니다. UITableViewin UIKit과 유사 하지만 작업이 없습니다.

List와 거의 같은 방식으로에 정적 데이터를 추가하여 다른 데이터 VStack위에 배치 View하거나 ForEach.

ForEach 배열과 같은 컬렉션을 반복하고 매번 표준화 된 방식으로 방대한 양의 데이터를 표시 할 수 있습니다.

ScrollView스크롤이 가능 VStack하거나 HStack그 안에 포함되어 있습니다. 기본값은 ScrollView의 직접적인 아이가 있더라도, 수직 스크롤 ScrollView입니다 HStack.

즉, ScrollView(.horizontal)이 동작을 재정의하려는 경우 사용해야 합니다. 를 사용하는 ForEach것처럼 계속 사용할 수 List있지만, VStack또는 의 추가 레이어를 HStack사용하면 더 복잡한 방법이됩니다.

VStacks물론에는의 UITableView셀 과 모양이 비슷한 행이 없습니다 UIKit. 예를 들어 List로 구성된 A 는 구분선없이 서로 겹쳐 Text쌓 Texts입니다.

View이러한 행을 모방하거나 행에 완전히 다른 모양을 제공 하는 사용자 지정을 만들 수 있습니다 .

그러나 List가로 스크롤이 필요하지 않으면 사용하는 것이 가장 좋습니다 .

List또한 사용자 정의 행을 지원 ScrollView하며 a VStack가 부족한 다른 기능 이 있습니다.

이 때 EditButtonA와 추가 된 View를 포함하는 List, 당신은 재 배열 또는에서 삭제 항목 수 있습니다 List.

이 상황에서 호출되는 메서드가 없으면 행 List이 사라지지만 그 뒤에있는 데이터는 영향을받지 않습니다. 다음 번에 스 와이프하여 행을 삭제 한 후 앱을 시작하면 기본 데이터가 수정되지 않았기 때문에 해당 행이 반환됩니다.

에서 해킹으로 스위프트의 onDelete튜토리얼 , 당신은 어떻게 볼 수있는 .onDelete수정이 작동합니다. 이렇게하면 사용자가 .NET Framework에서 항목을 삭제하기 위해 스 와이프 할 때 실행되는 메서드를 전달할 수 있습니다 List.

DynamicViewContent.onDelete수정 자의 반환 유형 이지만 ForEach콘텐츠를 업데이트해야 한다는 의미입니다 .

ForEach다른 View구조체는 기본 데이터가 변경 될 때 동적으로 변경 될 수 있음을 의미합니다.