2021년 1월 2일 토요일

뷰와 컨트롤 — 여러분이 기다려온 SwiftUI 2 문서

 

뷰와 컨트롤 — 여러분이 기다려온 SwiftUI 2 문서 

iOS 14, iPadOS 14, WatchOS 7 및 MacOS Big Sur 용으로 업데이트 됨 

Pixabay로부터 입수 된 Albrecht Fietz의 일부 뷰 및 컨트롤 사진

2020 년 초, 저는 The Complete SwiftUI Documentation You'veen Waiting For 라는 긴 Medium 게시물을 썼습니다 .

이것이 애플이 제공하는 불충분 한 문서로 인해 남겨진 틈을 메우려 고 할 때 배운 것을 공유하는 방법이었습니다. 내 게시물이 많은 사람들에게 도움이되는 것 같았지만 6 개월 늦게 썼습니다.

이제 Apple의 2020 개발자 컨퍼런스가 끝났으므로 SwiftUI에 몇 가지 새로운 기능이 제공되었으므로이 업데이트가 내 문서가 그 어느 때보 다 도움이되기를 바랍니다.

이것은 포스트 당 하나의 챕터로 시리즈로 출시 될 것입니다.

이 장의 이름은 Apple의 SwiftUI 문서에있는 장 이름과 일치합니다.

나는 그들 중 누구도 이것만큼 길지 않을 것이라고 보장 할 수 있습니다.

The View Protocol 
@ViewBuilder
New and Updated Views
ColorPicker (NEW in 2.0)
SpriteView (NEW in 2.0)
TextEditor (NEW in 2.0)
SignInWithAppleButton (NEW in 2.0)
ProgressView (NEW in 2.0)
GaugeView (NEW in 2.0)
Label (NEW in 2.0)
Link (NEW in 2.0)
Menu (NEW in 2.0)
MenuButton (Deprecated in 2.0)
Text (Updated in 2.0)
Image (Updated in 2.0)
Button (Updated in 2.0)
PasteButton (Updated in 2.0)
Toggle (Updated in 2.0)
DatePicker (Updated in 2.0)
New and Updated View Modifiers
.matchedGeometryEffect (NEW in 2.0)
.help (NEW in 2.0)
.accessibility(inputLabels:) (NEW in 2.0)
.accessibility(selectionIdentifier:) (Deprecated in 2.0)
.scaleEffect (Updated in 2.0)
.imageScale (NEW in 2.0)
.accentColor (Updated in 2.0)
.preferredColorScheme (Updated in 2.0)
.textContentType (NEW in 2.0)
.listItemTint (NEW in 2.0)
.listRowPlatterColor (Deprecated in 2.0)
.onLongPressGesture (Updated in 2.0)
.onOpenURL (NEW in 2.0)
.onPasteCommand (NEW in 2.0)
.onDrag and .onDrop (Updated in 2.0)
.onChange (NEW in 2.0)
.keyboardShortcut (NEW in 2.0)
.focusedValue and @FocusedBinding (NEW in 2.0)
.prefersDefaultFocus and .focusScope (NEW in 2.0)
.fullScreenCover (NEW in 2.0)
.defaultAppStorage (NEW in 2.0)
.appStoreOverlay (NEW in 2.0)
.toolbar (NEW in 2.0)
.previewContext (NEW in 2.0)
.userActivity, .onContinueUserActivity (NEW in 2.0)
.tabItem (Updated in 2.0)
.contextMenu (Updated in 2.0)
.navigationTitle and .navigationSubtitle (NEW in 2.0)
.navigationViewStyle (Updated in 2.0)
.navigationBarTitle (Deprecated in 2.0)
.navigationBarItems (Deprecated in 2.0)
Styles on iOS, iPadOS, Mac Catalyst and tvOS (NEW in 2.0)
Styles Only on macOS (NEW in 2.0)
Next Steps

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

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

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

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

당신이 사용은 사용자 인터페이스의 기초로 대신 스토리 보드의 SwiftUI하는 새로운 Xcode 프로젝트를 생성하면 자동으로 SwiftUI의 예를 제공 할 것 View이라고 ContentView.

ContentView구조체 안에 body라는 변수가 있음을 알 수 있습니다 . 이것은 View프로토콜 의 유일한 요구 사항이며 someSwift 5.1의 새로운 키워드를 사용합니다 .

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

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

@ViewBuilder

이것은 여러 뷰에서 단일 뷰를 구성 할 수있는 일종의 함수 빌더입니다. 이 속성을 View 본문 위에 추가하고 cmd- 클릭하고 정의로 이동을 선택하면 흥미로운 내용이 많이 표시됩니다. 아마도 가장 중요한 부분은 다음과 같습니다.

public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View

Xcode 12에서 body 속성은 .

이것이 왜 중요합니까? 이제 body 속성에 직접 자식으로 최대 10 개의 뷰를 넣을 수 있습니다. 이전에는 뷰 를 ,, 또는 의 레이아웃에 영향을주지 않고의 Group이점을 얻을 수있는 방법 인 . 이는 새 버전의 SwiftUI에서 더 이상 필요하지 않으며 그 결과 의 사용 이 더 틈새 시장이 될 것입니다.@ViewBuilderVStackHStackZStackGroup

뷰 를 사용하려고 할 때 body 속성에 직접 배치하는 것은 모호하므로 여전히 뷰를 VStack또는 HStack에 배치해야합니다.

수정 자보기

2020 년에 새로 추가 된보기에 대해 알아보기 전에보기 수정자가 무엇인지 다시 살펴 보겠습니다. 새로운 뷰와 함께 표시되므로 나중에 설명하기 위해 기다리는 것은 의미가 없습니다.

