당신이 기다려온 완전한 SwiftUI 문서
Apple의 문서를 넘어서십시오. 코드 예제, 제안 된 모범 사례 및 모든보기, 컨트롤, 레이아웃 등에 대한 설명!
이 문서는 Medium 섹션 링크 제한으로 인해 Chrome에서 가장 잘 볼 수 있습니다.
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
프로토콜 을 따르지만 View
Apple이 제공 한 기본 클래스에서 상속하지 않습니다 .
이것은 UIView
UIKit의 거의 모든 것이 상속되는. A는 UIView
기본적으로 프레임이 할당되지 않고 UIViewController
서브 클래스 의 서브 뷰로 추가되지 않으면 볼 수 없습니다 .
View
이라고 ContentView
.ContentView
구조체 안에 body라는 변수가 있음을 알 수 있습니다 . 이것은 View
프로토콜 의 유일한 요구 사항이며 some
Swift 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") | |
} | |
} | |
} |
참고 마지막 초기화하는 데 걸리는 하나 LocalizedStringKey
, tableName
, bundle
,과 comment
는 사용하는 별도의 파일이 필요합니다 .strings
파일 확장자를.
이 initializer에 대한 Apple의 문서 에서 언급했듯이 유일한 필수 매개 변수는 키의 문자열입니다. 이러한 다른 매개 변수가 무엇을 요구하는지 알 수 있도록 대부분 장황한 예제를 제공했습니다.
의 기본 tableName
IS Localizable
하는 문자열 파일의 표준 이름. Local
이 매개 변수가 필요한 이유를 보여주기 위해 일부러 내 이름을 지정했습니다 .
번들은 기본적으로 기본 번들이므로이 Bundle.main
경우 전달 은 중복됩니다.
주석은 상황에 맞는 정보를 제공해야하지만이 예제에서는 문자열 Comment
.
Text
들 여기에 포함 된 VStack
몸 변수의 불투명 결과 유형이 하나 개의 유형으로 설정해야하기 때문이다. 즉, 여러 유형이 포함될 수 있으므로 여러 항목으로 설정할 수 없습니다.
VStack
내부에 최대 10 개의 뷰를 포함 할 수 있지만 각 뷰는 , 또는 VStack
일 수 있으며 각각 내부에 10 개의 유형을 가질 수 있습니다.HStack
Group
이에 대한 자세한 내용을 보려면 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
의 Text
s가 고정 된 최대 크기를 갖기 때문에 이것의 경계 가 제한 되어 있음을 보여줍니다 .
이것이 없으면 컨테이너가 s 내부 를 수용하도록 확장 되므로 Text
s에 대한 정렬은 효과가 없습니다 .VStack
Text
선행 (왼쪽) 또는 후행 (오른쪽) 가장자리에 맞추려면 선행 또는 후행 가장자리가있을 위치를 정의해야합니다. 이것은 또한 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
사용자가 텍스트를 입력하도록하려면 해당 데이터를 저장하기위한 바인딩이 필요합니다. 이 첫 번째 예제는 State
SwiftUI 구조체에 문자열을 로컬로 저장하고 실제로 사용하거나 영구적으로 저장할 수있는 곳에 저장하지 않는 변수를 사용합니다.
데이터를 유지하고 계산 된 값을 제공 할 수 있으려면 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
추상적 인 인터페이스, 어떤에서 UIView
, UIViewController
그리고 UIKit 상속의 다른 기본적으로 모든.
SwiftUI 이벤트가 동일한 정도로 처리되는 방식은 알 수 없지만 .NET 을 사용한 모든 사람에게 익숙한 첫 번째 응답자 라는 문구 를 사용하고 UITextField
있습니다.
bool in onEditingChanged
을 호출 fieldActive
하거나 명확하게 표시 할 수있는 다른 항목을 호출 할 수 있습니다 .
중요한 것은 당신이 편집 A를 시작할 때이다 TextField
, onEditingChanged
true로 설정되는 부울 호출됩니다. 키보드의 리턴 키를 누르면 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
표시 할 때 기본값이 아닌 다른 키보드 유형을 사용할 수 없습니다 .TextField
List
TextField
s 를 표시하려고 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
전경색 또는 배경색을 변경하고 테두리를 추가하고 다른 TextFieldStyle
s를 사용할 수 있지만 현재 자리 표시 자 텍스트의 전경색을 변경할 수 없습니다.
폰트
Apple의 문서에 대해 자세히 설명 할 수 없으므로 Font
Apple의 표준 글꼴과 동일한 방식으로 사용자 지정 글꼴을 사용하는 간단한 방법을 제공했습니다.
/* | |
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.image
, Image
는 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에서 업데이트 됨)
A Button
는 자신의 모습이 없습니다. 즉, 을 준수하는 구체적인 유형 인 Button
a 를 제공해야 합니다 .Label
View
가장 확실한 예는 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
가 필요 합니다.Label
View
대부분의 경우 이는 Text
또는 Image
일 수 있지만 사용자가 만든 사용자 정의보기 일 수도 있습니다.
View
아이폰의 오른쪽, 각각의 연속에서 귀하의 링크 슬라이드를 대상으로 NavigationLink
같은 방법으로 슬라이드. 초기으로 돌아 가면 View
왼쪽 가장자리에서 스 와이프하거나 탐색 메뉴의 왼쪽 상단에있는 뒤로 버튼을 사용할 수 있습니다.
import SwiftUI | |
struct ContentView: View { | |
var body: some View { | |
NavigationView { | |
//Example 1: Using a sequence from 0...49 | |
List { | |
ForEach((0...49), id: \.self) { | |
index in | |
NavigationLink(destination: DogView(index: index)) { | |
Image("full\(index)") | |
.resizable() | |
.scaledToFit() | |
} | |
} | |
} | |
} | |
} | |
} | |
struct DogView: View { | |
let index: Int | |
var body : some View { | |
Image("dog\(index)") | |
} | |
} | |
struct ContentView2: View { | |
let array = ["full0", "full1", "full2", "full3", "full4", "full5", "full6", "full7", "full8", "full9", "full10", "full11", "full12", "full13", "full14", "full15", "full16", "full17", "full18", "full19", "full20", "full21", "full22", "full23", "full24", "full25", "full26", "full27", "full28", "full29", "full30", "full31", "full32", "full33", "full34", "full35", "full36", "full37", "full38", "full39", "full40", "full41", "full42", "full43", "full44", "full45", "full46", "full47", "full48", "full49"] | |
var body: some View { | |
NavigationView { | |
//Example 2: Using an array | |
List(array, id: \.self) { imageName in | |
NavigationLink(destination: DogView(index: Int(self.array.firstIndex(of: imageName) ?? 0))) { | |
Image(imageName) | |
.resizable() | |
.scaledToFit() | |
} | |
} | |
} | |
} | |
} |
이 예제는 내 시계 앱 Dog HQ에서 가져온 것입니다. 전체 크기의 강아지 사진 스크롤 목록을 표시하며 각 사진은 확대 된 버전으로 연결됩니다.
이것이 DogView
내가 어떤 개가 목적지가되고 싶은지 알 수 있도록 인덱스를 확대 된 생성자에게 전달해야하는 이유 입니다.
List
세로로 스크롤하고 원하는 크기로 확장하는를 결합하면 ForEach
50 개의 행을 만들고 지정한 이름으로 해당 인덱스를 클로저에 전달할 수 있습니다.
에 대한 반복 ForEach
은 배열의 최대 항목 수를 갖는 시퀀스 일 수 있거나 배열이 a에 대한 생성자에 전달되고 List
지정한 이름으로 클로저 내부에서 액세스 될 수 있습니다 .
분명히 저는이 배열로 표면을 긁고 있습니다. 배열에는라는 문자열 속성이있는 사용자 정의 클래스 imageName
, 숫자 값 또는 점 구문을 사용하여 액세스 할 수있는 다른 클래스의 인스턴스 와 같은 복잡한 유형이 포함될 수 있습니다 .
화면 상단의 탐색 모음에는 선행 및 후행 단추가 포함될 수 있습니다. 이것의 주요 용도 EditButton
는 아래에서 자세히 설명 하는를 추가하는 것 같습니다.
EditButton
편집 버튼은 List
항목 이 여러 개 있고 일부 항목을 삭제할 수 있도록하려는 경우 매우 유용 합니다. 탭하면 편집 모드 (당연히)로 이동하여 각 행에 수평선이있는 빨간색 원이 표시됩니다.
누르면 편집하는 것은 최종 확인의 역할을 오른쪽 끝의 삭제 버튼을 공개, 왼쪽에 행을 밀어 것입니다.
목록에서 데이터 삭제를 처리하는 함수를 구현해야합니다. 그렇지 않으면 변경 사항이 시각적으로 만 표시되고 데이터가 실제로 예상대로 삭제되지 않습니다.
사용에 대한 자세한 내용 EditButton
은 List, ScrollView, ForEach 및 DynamicViewContent를 참조하십시오 .
MenuButton
MenuButton
macOS 앱에서만 사용할 수 있으므로 모든 표준 .menuButtonStyle
옵션 을 사용하는 Mac 앱 예제를 제공했습니다 .
왼쪽에서 오른쪽으로, 이러한 스타일은 BorderlessButtonMenuButtonStyle
, BorderlessPullDownMenuButtonStyle
하고 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
.
CNContact
CNMutableContact
CSLocalizedString
MKMapItem
NSAttributedString
NSMutableString
NSString
NSTextStorage
NSURL
NSUserActivity
UIColor
UIImage
비녀장
참조 : 토글 (2.0에서 업데이트 됨)
Toggle
UISwitch
UIKit 의 SwiftUI에 해당 합니다. IBAction
Swift 코드를 UISwitch
Storyboard에 연결 하고 값이 변경 될 때 실행 되는 함수 대신 SwiftUI는 바인딩을 사용합니다.
변수를 State
(구조체 내) 또는 Published
(에 부합하는 외부 클래스에서 ObservableObject
)로 표시하지 않으면 SwiftUI는 View
값이 변경 될 때의 내용을 다시 그리지 않습니다 .
이것은 Published
SwiftUI가 해당 변수의 존재를 인식하는 유일한 방법이기 때문에 바인딩 프로세스의 필수적인 부분입니다. 특히 외부 코드를으로 표시 합니다.
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
typealias
here를 사용하는 것은보다 간결한 로컬 이름으로 구조체를 참조하는 방법 일뿐입니다. 이것은 너무 아마도 makeBody
아래와 기능, 유사한 프로토콜과 같은 선언 서명을 할 수 있습니다 ButtonStyle
, PickerStyle
그리고 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
원하는 것을 반환 할 수 있습니다. 당신은을 반환 할 수 Text
, Button
, Image
, 심지어 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
단지 한 줄을 차지합니다.
위의 스크린 샷에서 볼 수 있듯이, 기본 DatePicker
A의는 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)") | |
} | |
} | |
} | |
} | |
슬라이더
슬라이더를 사용하면 최소값과 최대 값 사이에서 엄지 손가락 (흰색 원)을 스 와이프 할 수 있습니다. 이것은 UISlider
UIKit에서 와 유사합니다 . 생성 할 때 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() | |
} | |
} |
스테퍼
Stepper
SwiftUI의 A 는 기본적으로 UIStepper
in 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") | |
} | |
} | |
} | |
} |
레이아웃 및 프레젠테이션보기
HStack, VStack 및 ZStack
항상 세로로 작성되지만 이러한 스택은 자녀를 다른 방향으로 정렬합니다.
VStack
최대 10 명의 자녀 (및 모든 자손)로 전화 화면을 빠르게 채울 수 있으므로 모든 앱에 유용한 시작점입니다.
HStack
사용 가능한 가로 공간을 사용하여 세로 방향 전화 화면에 많은 공간을 허용하지 않을 수있는 자식을 레이아웃합니다. 이것은 Text
a List
(아래 참조) 와 같이 컨트롤 옆에 레이블 을 배치하려는 경우에 유용합니다 .
List, ScrollView, ForEach 및 DynamicViewContent
참고 항목 : 목록 (2.0에서 업데이트 됨)
참고 항목 : ForEach 및 DynamicViewContent (2.0에서 업데이트 됨)
에 대한 예제에서 언급했듯이 NavigationLink
a List
는 동적 행 수를 수용하기 위해 수직으로 확장되는 스크롤보기입니다. UITableView
in 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
가 부족한 다른 기능 이 있습니다.
이 때 EditButton
A와 추가 된 View
를 포함하는 List
, 당신은 재 배열 또는에서 삭제 항목 수 있습니다 List
.
이 상황에서 호출되는 메서드가 없으면 행 List
이 사라지지만 그 뒤에있는 데이터는 영향을받지 않습니다. 다음 번에 스 와이프하여 행을 삭제 한 후 앱을 시작하면 기본 데이터가 수정되지 않았기 때문에 해당 행이 반환됩니다.
에서 해킹으로 스위프트의 onDelete
튜토리얼 , 당신은 어떻게 볼 수있는 .onDelete
수정이 작동합니다. 이렇게하면 사용자가 .NET Framework에서 항목을 삭제하기 위해 스 와이프 할 때 실행되는 메서드를 전달할 수 있습니다 List
.
DynamicViewContent
.onDelete
수정 자의 반환 유형 이지만 ForEach
콘텐츠를 업데이트해야 한다는 의미입니다 .
ForEach
다른 View
구조체는 기본 데이터가 변경 될 때 동적으로 변경 될 수 있음을 의미합니다.