Gabungkan DragGesture, RotateGesture dan MagnifyGesture dengan simultaneously(with:), kemudian paparkan nilainya melalui satu saluran transformasi. Untuk kanvas Mac, pastikan permukaan input kekal pegun dan simpan perubahan sementara setiap gerak isyarat secara berasingan daripada hasil yang telah disahkan.
Tutorial ini membina pemapar bentuk kecil dalam app macOS anda sendiri: klik dan seret untuk mengalih, cubit untuk mengezum, dan gunakan gerak isyarat putaran pad jejak untuk memutarkan bentuk. Ia hanya menggunakan segi empat tepat dan bulatan dalam memori. Komposisi ini membolehkan gerak isyarat yang dikenal pasti bertindih; input yang benar-benar bertindih bergantung pada peristiwa yang dihantar oleh peranti.
Apakah maksud serentak pada Mac?
Apple’s simultaneously(with:) menggabungkan dua gerak isyarat menjadi satu gerak isyarat lain, jadi hasilnya boleh digabungkan dengan gerak isyarat ketiga. Ini menyediakan komposisi yang diperlukan untuk pan, putaran dan zum tanpa memerlukan tekan lama atau tindakan awal yang lain. Dokumentasi gerak isyarat serentak Apple menerangkan gabungan ini.
Tentukan maksud pan sebelum menulis kod. Di sini, pan bermaksud menyeret sambil menahan butang tetikus utama, termasuk klik dan seret pada pad jejak. Gelongsoran dua jari merentasi pad jejak biasanya menghasilkan input skrol. Panduan peristiwa arkib Apple membezakan aliran skrol itu daripada gerak isyarat zum dan putaran; dokumentasi AppKit semasa masih menyediakan skrol melalui scrollWheel(with:). Menambah gerak isyarat seret tidak menukarkan skrol kepada seretan. Mengendalikan Peristiwa Pad Jejak, scrollWheel(with:)
Gunakan jadual keputusan ini untuk memilih lapisan input bagi kanvas anda:
| Interaksi yang dikehendaki | Titik permulaan | Batasan yang perlu dikekalkan |
|---|---|---|
| Pan klik dan seret, zum cubit, putaran pad jejak | Contoh SwiftUI di bawah | Komposisi membolehkan aliran gerak isyarat yang dikenal pasti bertindih. |
| Skrol dua jari untuk pan | Kanvas AppKit yang dibenamkan dalam SwiftUI | Skrol kekal sebagai aliran peristiwa skrol. |
| Transformasi tiga komponen yang terjamin daripada satu corak jari | Interaksi tersuai yang ditentukan secara berasingan | Komposisi serentak sahaja tidak menjamin perkakasan atau macOS akan mengeluarkan setiap komponen. |
Contoh ini memerlukan macOS 14 atau lebih baharu, yang menyediakan MagnifyGesture dan RotateGesture. Ia menggunakan pemula asasnya dan bukannya API konfigurasi jenis input yang lebih baharu. MagnifyGesture, RotateGesture
Bagaimanakah keadaan transformasi patut disusun?
Berikan setiap komponen nilai yang disahkan dan nilai gerak isyarat langsung. Terjemahan dan putaran digabungkan melalui penambahan; skala digabungkan melalui pendaraban. Persamaan berikut mentakrifkan tingkah laku contoh ini, bukannya keperluan yang ditetapkan oleh SwiftUI:
terjemahan yang dipaparkan = terjemahan yang disahkan + terjemahan seretan langsung
putaran yang dipaparkan = putaran yang disahkan + putaran langsung
skala yang dipaparkan = clamp(skala yang disahkan × pembesaran langsung)
Gunakan @GestureState untuk nilai langsung. Apple mendokumenkan bahawa ia ditetapkan semula apabila gerak isyarat menjadi tidak aktif. Sahkan setiap komponen melalui panggil balik onEnded masing-masing, yang hanya dijalankan selepas gerak isyarat berjaya diselesaikan. Ini menjadikan pembatalan jelas maksudnya: buang pratonton belum selesai bagi komponen itu dan kekalkan nilai terdahulu yang telah disahkan. GestureState, onEnded(_:)
Kekalkan panggil balik ini pada gerak isyarat masing-masing sebelum menggabungkannya. Setiap panggil balik kemudian menerima nilainya sendiri yang ditaip dan hanya mengubah sifat yang dimilikinya. Tiada petikan keadaan transformasi permulaan yang dikongsi untuk ditindih oleh gerak isyarat yang bermula kemudian.
Terjemahan seretan memerlukan perhatian khusus: nilainya ialah jumlah pergerakan sejak gerak isyarat bermula. Jika jumlah itu ditambah pada ofset yang disahkan setiap kali kemas kini, pergerakan terdahulu akan dikira berulang kali. Tetapkan jumlah itu pada pratonton langsung, kemudian tambah jumlah akhir sekali sahaja apabila selesai. DragGesture.Value.translation
Bina contoh SwiftUI yang lengkap
Cipta app SwiftUI macOS dengan sasaran penggunaan macOS 14 atau lebih baharu. Gantikan titik masuk app dan paparan kandungan yang dijana dengan satu fail berikut; pastikan hanya satu pengisytiharan @main dalam sasaran. Tiada aset, pakej atau data luaran diperlukan.
Kanvas kelabu memiliki gerak isyarat tersebut. Bentuk anak menerima transformasi visual, jadi mengalih atau memutarkan bentuk tidak mentransformasikan permukaan input gerak isyarat. Contoh ini sengaja mengezum dan memutarkan bentuk pada titik tengahnya.
import SwiftUI
@main
struct TransformCanvasApp: App {
var body: some Scene {
WindowGroup {
TransformCanvas()
.frame(minWidth: 400, minHeight: 300)
}
.defaultSize(width: 720, height: 500)
}
}
struct TransformCanvas: View {
@State private var offset = CGSize.zero
@State private var angle = Angle.zero
@State private var scale: CGFloat = 1
@GestureState private var liveOffset = CGSize.zero
@GestureState private var liveAngle = Angle.zero
@GestureState private var liveScale: CGFloat = 1
private func boundedScale(_ value: CGFloat) -> CGFloat {
min(max(value, 0.25), 4)
}
private var pan: some Gesture {
DragGesture(minimumDistance: 3, coordinateSpace: .local)
.updating($liveOffset) { value, state, _ in
state = value.translation
}
.onEnded { value in
offset = CGSize(
width: offset.width + value.translation.width,
height: offset.height + value.translation.height
)
}
}
private var rotation: some Gesture {
RotateGesture()
.updating($liveAngle) { value, state, _ in
state = value.rotation
}
.onEnded { value in
angle = angle + value.rotation
}
}
private var zoom: some Gesture {
MagnifyGesture()
.updating($liveScale) { value, state, _ in
state = value.magnification
}
.onEnded { value in
scale = boundedScale(scale * value.magnification)
}
}
private var manipulation: some Gesture {
pan.simultaneously(with: rotation)
.simultaneously(with: zoom)
}
var body: some View {
ZStack {
Color.gray.opacity(0.12)
RoundedRectangle(cornerRadius: 18)
.fill(Color.blue)
.overlay(alignment: .topLeading) {
Circle()
.fill(Color.white)
.frame(width: 24, height: 24)
.padding(16)
}
.frame(width: 180, height: 120)
.scaleEffect(
boundedScale(scale * liveScale),
anchor: .center
)
.rotationEffect(angle + liveAngle, anchor: .center)
.offset(CGSize(
width: offset.width + liveOffset.width,
height: offset.height + liveOffset.height
))
.allowsHitTesting(false)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.clipped()
.gesture(manipulation)
}
}
Apple mendokumenkan jenis gerak isyarat seret, zum dan putaran yang mendasarinya serta cara melampirkan gerak isyarat pada paparan. Contoh ini menggabungkan API tersebut dengan dasar keadaan dan koordinat yang jelas. SwiftUI tidak tersedia dalam persekitaran penyelidikan, jadi program ini disemak berdasarkan dokumentasi tetapi tidak dikompil atau dijalankan di sini. DragGesture, MagnifyGesture, RotateGesture
Mengapa skalakan, kemudian putarkan, kemudian ofset?
Pemetaan yang dimaksudkan ialah menskalakan bentuk pada titik tengahnya, memutarkannya pada titik tengah yang sama, kemudian menterjemahkannya dalam koordinat kanvas. Meletakkan ofset pada akhir memastikan pan tidak terjejas oleh skala dan putaran bentuk. Gerakan penuding secara mendatar sepatutnya menggerakkan bentuk secara mendatar walaupun bentuk itu telah berpusing.
Susunan ini ialah pilihan reka bentuk yang berasaskan saluran transformasi contoh. Apple mendokumenkan bahawa scaleEffect menskalakan output terpapar di sekeliling sauh, rotationEffect memutarkan output terpapar dan offset mengalihkan kandungan yang dipaparkan. Kesan visual ini tidak menjadikan kanvas pegun sebagai bekas susun atur yang ditransformasikan. scaleEffect(_:anchor:), rotationEffect(_:anchor:), offset(_:)
Had 0.25 dan 4 ialah tetapan aplikasi untuk tujuan ilustrasi. Had yang sama digunakan semasa pratonton dan pengesahan, jadi skala yang dipaparkan kekal selepas gerak isyarat dilepaskan. min dan max Swift memilih nilai terkecil dan terbesar untuk menyatakan had tersebut. min(::), max(::)
Semak interaksi dengan jangkaan yang konkrit
Mulakan dengan setiap komponen secara berasingan, kemudian cuba tindihkannya menggunakan peranti input yang tersedia. Bulatan putih menjadikan putaran kelihatan walaupun segi empat tepat itu kecil.
- Klik dan seret pada kanvas, lepaskan, kemudian seret sekali lagi. Seretan kedua sepatutnya bermula dari kedudukan seretan pertama yang telah disahkan.
- Cubit untuk membesarkan bentuk, lepaskan, kemudian cubit sekali lagi. Setiap gerak isyarat sepatutnya mendarabkan skala yang dikekalkan.
- Putar, lepaskan, kemudian putar sekali lagi. Sudutnya sepatutnya terkumpul.
- Selepas mengezum dan memutar, seret secara mendatar. Bentuk itu sepatutnya masih bergerak mengikut paksi mendatar kanvas.
- Jika peranti menghantar input bertindih, tamatkan satu komponen sambil meneruskan komponen yang lain. Komponen yang masih berjalan sepatutnya terus mengemas kini sifatnya sendiri.
- Hentikan gerak isyarat aktif, contohnya dengan bertukar aplikasi. Sebarang gerak isyarat yang menjadi tidak aktif tanpa penyelesaian yang berjaya sepatutnya membuang pratonton langsungnya, bukannya membiarkan keadaan sementara tersangkut.
Sebagai semakan aritmetik untuk ilustrasi, andaikan skala yang dikekalkan ialah 1.5 dan cubitan melaporkan 1.2. Hasil yang dipaparkan ialah 1.8. Jika sudut yang dikekalkan ialah 30 darjah dan putaran langsung ialah 15 darjah, sudut yang dipaparkan ialah 45 darjah. Terjemahan seretan sebanyak 40 mata kekal sebagai ofset 40 mata pada kanvas tanpa mengira skala dan sudutnya.
Contoh ini mengesahkan komponen secara berasingan. Jika putaran berjaya diselesaikan sementara seretan yang masih aktif kemudian dibatalkan, putaran yang selesai kekal manakala seretan yang belum selesai dikembalikan. Editor yang memerlukan satu operasi atomik merentas semua komponen memerlukan model sesi interaksi tambahan dengan dasar pengesahan dan pengembalian yang jelas.
Bentuk itu juga boleh diseret keluar dari kanvas yang kelihatan kerana terjemahannya sengaja tidak dihadkan. Kanvas kekal boleh diseret, membolehkan bentuk digerakkan semula ke arah bertentangan. Pemapar yang lengkap boleh menambah arahan tetapan semula berasingan atau mengehadkan terjemahan mengikut sempadan kandungannya sendiri.
Bilakah anda patut menggunakan kanvas AppKit?
Pilih lapisan input AppKit apabila tugas tersebut memerlukan pan melalui skrol dua jari atau kawalan terus terhadap momentum skrol. Benamkan paparan AppKit milik anda melalui NSViewRepresentable, yang disediakan Apple untuk mencipta, mengemas kini dan meleraikan paparan AppKit dalam SwiftUI. NSViewRepresentable
Pastikan setiap aliran input mempunyai satu pemilik sahaja. Dalam kanvas AppKit itu, kendalikan skrol melalui scrollWheel(with:) dan gunakan pengecam gerak isyarat untuk pembesaran dan putaran. Salurkan perubahan yang terhasil ke dalam model terjemahan, sudut dan skala yang sama. Elakkan daripada melampirkan pengendali SwiftUI lain yang turut menggunakan input yang sama pada model tersebut.
Bagi pengecam gerak isyarat AppKit yang sepatutnya boleh berfungsi serentak, laksanakan gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) dan pulangkan true hanya untuk pasangan pengecam yang dimaksudkan dan milik kanvas anda. Apple mendokumenkan bahawa kaedah perwakilan itu membenarkan pengecaman serentak; kaedah ini tidak menghasilkan aliran input yang tiada. Kaedah perwakilan pengecaman serentak
Kekalkan konvensyen nilai apabila merapatkan API. Pengecam gerak isyarat pembesaran AppKit melaporkan nilai yang perlu ditambah kepada 1 untuk mendapatkan faktor skala, manakala pengecam gerak isyarat putarannya mendedahkan radian. Jangan salin tetapan nilai SwiftUI tanpa perubahan. NSMagnificationGestureRecognizer.magnification, NSRotationGestureRecognizer.rotation
Untuk skrol, Apple mengenal pasti scrollingDeltaX sebagai sifat delta mendatar yang disyorkan dan menyatakan bahawa delta tidak tepat mungkin perlu dilaraskan. Dokumentasi momentumnya menerangkan bahawa peristiwa skrol berterusan selepas gerak isyarat fizikal berhenti. Tentukan sama ada kanvas menerima sambungan itu, kemudian salurkan setiap peristiwa yang diterima sekali sahaja. scrollingDeltaX, momentumPhase
Jambatan itu sepatutnya mengubah transformasi kandungan kanvas anda, sementara SwiftUI kekal bertanggungjawab terhadap susun atur paparan yang diwakilkan. Apple secara jelas memberi amaran supaya tidak menetapkan bingkai atau sempadan paparan yang diwakilkan secara langsung. Gunakan penyelaras bagi representable untuk membawa perubahan perwakilan dan tindakan sasaran kembali ke SwiftUI. NSViewRepresentable
Bagi pemapar klik dan seret, pelaksanaan SwiftUI menyediakan titik permulaan yang lengkap: satu permukaan input pegun, tiga komponen gerak isyarat yang diurus secara berasingan dan susunan transformasi yang disengajakan. Kekalkan batasan tersebut apabila mengembangkannya, dan pilih laluan AppKit apabila tugas untuk pengguna memerlukan pan melalui skrol.
Sumber
- simultaneously(with:) — Apple
- Mengendalikan Peristiwa Pad Jejak — Apple, Arkib Dokumentasi
- scrollWheel(with:) — Apple
- MagnifyGesture — Apple
- RotateGesture — Apple
- GestureState — Apple
- onEnded(_:) — Apple
- translation — Apple
- DragGesture — Apple
- scaleEffect(_:anchor:) — Apple
- rotationEffect(_:anchor:) — Apple
- offset(_:) — Apple
- min(::) — Apple
- max(::) — Apple
- NSViewRepresentable — Apple
- gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) — Apple
- magnification — Apple
- rotation — Apple
- scrollingDeltaX — Apple
- momentumPhase — Apple