모든 뷰는 ViewModifier 프로토콜을 준수하는 구조체로 수정할 수 있습니다. 모든 프로토콜에 필요한 것은 일반 뷰를 반환하는 body (content : Content)라는 함수입니다. View를 직접 만들 수 없기 때문에 수정자가 호출 될 때까지 ViewModifier에 전달하는 유형을 알 수 없습니다. 콘텐츠는 구체적인 유형에 대한 프록시 역할을합니다. 뷰의 본문과 같은 반환 유형은 구현에서 유추됩니다.

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

import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Blue")
.foregroundColor(.blue)
.padding(10)
Text("Blue")
.modifier(BluePaddingModifier(padding: 10))
Text("Blue")
.blue(10)
Text("Blue")
.b(10)
}
}
}
struct BluePaddingModifier: ViewModifier {
let padding: CGFloat
func body(content: Content) -> some View {
content
.foregroundColor(.blue)
.padding(padding)
}
}
extension View {
func blue(_ padding: CGFloat) -> some View {
let modifier = BluePaddingModifier(padding: padding)
return ModifiedContent(content: self, modifier: modifier)
}
func b(_ padding: CGFloat) -> some View {
self.modifier(BluePaddingModifier(padding: padding))
}
}

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

첫 번째 방법 인 blue(_:)은 기본적으로하는 일을 .modifier(YourModifier())합니다. 구성하여 ModifiedContent, 우리는 결국 View수식을 가지고 그 적용했다. 그러나 동일한 결과를 얻기 위해 인스턴스 메소드 .modifier(YourModifier())를 호출하여이를 덜 복잡하게 만들 수 View있습니다.

두 번째 메서드 b(_:)인는 필요할 .modifier(YourModifier())때마다 호출 할 필요가 없습니다. 이것이 표준 수정자가 보이는 방식이며 View.

분명히 b(_:)속성, 메서드 또는에 대한 꽤 끔찍한 이름 ViewModifier이지만 식별자를 점차 짧게 만들어 가장 간단한 식별자를 나타냅니다.

먼저 2020 년에 완전히 새로 워진 모든 뷰를 볼 수 있고, 그다음에 올해 업데이트 된 2019 년 뷰를 볼 수 있습니다.

그런 다음 마지막에 어떤 View Modifier가 새로 추가되거나 업데이트되었는지 확인합니다.

2.0의 새로운 기능 : ColorPicker

iOS 개발자를위한 색상 선택기가 포함 된 적이 없습니다. 이전에 타사 제품을 사용해 보았지만 종속성으로 인해 향후 모든 프로젝트 및 배포 대상과의 호환성을 보장하기 위해 다른 개발자에게 의존하게됩니다. WWDC 전 마지막 몇 주 동안 마침내 SwiftUI에서 생각할 수있는 모든 종류의 색상 선택기 컨트롤을 만들기로 결정했습니다. 이를 통해 이러한 컨트롤 의 Swift Package에서 색상 선택기  만들고 모든 프로젝트에 대한 색상 선택기를 만들 수 있습니다.

그런 다음 WWDC가 나왔고 이제 iOS 14 용 ColorPicker가 있습니다.이 새로운 컨트롤은 매우 유능 해 보이며 어디에서나 색상을 선택할 수있는 스포이드와 같은 기능이 있습니다. 스포이드를 사용하여 ColorPicker의 UI 자체에서 색상을 선택할 수도 있으므로 이것이 무료로 제공되는 강력한 새 기능이라는 것이 분명해 보입니다. 하지만 제가 해결할 수있는 한, 새로운 색상 선택기에는 변경할 수없는 매우 엄격한 컨트롤 세트가 있습니다. 공식 문서가 아직 업데이트되지 않은 경우 ColorPicker가 제공하는 내용을 캔버스, 팔레트 또는 슬라이더로만 제한하는 것과 같이 변경할 수있는 방법이없는 것처럼 보입니다.

대신이 세 가지 옵션은 사용자가 세그먼트 선택기로 선택합니다.

struct ColourPickerView: View {
@State private var bgColor =
Color(.sRGB, red: 0.98, green: 0.9, blue: 0.2)
var body: some View {
VStack {
ColorPicker("Alignment Guides", selection: $bgColor)
.frame(width: 500, height: 1000)
}
}
}
view rawColorPicker.swift hosted with ❤ by GitHub

ColorPicker가 앱에서 어떻게 보이는지에 대한 유용한 스크린 샷 및 애니메이션 GIF는 Using a ColorPicker with SwiftUI를 확인하십시오 .

2.0의 새로운 기능 : SpriteView

SpriteKit은 2D 게임 제작을위한 Apple의 프레임 워크입니다. 스프라이트는 무엇보다도 플레이어, 적, 발사체를 나타내는 데 사용되는 작은 비트 맵입니다. 게임에서 한 번에 화면에 많은 스프라이트가있을 수 있으므로 성능을 염두에두고이를 수행하도록 설계된 도구를 사용하는 것이 좋습니다. 이제 SpriteKit에서를 표시하는 SwiftUI 뷰 SKScene를 생성하여 게임을 생성 한 다음 해당 게임을 SwiftUI 뷰를 배치 할 위치에 둘 수 있습니다.

내 예에서는 오른쪽에서 오는 적에게 발사체를 발사하는 간단한 사각형 스프라이트가 있습니다. 그들이 왼쪽 끝까지 가면 당신은 생명을 잃습니다. 그중 하나를 쓰러 뜨리면 점수가 올라갑니다.

나중에 만들 SpriteKit 씬이 필요한 SwiftUI를 먼저 살펴 보겠습니다.

import SwiftUI
import SpriteKit
class GameModel: ObservableObject {
static let startLives = 5
static let startScore = 0
static let shared = GameModel()
@Published var lives = startLives
@Published var score = startScore
@Published var gameOver = false
@Published var restartGame = false {
didSet {
if restartGame {
gameOver = false
lives = GameModel.startLives
score = GameModel.startScore
}
}
}
}
struct ContentView: View {
@ObservedObject var data = GameModel.shared
@State var scene = GameScene(size: CGSize(width: 300, height: 400))
var body: some View {
VStack {
HUDView(data: data)
SpriteView(scene: self.scene)
.onChange(of: data.restartGame) { shouldRestart in
data.restartGame = false
scene.restart()
}
}
}
}
struct HUDView: View {
@ObservedObject var data: GameModel
@AppStorage("highScore") var highScore = 0
var body: some View {
HStack {
Text("Score: \(data.score)")
Text("High score: \(highScore)")
if self.data.gameOver {
Button("Restart") {
if data.score > highScore {
highScore = data.score
}
data.restartGame = true
}
}
Spacer()
Text("Lives: \(data.lives)")
}
}
}

데이터를 저장 하는 ObservableObject호출 GameModel ContentView게임을 표시 하는 구조체가 있습니다. 의 상단에는 현재 점수, 우리의 생명 수, 이전에 기록 된 최고 점수를 알려주는를 VStack표시합니다 HUDView. 수명이 다하면 다시 시작 버튼이 나타납니다. 이 모든 작업은 에서 new modifier로 관찰 @Published 중인 GameModel객체 의 속성을 변경하는 것 .onChange입니다 ContentView.

기본적으로 우리는 언제 restartGame가 참인지 말하고 GameScene, 게임을 처음부터 일시 중지를 해제하고 다시로드해야한다는 메시지를 우리에게 보내고 싶습니다 .

높은 점수는 편리한 방법으로 @AppStorage데이터를 저장 하는 속성 래퍼를 사용하여 기록됩니다 UserDefaults. 이 문서의 다른 장에서 새 속성 래퍼에 대한 자세한 정보를 제공 할 것이지만 중요한 것은이 래퍼가있는 속성이 지속적으로 저장되고 다음에 앱이로드 될 때 쉽게 불러올 수 있다는 것입니다. 플레이어가 생명이 없을 때 게임 오버 상태도 있습니다.이 상태는 플레이어가 게임을 다시 시작하기 위해 재시작 버튼을 탭해야합니다. 이것은 점수를 재설정하고, 적을 제거하고, 게임 시작과 마찬가지로 적의 스폰을 다시 시작합니다.

다음은 SpriteKit 코드입니다.

import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
static let projectileSize = CGFloat(10)
static let playerSize = CGFloat(15)
static let enemySize = CGFloat(30)
let data = GameModel.shared
var playerPosition: CGPoint {
CGPoint(x: size.width * 0.1, y: size.height * 0.5)
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
startGame()
}
func startGame() {
addWalls()
addPlayer()
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(addEnemy), SKAction.wait(forDuration: 1.0)])))
}
func restart() {
removeAllChildren()
self.scene?.view?.isPaused = false
startGame()
}
func addPlayer() {
let player = SKSpriteNode(color: UIColor.red, size: CGSize(width: Self.playerSize, height: Self.playerSize))
player.position = playerPosition
addChild(player)
}
func addWalls() {
let wallFrames = [
CGRect(x: 0, y: frame.midY, width: 10, height: size.height),
CGRect(x: size.width, y: frame.midY, width: 10, height: size.height),
CGRect(x: frame.midX, y: size.height, width: size.width, height: 10)
]
wallFrames.forEach {
let wall = SKSpriteNode(color: UIColor.orange, size: $0.size)
wall.position = CGPoint(x: $0.minX, y: $0.minY)
wall.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: wall.size.width, height: wall.size.height))
wall.zPosition = 1
wall.physicsBody?.isDynamic = false
addChild(wall)
}
}
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else { return }
if [nodeA.name, nodeB.name].contains("projectile") && [nodeA.name, nodeB.name].contains("enemy") {
[nodeA, nodeB].forEach {
$0.removeAllActions()
$0.name = "projectile"
$0.physicsBody?.affectedByGravity = true
$0.run(SKAction.sequence([SKAction.wait(forDuration: 2), SKAction.removeFromParent()]) )
}
self.data.score += 1
}
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 4294967296)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func addEnemy() {
let enemy = SKSpriteNode(color: UIColor.blue, size: CGSize(width: Self.enemySize, height: Self.enemySize))
let actualY = random(min: Self.enemySize / 2, max: size.height - Self.enemySize / 2)
enemy.position = CGPoint(x: size.width + Self.enemySize, y: actualY)
enemy.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: Self.enemySize, height: Self.enemySize))
guard let physicsBody = enemy.physicsBody else {
return
}
physicsBody.affectedByGravity = false
physicsBody.contactTestBitMask = physicsBody.collisionBitMask
enemy.name = "enemy"
addChild(enemy)
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
let actionMove = SKAction.move(to: CGPoint(x: -enemy.size.width/2, y: actualY),
duration: TimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
enemy.run(SKAction.sequence([actionMove, actionMoveDone]), completion: {
if self.data.lives > 0 {
self.data.lives -= 1
}
if self.data.lives <= 0 {
self.data.gameOver = true
self.scene?.view?.isPaused = self.data.gameOver
}
})
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touchLocation = touches.first?.location(in: self) else {
return
}
let projectile = SKShapeNode(circleOfRadius: Self.projectileSize)
projectile.fillColor = UIColor.green
projectile.position = playerPosition
projectile.zPosition = 1
projectile.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: Self.projectileSize, height: Self.projectileSize))
projectile.name = "projectile"
guard let physicsBody = projectile.physicsBody else {
return
}
physicsBody.contactTestBitMask = physicsBody.collisionBitMask
addChild(projectile)
physicsBody.applyImpulse(CGVector(dx: (touchLocation.x - playerPosition.x) / 50, dy: (touchLocation.y - playerPosition.y) / 50))
}
}

Swift로 작성된 SpriteKit 게임이므로 게임의 논리에 대해 너무 걱정하지 마십시오. SpriteKit에 대해 잘 모르는 경우에는 분명히 알지 못하지만 시작하는 데 도움이되는 많은 자습서가 있습니다.

알아야 할 중요한 점 SpriteView은 SwiftUI에 2D 게임을 삽입하는 쉬운 방법을 제공한다는 것입니다.

2.0의 새로운 기능 : TextEditor

WWDC 2020 이전에는 TextField또는 .NET을 사용하는 iOS에서만 텍스트 편집을 처리 할 수있었습니다 SecureTextField. 이들은 기본적으로 동일한 텍스트 필드이며 SecureTextField암호 필드와 마찬가지로 문자를 검은 색 원으로 대체하여 입력하는 내용을가립니다. 이러한 텍스트 필드의 중요한 유사점은 한 줄만 허용한다는 것입니다. 즉, 여러 줄 편집을위한 유일한 옵션 은 UIKit UIViewRepresentable에서 변환 UITextView하는 데 사용 하는 것입니다 .

struct ContentView : View {
@State private var text = ""
var body: some View {
MultilineTextView(text: $text)
}
}
struct MultilineTextView: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.isScrollEnabled = true
view.isEditable = true
view.isUserInteractionEnabled = true
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}

이것은 비교적 복잡하지만 더 많은 속성을 허용합니다. 입력 된 텍스트에서 탭할 수있는 URL을 만드는에 대한 UITextView변경을 허용 dataDetectorTypes합니다. 현재 텍스트는를 사용하여 새 텍스트로 바꿀 수 있으며 clearsOnInsertion를 호출하여 지정된 문자열이 표시 될 때까지 스크롤 할 수 있습니다 scrollRangeToVisible. 자세한 정보는 UITextView 문서를 확인하십시오 .

이러한 속성에 액세스 할 수는 없지만 새로운 속성 TextEditor TextField.

이제 TextEditor한 줄을 만드는 것처럼 쉽게 여러 줄을 만들 수 있습니다 TextField.

struct TextEditingView: View {
@State private var fullText: String = "This is some editable text..."
var body: some View {
TextEditor(text: $fullText)
}
}

에 적용 할 Text수있는 모든 항목을에 적용 할 수 있습니다 TextEditor. 사용자 지정 글꼴과 함께 새로운 동적 유형 구문 을 사용하려고했을 때 텍스트가 TextStyle. 이것은 첫 번째 베타의 버그이거나 내가 한 방식의 문제 일 수 있습니다. 내가 문서 편집기의 기능을 만들 수 어쨌든, 가장 좋은 예는과 글꼴 크기를 변경 할 수 있도록했다 Stepper과 함께 글꼴 무게를 Picker. A ColorPicker를 사용하여 전경 (글꼴) 색상을 선택할 수 있지만 배경이 현재 작동하지 않는 것 같습니다.

import SwiftUI
enum WeightType: String, CaseIterable {
case black, bold, heavy, light, medium, regular, semibold, thin, ultraLight
var weight: Font.Weight {
switch self {
case .black:
return .black
case .bold:
return .bold
case .heavy:
return .heavy
case .light:
return .light
case .medium:
return .medium
case .regular:
return .regular
case .semibold:
return .semibold
case .thin:
return .thin
case .ultraLight:
return .ultraLight
}
}
}
struct TextEditorView: View {
@State private var text: String = ""
@State private var fontWeight = WeightType.regular
@State private var fontSize = CGFloat(12)
@State private var customFont = true
@State private var foregroundColor = Color.primary
@State private var colourPickerShown = true
var body: some View {
ScrollView(.vertical) {
VStack {
Picker(selection: $fontWeight, label: EmptyView()) {
ForEach(WeightType.allCases, id: \.self) { fontWeight in
Text(fontWeight.rawValue)
}
}
.frame(height: 130)
.offset(y: -25)
Stepper(value: $fontSize, in: 1...100) {
Text("Font size: \(String(format: "%.1f", fontSize))")
}
HStack {
Button("Delete all") {
self.text = ""
}
.padding()
.background(Color.secondary)
.foregroundColor(.white)
.cornerRadius(5)
ColourPickerButton(dismissSheet: true, buttonTitle: "Text colour", previousColour: foregroundColor, colour: $foregroundColor, colourPickerShown: $colourPickerShown)
RoundedRectangle(cornerRadius: 5)
.aspectRatio(1, contentMode: .fit)
.foregroundColor(foregroundColor)
}
.frame(height: 50)
TextEditor(text: $text)
.font(.system(size: fontSize, weight: self.fontWeight.weight))
.foregroundColor(foregroundColor)
.frame(height: 300)
.padding()
.border(Color.secondary, width: 4)
}
.padding(.horizontal)
}
}
}
struct ColourPickerButton: View {
let dismissSheet: Bool
let buttonTitle: String
let previousColour: Color
@Binding var colour: Color
@State var colourInitialised = false
@Binding var colourPickerShown: Bool
var body: some View {
Button(buttonTitle) {
colourPickerShown.toggle()
}
.padding()
.background(Color.secondary)
.foregroundColor(.white)
.cornerRadius(5)
.onChange(of: colour) { _ in
if dismissSheet && colour == previousColour {
self.colourPickerShown.toggle()
}
}
.sheet(isPresented: $colourPickerShown) {
ColourPickerSheetView(colour: $colour, previousColour: colour)
}
}
}
struct ColourPickerSheetView: View {
@Binding var colour: Color
let previousColour: Color
var body: some View {
VStack {
if colour == previousColour {
Text("Swipe down to dismiss")
ColorPicker("Colour", selection: $colour)
}
}
}
}

TextEditorsystemBackground 색상 의 불투명 한 배경이있는 것처럼 보이며 배경을 추가하면 배경이 뒤에 배치됩니다.

배경이 전혀 보이지 않습니다.

해결 방법이 있는데, 아마도 지금은 우리가 할 수있는 전부일 것입니다 :

TextEditor는 아래 스크린 샷과 같이 테두리를 지원합니다.

2.0의 새로운 기능 : SignInWithAppleButton

Apple로 로그인은 이메일과 암호를 제공하지 않고도 앱에 안전하게 로그인하는 방법으로 iOS 13에 도입되었습니다. Apple로 로그인은 생체 인식을 사용하여 본인이 Apple ID를 소유 한 사람임을 인증 한 다음 앱에 자동으로 생성 된 전달 주소와 암호를 앱에 보냅니다.

이 새로운 버튼은 앱에서 Apple로 로그인을 채택하는 프로세스를 간소화합니다.

다음 예제는 SignInWthAppleButton허용되는 최대 크기 (너비 ≤ 375)에서 를 표시합니다 . 이보기에는 제약이 있습니다. Apple은 방금 기존 ASAuthorizationAppleIDButton 을으로 래핑 한 것 같습니다 UIViewRepresentable. 이는 작년에 SwiftUI에 버튼을 가져 오기 위해 필요한 작업입니다. 그러나 iOS 13에서 Apple로 로그인  구현하는 데 필요한 단계를 확인 하면 코디네이터에서 위임 프로토콜을 설정하는 추가 작업 이 필요하지 않습니다 .

import SwiftUI
import AuthenticationServices
struct SignInWithAppleView: View {
@State var output = ""
func output(_ result: Result<ASAuthorization, Error>) {
switch result {
case .success (let authResults):
self.output = "Authorization successful\n\n\(authResults)\n\(authResults.provider)\n\(authResults.credential)"
case .failure (let error):
self.output = "Authorization failed: \(error.localizedDescription)"
}
print(self.output)
}
var body: some View {
VStack {
Text("Tap the button to authenticate.\nYou must be signed into an Apple ID on this device.")
.foregroundColor(.black)
.lineLimit(nil)
SignInWithAppleButton(
.signIn,
onRequest: { request in
request.requestedScopes = [.fullName, .email]
},
onCompletion: { result in
output(result)
})
.frame(maxWidth: 375)
.aspectRatio(7, contentMode: .fit)
Text(output)
.foregroundColor(.black)
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
}
}

Apple의 예에서 채택한 내 버전은 인증 결과를 Text앱에 출력하고 인쇄합니다. 처음 시도 할 때 로그인 버튼이 아무 작업도하지 않는다는 것을 알 수 있습니다. 그것은 확실히 제 경험이었습니다.

두 번 누르면 예상대로 출력이 표시됩니다.

ProgressView (2.0의 새로운 기능)

UIActivityIndicatorView 는 미정 인로드 상태에 대한 스피너를 표시 할 수있는 UIKit 컨트롤입니다. UIViewRepresentable로 래핑하지 않고 직접 사용할 수 없었지만 이제는 이에 상응하는 것이 있습니다! 매개 변수없이 ProgressView를 생성하면 스피너가 표시되지만 진행률 값을 전달하면 수평 진행률 표시 줄로 표시 될 수 있습니다.

진행률 표시 줄 양식은 웹 페이지가로드 될 때 와 함께 자주 사용되는 UIProgressView 와 유사합니다 WKWebView.

struct ProgressingView: View {
@State private var progress = 0.5
var body: some View {
VStack(spacing: 15) {
ProgressView()
ProgressView("Downloading", value: progress, total: 1)
.progressViewStyle(CircularProgressViewStyle(tint: .accentColor))
ProgressView(value: progress)
.accentColor(.red)
Slider(value: $progress, in: 0...1)
}
}
}

중간 예제에 이름, 값 및 총 매개 변수가 있음을 알 수 있습니다. 일반적으로 이렇게 ProgressView하면 하단 예제와 같이 가로 진행률 표시 줄로 표시됩니다. 그러나 .progressViewStyle수정자를 적용 CircularProgressViewStyle하여 기본값과 일치하는 색조로에 전달 했음을 알 수 accentColor있습니다. 진행률 표시 줄이 아닌 결과는 파란색 원형 스피너입니다.

스타일이 적용될 때 "다운로드 중"레이블은 유지 ProgressView되지만는 원형 이 되어야하며 이로 인해 자동 동작이 취소 된다는 사실을 보여주는 그림입니다 .

GaugeView (2.0의 새로운 기능)

GaugeViewViewWatchOS 7 전용 의 유일한 새로운 기능 입니다. 이것은 값이 척도에있는 위치를 표시하는 비교적 간단한 지표입니다. 아래의 예에서는 이 표시 Slider되는 값을 변경 하기 위해를 추가했습니다 Gauge. Slider는 실제로 현재 값이 자체 파란색 막대에있는 위치를 표시하므로 약간 이상하게 보입니다. 게이지 스타일링은 매우 어렵습니다. 또한 호출되는 기본 게이지 스타일은 LinearGaugeStyle사용 Color.primary의 전경 색상으로, 그리고 사용 .foregroundColor또는 .accentColor수정하면이 변경되지 않습니다. 마찬가지로 CircularGaugeStyle사용 Color.gray, 그리고 이것은 변경할 수 없습니다.

import SwiftUI
struct GaugeView: View {
let sliderValue: Double
var body: some View {
VStack {
Gauge(value: sliderValue, in: 0...1) {
Text("Gauge")
}
.frame(maxHeight: .infinity)
}
}
}
struct ContentView: View {
@State var isCircular = false
@State var sliderValue = Double()
var body: some View {
VStack {
Toggle(isOn: $isCircular) {
Text("Circular")
}
if isCircular {
GaugeView(sliderValue: sliderValue)
.gaugeStyle(CircularGaugeStyle())
}
else {
GaugeView(sliderValue: sliderValue)
.gaugeStyle(LinearGaugeStyle())
}
Text("\(sliderValue)")
Slider(value: $sliderValue, in: 0...1) {
Text("Slider")
}
}
.padding()
}
}
view rawGaugeView.swift hosted with ❤ by GitHub

게이지에서 현재 값이있는 위치를 나타내는 엄지 손가락 또는 원은 적어도의 경우 마스크처럼 보입니다 LinearGaugeStyle. 추가 .background(Color.blue)하면 원의 색상이 변경됩니다.

라벨 (2.0의 새로운 기능)

struct LabelView: View {
var body: some View {
VStack {
Label("Games", systemImage: "gamecontroller")
HStack {//Roughly equivalent to
Image(systemName: "gamecontroller")
Text("Games")
}
}
}
}
view rawLabelView.swift hosted with ❤ by GitHub

이것은 Apple의 현재 더 큰 SF Symbols 무료 아이콘 컬렉션의 심볼을 더 큰 컨텍스트를 제공하는 텍스트와 결합하는 비교적 간단한 방법입니다. 내 예제에서 나는 비교하고 Label를 사용하여 해당과 함께 HStack. 거기의 정렬 의한 최초의 엑스 코드 (12) 베타 버그했습니다 Label Image텍스트로 잘못 정렬 할 수는 있지만, 지금은 수정되었습니다. 에 대한 두 가지 옵션이 있습니다. 그 .labelStyle중 하나 Text Image.

링크 (2.0의 새로운 기능)

저는 항상 iOS 앱에서 하이퍼 링크를 사용할 수 없다는 것이 조금 아쉽다고 생각했습니다. 물론 URL을 여는 파란색 텍스트가있는 버튼을 만들 수 있지만 이렇게하려면 매번 작성하려는 것보다 더 많은 코드가 필요합니다. 이 예제에서는 Button원래 형식으로 코드를 얻을 수있는만큼 작기 때문에 제목 문자열 (또는 지역화 된 문자열 키) 만 받는 편리한 이니셜 라이저를 사용하고 있습니다.

struct LinkStyleView: View {
let urlString = "https://medium.com/better-programming/the-complete-swiftui-documentation-youve-been-waiting-for-fdfe7241add9"
var body: some View {
Group {
if URL(string: urlString) != nil {
//The old way to create a Link-style Button
Button("Read more") {
if let url = URL(string: urlString) {
UIApplication.shared.open(url, options: [:], completionHandler: {_ in })
}
}
}
else {
EmptyView()
.onAppear { assertionFailure("URL was nil") }
}
}
}
}
view rawLinkStyleView.swift hosted with ❤ by GitHub

iOS 14에는 이제 위 Link의 작업 부분을 수행하는이 Button있습니다. 나는 Apple의 문서 가 '!'를 사용하여 URL을 안전하지 않게 언 래핑하는 방식이 마음에 들지 않았습니다 . 왜냐하면 이것은 코드 샘플에서 권장하는 매우 나쁜 습관이기 때문입니다. 물론, URL을 IP 주소로 변환하는 IANA (Internet Assigned Numbers Authority) 소유 사이트 인 example.com/TOS.html에 연결되기 때문에이 특정 URL이 성공적으로 생성되었음을 알 수 있습니다 .

그러나 URL 문자열이 유효하다는 인간의 확신에 맡기면 조만간 실수하게 될 것입니다.

선택 사항을 안전하지 않게 풀 때 예기치 않게 nil을 찾는 앱은 즉시 충돌합니다.

그렇기 때문에 위의 예제는 Apple의 예제보다 몇 줄 더 걸리지 만 안전하게 수행합니다. 이 예제는 SwiftUI의 첫 번째 버전에서 선택적 바인딩 ( if let또는 guard let) 이 부족하여 실제로 방해가됩니다. 대신 URL이 존재하는지 확인하기 위해 URL을 nil과 비교하는 것으로 제한되어 있습니다. 이 비교에서 URL이 nil이 아님을 확인하더라도, 이것이 Button먼저 래핑을 풀지 않고 에서 사용할 수 있다는 의미는 아닙니다 . 이것이 Button액션 에 약간 혼란스러운 추가 단계가있는 이유 입니다. 선택적으로 URL을 바인딩하여 nil이 아닌지 확인합니다.

in the action assertionFailure뒤에 else 문 을 넣을 수 있었지만 예제 와의 일관성을 위해 를 추가하고 싶었습니다 . 포함하는 else 문 은 필요하지 않습니다. 클로저 의 유일한 점유자 주변의 모든 if 문 은 if 조건이 거짓 일 때 반환 됩니다. 그러나 URL이 nil이면 어떤 일이 발생할지 보여주기 위해 이것을 명시 적으로 추가하고 싶었습니다. 사용자는 아무것도 볼 수 없지만 디버그 모드에서 개발자에 대해 어설 션이 트리거됩니다.if letButtonEmptyViewLinkEmptyViewViewBuilderEmptyView

이렇게하면 URL이 nil임을 알 수 있지만 최종 사용자에게 충돌을 일으키지 않습니다.

struct LinkView: View {
let urlString = "https://medium.com/better-programming/the-complete-swiftui-documentation-youve-been-waiting-for-fdfe7241add9"
var body: some View {
Group {
if let url = URL(string: urlString) {
//The new way to create a Link
Link("Read more", destination: url)
}
else {
EmptyView()
.onAppear { assertionFailure("URL was nil") }
}
}
}
}
view rawLinkView.swift hosted with ❤ by GitHub

이제 SwiftUI가 지원하는 경우 URL과 같은 속성을 직접 만들고 해당 데이터를 사용하는 뷰를 만들 수 있습니다. 이전과 마찬가지로 링크는 URL을 생성 할 수있을 때만 표시되지만이 경우를 확인하기 위해 여러 번 확인할 필요는 없습니다.

메뉴 (2.0의 새로운 기능)

기본 스타일은 BorderedButtonMenuStyle입니다.

MenuButton이 (가)로 대체되었습니다 Menu. 원본은 드롭 다운 메뉴 였고 대체품도 그리 다르지 않습니다. 새 이름이 추가하는 가장 중요한 것은 메뉴와 그 안의 항목에 대해 이야기하고 있으므로 명확성이므로 버튼이라고 부르는 것은별로 의미가 없습니다. Menu스타일에 대한 몇 가지 다른 옵션이 제공됩니다. 기본 스타일은 BorderedButtonMenuStyle입니다.이 때문에 DefaultButtonMenuStyle오른쪽은 똑같이 BorderlessButtonMenuStyle보이고 가운데는 다르게 보입니다.

import SwiftUI
@available(OSX 10.16, *)
@available(iOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
struct MenuView : View {
@State var selectedOption = "Select an option"
var body : some View {
HStack {
Menu(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")
}
}
.menuStyle(BorderedButtonMenuStyle())
Menu(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")
}
}
.menuStyle(BorderlessButtonMenuStyle())
Menu(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")
}
}
.menuStyle(DefaultMenuStyle())
}
.padding()
.frame(height: 50)
}
}
view rawRevDoc Menu.swift hosted with ❤ by GitHub

그외 메뉴에 "풀다운는"사실에 대한 참조를 제거하고 새로운 스타일에서 유일한 변화는이 MenuButton라고 Menu하고, .menuButtonStyle수정이 지금이라고합니다 .menuStyle.

첫 번째 베타에서는 Menu대체하는 컨트롤과 마찬가지로 macOS에서만 사용할 수있었습니다.

그러나 베타 3는 iOS에서도 메뉴 지원을 추가했습니다 .

MenuButton (2.0에서 더 이상 사용되지 않음)

Menu를 대체 MenuButton하지만 동일한 기능을 많이 제공 하는 위를 참조하십시오 .

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

텍스트는 뷰를 만드는 가장 간단한 구성 요소 일 것입니다. 대부분의 경우를 전달 String하여 생성하고 이것이 표시되는 콘텐츠가됩니다. 이 문서의 원래 버전에는 지역화, ObservableObjects 및 하위 문자열을 포함하여 문서를 만드는 다른 모든 방법이 포함되어 있습니다. 이 예제의 맨 아래에서 찾을 수 있지만 2020 년에는 예제 맨 위에 포함 된 많은 새로운 이니셜 라이저가 있습니다.

첫 번째는 .NET Framework에서 상속하는 모든 일반 객체와 모든 클래스를 사용할 수 있으므로 매우 흥미 롭습니다 Formatter. 이것은 FormatterApple이 제공 하는 유형이거나 내 예에서와 같이 Formatter사용자 정의 유형을 위해 특별히 만든 유형일 수 있습니다. a Formatter가 구현 되는 방법을 정확히 알지 못했기 때문에 선택적으로 개체를 사용자 지정 클래스의 인스턴스에 바인딩하고 다른 모든 상황에서는 nil을 반환했습니다.

주의점 걸리는 일이 LocalizedStringKey, tableName, bundle, 및 comment용도 별도의 파일이 필요합니다 .strings파일 확장자를. 이 initializer에 대한 Apple의 문서 에서 언급했듯이 유일한 필수 매개 변수는 키의 문자열입니다. 이러한 다른 매개 변수가 무엇을 필요로하는지 알 수 있도록 대부분의 자세한 예제를 제공했습니다.

의 기본 tableNameIS Localizable하는 문자열 파일의 표준 이름. Local이 매개 변수가 필요한 이유를 보여주기 위해 일부러 내 이름을 지정했습니다 . 번들은 기본적으로 기본 번들이므로이 Bundle.main경우 전달 은 중복됩니다. 주석은 상황에 맞는 정보를 제공해야하지만이 예제에서는 문자열 Comment.

/*
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"
}
class MyFormatter: Formatter {
override func string(for obj: Any?) -> String? {
if let customObj = obj as? CustomType {
return customObj.name + "\n" + customObj.text
}
else {
return nil
}
}
}
class CustomType: NSObject {
let name: String
let text: String
init(name: String, text: String) {
self.name = name
self.text = text
}
}
struct ContentView: View {
@ObservedObject var data = DataModel.shared
let substring: Substring = "This is a substring"
let string = "This is a string"
let codeUpdated = Date(timeIntervalSince1970: 1594548848)
var body: some View {
VStack {
Group {
//NEW: Use a custom type and a custom formatter to display it
Text(customObject, formatter: formatter)
//NEW: Add an Image with string interpolation
Text("\(Image(systemName: "gamecontroller")) Games")
//NEW: Add an Image with string interpolation
Text(Image(systemName: "gamecontroller"))
//NEW: A range between two dates
Text(ClosedRange<Date>(uncheckedBounds: (lower: codeUpdated, upper: Date())))
//NEW: A range between a date and an added duration (1 day in seconds)
Text(DateInterval(start: codeUpdated, duration: 86400))
//NEW: Use the new DateStyle to specify formatting as a duration, not a date range
Text(codeUpdated, style: .relative)
//NEW: Change the case of text
Text("Make this uppercase")
.textCase(.uppercase)
Text("MAKE THIS LOWERCASE")
.textCase(.lowercase)
Text("KEEP THIS THE SAME")
.textCase(.none)
}
Group {
//NEW: Font styles
Text("Caption 2")
.font(.caption2)
Text("Title 2")
.font(.title2)
Text("Title 3")
.font(.title3)
//NEW: Font designs
Text("Monospaced")
.font(.system(.body, design: .monospaced))
Text("Serif")
.font(.system(.body, design: .serif))
}
Group {
//This is a substring
Text(substring)
//This is a string
Text(string)
//This is an ObservedObject string
Text(data.string)
//This uses the text 'string_key' exactly as it is without using it to look up a localisation
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")
}
}
}
}
view rawRevDoc Text.swift hosted with ❤ by GitHub

Text이제 Imageusing 보간을 포함 하거나 Date. 세 가지 새로운 글꼴 : caption2, title2 title3. 이들은 UIFont의 일부로 사용할 수 있었으므로 SwiftUI로 만든 것은 놀라운 일이 아닙니다. 새로운 수정자를 사용하면 텍스트가 대문자인지 소문자인지와 고정 폭 또는 세리프 디자인을 사용할지 여부를 선택할 수 있습니다.

Dynamic Type을 사용하여 사용자 정의 글꼴을 시스템 스타일만큼 액세스 할 수 있도록 만드는 새로운 방법은 포함하지 않았습니다. 이를 위해 Hacking With Swift의 Dynamic Type 가이드에 링크 할 것입니다. 거기에서 말한 내용 만 반복 할 것입니다.

이미지 (2.0에서 업데이트 됨)

SF Symbols 2는 Mac 앱에서의 사용을 지원합니다. Image(systemNamed:)Xcode 11에서 SF 기호를 사용 하려고 하면 "외부 인수 레이블 'systemNamed :'in call"오류가 발생합니다. 즉, macOS에는 표시 할 방법이 없기 때문에 기본 Mac 앱 또는 Catalyst 앱에서도 SF Symbols를 사용할 수 없습니다. 아마도 macOS Catalina에서 실행되는 SF Symbols Mac 앱은 .NET을 사용할 수 없었기 때문에 심볼의 썸네일로 PNG를 사용했을 것 Image(systemNamed:)입니다.

어쨌든 Xcode 12 및 macOS 11 Big Sur부터는 이러한 경고가 표시되지 않으며 Image(systemNamed:)기본 macOS 및 Mac Catalyst 앱에서 사용할 수 있습니다 .

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

이제 CardButtonStyletvOS에는 색상이 덜 눈에 띄는 작은 버튼을 만드는 옵션 이 있습니다 . 위의 예는 Button스타일이 전혀없이 생성 된를 보여 DefaultButtonStyle주므로를 사용하므로 차이를 확인할 수 있습니다. 새로운 Button것은 훨씬 더 미묘하며 더 밝은 색상이 주어진 옵션보다 덜 중요한 옵션에 사용할 수 있습니다.

import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button("DefaultButtonStyle") {
print("Default button pressed")
}
Button(action: { print("CardButtonStyle pressed")}) {
Text("CardButtonStyle")
.padding()
}
.buttonStyle(CardButtonStyle())
}
}
}

CardButtonStyle Button어떤 이유로 패딩이 없기 때문에 에 패딩을 추가해야했습니다 . 이것은 Xcode 12 베타 2에서만 테스트하고 있으므로 이후 베타에서 변경 될 수 있습니다.

PasteButton (2.0에서 업데이트 됨)

이 컨트롤을 사용하면 MacOS에 정보를 붙여 넣을 수 있지만 iOS에서는 사용할 수 없습니다. UTI로 표현되는 다양한 데이터 유형을 사용할 수 있습니다. Apple의 문서를 인용하기 위해 "Uniform Type Identifiers는 다른 앱에서로드, 저장 또는 열 수있는 리소스에 대한 공통 유형을 선언합니다." Xcode 11에서는 .NET Framework를 만들 때 이러한 문자열을 배열에 제공해야했습니다 PasteButton.

이 버튼을 구현할 때 도움이 될 모든 유형의 UTI 문자열을 찾을 수있는 함수를 예제에 포함했습니다.

이제 UTType지원하려는 유형을 훨씬 쉽게 만들 수있는 라는 새로운 구조 가 있습니다. 이 구조의 이니셜 라이저에 Xcode 11에서 작동했던 문자열을 전달하거나 제공된 많은 시스템 선언 유형 중 하나를 사용할 수 있습니다 .

import SwiftUI
import UniformTypeIdentifiers
@available(OSX 10.16, *)
@available(iOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
struct PasteButtonView: View {
@State var text = String()
let utType = UTType.text
//The declaration above is equivalent to
//let utType = UTType("public.utf8-plain-text")!
var body: some View {
VStack {
Text(text)
PasteButton(supportedContentTypes: [utType], payloadAction: { array in
guard let firstItem = array.first else {
return
}
firstItem.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")
}
}
}

문자열을받는 이니셜 라이저는 선택 사항을 반환하므로 사용하는 경우 사용하는 문자열이 올바른지 확인해야합니다. 내가 사용하고 UTType.text내 예를 들어,하지만 문자열에서 수동으로 구성 아래 나는 예를 포함. 느낌표를 사용하여 옵션을 강제로 풀지 않는 것이 좋습니다. UTType시스템 선언 유형에서 얻은 유형이 nil이 아니므로 동일한 유형이 문자열에서 생성되지 않음 을 보여주고 싶었습니다 .

필요한 유형 식별자를 결정했으면에서 가져온 데이터를 처리해야합니다 NSItemProvider. 내 예제는 배열의 첫 번째 항목 만 붙여 넣지 만 다른 데이터 유형과 여러 항목을 처리 할 수있는 방법을 명확하게 보여줍니다.

Here’s a list of the types that conform to NSItemProviderWriting, and can therefore be used for pasting with the PasteButton:

Toggle (Updated in 2.0)

The default style on iOS, SwitchToggleStyle, now allows us to choose a tint colour that is only shown when the bool the Toggle has a Binding to is true. While the default Toggle tint on iOS and iPadOS is green, on Mac Catalyst it is blue. Using the new SwitchToggleStyle with the tint colour option in a native Mac app currently displays a switch as we would expect, but the tint colour is still the default blue.

import SwiftUI
struct ToggleTintView: View {
@State var toggleIsOn = true
var body: some View {
VStack {
Toggle(isOn: $toggleIsOn) {
Text("Toggle")
}
Toggle(isOn: $toggleIsOn) {
Text("Toggle")
}
.toggleStyle(SwitchToggleStyle(tint: .red))
}