Apabila seretan kanvas berakhir, kira geometri akhir item dalam ruang koordinat sasaran. Uji pembendungan penuh dahulu, kemudian pertindihan dengan luas positif. Tutorial ini melaksanakan peraturan tersebut untuk item segi empat tepat dalam app Mac anda sendiri dan menerangkan padanan setara AppKit.
Takrifkan maksud pertindihan dan pembendungan
Bayangkan kanvas desktop ringkas dengan segi empat tepat biru yang boleh dialihkan dan sasaran kelabu yang tetap. Tugas pembaca ialah melaporkan sama ada item yang dilepaskan muat sepenuhnya di dalam sasaran, bertindih sebahagian dengannya, atau tidak berkongsi sebarang kawasan dengannya. Segala-galanya dalam contoh ini adalah sintetik dan disimpan dalam memori.
Pembendungan mempunyai arah: sasaran mesti membendung item. Apple mendokumenkan bahawa CGRectContainsRect(target, item) mengembalikan true apabila gabungan segi empat tepat sama dengan sasaran. Ini termasuk item yang sepadan tepat dengan sasaran. Menyemak pembendungan dahulu mengelakkan item yang terkandung sepenuhnya daripada dilaporkan sebagai pertindihan separa. Dokumentasi CGRectContainsRect
Dalam contoh ini, pertindihan memerlukan kawasan bersama yang lebarnya positif dan tingginya positif. Sentuhan pada tepi atau bucu sahaja dikira sebagai Tiada pertindihan kawasan. Kira persilangan dan periksa dimensinya supaya tingkah laku sentuhan dinyatakan dengan jelas. Apple mendokumenkan bahawa CGRectIntersection mengembalikan segi empat tepat yang dikongsi, atau segi empat tepat null apabila tiada persilangan. Dokumentasi CGRectIntersection
| Hasil | Peraturan yang digunakan dalam tutorial ini | Contoh |
|---|---|---|
Terkandung |
Sasaran membendung keseluruhan item, termasuk sempadan yang bertepatan | Item muat di dalam sasaran atau sepadan tepat dengannya |
Bertindih |
Pembendungan gagal, tetapi kawasan bersama mempunyai lebar dan tinggi yang positif | Item melintasi tepi sasaran |
Tiada pertindihan kawasan |
Tiada persilangan dengan luas positif | Item terpisah atau hanya menyentuh tepi |
Geometri tidak sah |
Salah satu segi empat tepat mempunyai koordinat bukan terhingga atau dimensi bukan positif | Item berlebar sifar |
Ini ialah peraturan aplikasi yang berpunca daripada operasi geometri. Jika antara muka anda memerlukan ruang tepi di dalam sasaran, takrifkan ruang itu secara berasingan dan uji terhadap segi empat penerimaan yang lebih kecil. Pastikan sasaran yang kelihatan dan peraturan penerimaan mudah difahami oleh orang yang menyeret item.
Gunakan satu ruang koordinat dan anjakan akhir seretan
Segi empat tepat yang anda lukis dan segi empat tepat yang anda kelaskan mesti menggambarkan kedudukan yang sama. Dalam contoh ini, item dan sasaran kedua-duanya menggunakan koordinat kanvas. Kanvas menerima ruang koordinat bernama, dan DragGesture item menggunakan nama yang sama. Modifier coordinateSpace(_:) semasa daripada Apple menyokong corak ini pada macOS 14 dan lebih baharu. Dokumentasi ruang koordinat bernama
Simpan segi empat tepat item yang telah dikomit secara berasingan daripada anjakan seretan sementara. Semasa gerak isyarat berlangsung, lukis segi empat tepat yang telah dikomit dengan anjakan tersebut. Apabila gerak isyarat selesai dengan jayanya, kira semula segi empat tepat akhir itu daripada nilai panggil balik, kelaskannya dan komitkannya sekali.
Apple mentakrifkan translation sebagai jumlah anjakan dari permulaan seretan hingga peristiwa semasa. Oleh itu, menambah keseluruhan anjakan berulang kali pada kedudukan yang sudah dikemas kini akan mengumpulkan pergerakan berlebihan. Gunakan anjakan itu pada kedudukan komit yang tidak berubah. Dokumentasi anjakan seretan
Titik pelepasan sahaja tidak dapat menentukan sama ada keseluruhan item muat di dalam sasaran. Seseorang boleh memegang segi empat tepat berhampiran satu bucu, lalu sebahagian besarnya kekal di luar walaupun penuding berada di dalam. Menggunakan anjakan juga mengekalkan ofset pegangan asal tanpa mengalihkan pusat item ke penuding.
Bina contoh segi empat tepat SwiftUI yang lengkap
Cipta app SwiftUI macOS dalam Xcode dengan sasaran penggunaan macOS 14 atau lebih baharu. Gantikan titik masuk app yang dijana dengan dua blok di bawah, diletakkan berturutan dalam satu fail Swift. Buang titik masuk app yang asal supaya sasaran mengandungi hanya satu pengisytiharan @main. Kedua-dua blok bersama-sama membentuk program yang lengkap.
Blok pertama mentakrifkan pengelas geometri. Ia menolak dimensi tidak sah secara jelas dan bukannya menukarkannya secara senyap kepada segi empat tepat yang lain. Enumerasi Swift memastikan empat hasil yang mungkin kekal berbeza; nilai mentah rentetannya menyediakan mesej yang dipaparkan. Dokumentasi enumerasi Swift
import SwiftUI
import CoreGraphics
enum Placement: String {
case contained = "Contained"
case overlap = "Overlap"
case separate = "No area overlap"
case invalid = "Invalid geometry"
}
func isUsable(_ rect: CGRect) -> Bool {
let values = [rect.origin.x, rect.origin.y,
rect.width, rect.height,
rect.maxX, rect.maxY]
return values.allSatisfy { $0.isFinite }
&& rect.width > 0 && rect.height > 0
}
func classify(item: CGRect, target: CGRect) -> Placement {
guard isUsable(item), isUsable(target) else {
return .invalid
}
if CGRectContainsRect(target, item) {
return .contained
}
let shared = CGRectIntersection(item, target)
if !shared.isNull && shared.width > 0 && shared.height > 0 {
return .overlap
}
return .separate
}
Blok kedua menyediakan app dan kanvas. Pasangkan gerak isyarat pada segi empat tepat yang boleh dialihkan, seperti yang diterangkan dalam dokumentasi DragGesture Apple. Saiz kanvas tetap menjadikan geometri contoh ini jelas. Dokumentasi DragGesture
@main
struct RectanglePlacementApp: App {
var body: some Scene {
WindowGroup {
PlacementCanvas()
}
}
}
struct PlacementCanvas: View {
private let target = CGRect(x: 200, y: 100,
width: 200, height: 160)
@State private var item = CGRect(x: 40, y: 120,
width: 80, height: 60)
@State private var message = "Drag the blue item, then release."
@GestureState private var translation: CGSize = .zero
private var preview: CGRect {
item.offsetBy(dx: translation.width,
dy: translation.height)
}
private var move: some Gesture {
DragGesture(minimumDistance: 1,
coordinateSpace: .named("placementCanvas"))
.updating($translation) { value, state, _ in
state = value.translation
}
.onEnded { value in
let finalItem = item.offsetBy(
dx: value.translation.width,
dy: value.translation.height
)
let result = classify(item: finalItem, target: target)
message = result.rawValue
if result != .invalid {
item = finalItem
}
}
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(message)
ZStack(alignment: .topLeading) {
Color.white
Rectangle()
.fill(Color.gray.opacity(0.25))
.frame(width: target.width, height: target.height)
.position(x: target.midX, y: target.midY)
Rectangle()
.fill(Color.blue)
.frame(width: item.width, height: item.height)
.position(x: preview.midX, y: preview.midY)
.gesture(move)
}
.frame(width: 640, height: 380)
.coordinateSpace(.named("placementCanvas"))
Button("Reset") {
item = CGRect(x: 40, y: 120, width: 80, height: 60)
message = "Drag the blue item, then release."
}
}
.padding()
}
}
Seret item biru ke atas sasaran kelabu dan lepaskannya. Mesej itu merekodkan peletakan terakhir yang selesai. Pertindihan separa dan peletakan berasingan kekal di tempat anda melepaskannya; pembendungan tidak menukar keahlian induk secara automatik atau melekapkan item ke dalam bekas lain. Butang Reset memulihkan segi empat tepat permulaan.
Kemas kini kedudukan kekal sengaja tidak dibuat dalam panggil balik kemas kini gerak isyarat. @GestureState hanya menyimpan anjakan pratonton dan ditetapkan semula apabila gerak isyarat menjadi tidak aktif. Apple menetapkan bahawa onEnded hanya berjalan selepas gerak isyarat berjaya diselesaikan, dan mengesyorkan keadaan gerak isyarat untuk nilai yang perlu ditetapkan semula tanpa mengira cara gerak isyarat berakhir. Oleh itu, gerak isyarat yang terganggu boleh membuang pratonton sambil mengekalkan segi empat tepat yang telah dikomit dan hasil sebelumnya. Dokumentasi GestureState, Dokumentasi onEnded
Pelaksanaan ini menganggap segi empat tepat berisi sebagai geometri. Menambah bayang hiasan atau garis luar tidak membesarkan segi empat penerimaan. Jika anda menambah saiz semula, zum atau putaran, kemas kini model geometri dan penukaran koordinat sebelum bergantung pada pengelas yang sama.
Semak kes sempadan sebelum menambah item lain
Gunakan kes sintetik berikut dengan sasaran dalam contoh: asalan (200, 100), lebar 200, tinggi 160. Koordinat menerangkan segi empat tepat item dalam ruang kanvas yang sama.
| Asalan item | Saiz item | Hasil dijangka | Sebab |
|---|---|---|---|
(220, 120) |
80 × 60 |
Terkandung |
Setiap tepi berada dalam sasaran |
(360, 120) |
80 × 60 |
Bertindih |
Item melangkaui tepi kanan sasaran |
(400, 120) |
80 × 60 |
Tiada pertindihan kawasan |
Hanya tepi menegak yang bersentuhan |
(401, 120) |
80 × 60 |
Tiada pertindihan kawasan |
Terdapat jarak antara segi empat tepat |
(200, 100) |
200 × 160 |
Terkandung |
Segi empat tepat yang sama memenuhi syarat pembendungan |
(180, 80) |
240 × 200 |
Bertindih |
Item membingkungi sasaran, membalikkan arah pembendungan |
Pengiraan dalam jadual disemak dengan pengiraan deterministik yang bebas. Program Swift telah diperiksa tetapi tidak dikompil, kerana persekitaran penyelidikan tidak menyediakan pengkompil Swift. Bina program itu dalam sasaran app macOS dan uji kedudukan pelepasan berhampiran setiap sempadan.
Untuk berbilang item dalam memori, kekalkan identiti item yang dipilih ketika seretan bermula. Setelah selesai, bandingkan geometri akhirnya dengan sasaran yang layak dan kecualikan item itu sendiri. Jika beberapa sasaran memenuhi syarat, tetapkan peraturan pemilihan yang stabil, seperti susunan sasaran yang jelas. Pengelasan geometri sahaja tidak menentukan sasaran yang patut memiliki item tersebut.
Gunakan peraturan geometri yang sama dalam kanvas AppKit
Bagi paparan AppKit tersuai yang sedia ada, pastikan pengelas bebas daripada penghantaran peristiwa. mouseUp(with:) melaporkan pelepasan butang tetikus kiri; gunakannya untuk memuktamadkan hanya seretan yang telah dimiliki oleh kanvas anda. Peristiwa mouse-up yang tidak berkaitan tidak sepatutnya mencetuskan peletakan. Dokumentasi mouse-up AppKit
Urutan peristiwa yang praktikal ialah:
- Dalam
mouseDown(with:), pilih item sintetik menggunakan peraturan ujian sentuh sedia ada pada kanvas. Simpan identitinya, segi empat tepat permulaan dan kedudukan penuding permulaan. - Tukarkan setiap lokasi peristiwa tetikus yang berkaitan kepada koordinat kanvas. Bagi paparan yang dilampirkan pada tetingkap, ungkapan penukarannya ialah
convert(event.locationInWindow, from: nil). - Dalam
mouseDragged(with:), dapatkan pratonton daripada segi empat tepat yang disimpan dan sesaran penuding. - Dalam
mouseUp(with:), kira semula sesaran menggunakan peristiwa pelepasan, kelaskan segi empat tepat akhir, kemudian komitkannya sekali dan kosongkan seretan aktif.
Apple mendokumenkan locationInWindow sebagai lokasi peristiwa tetikus dalam koordinat tetingkap. Lebihan penukaran titik bagi NSView.convert(_:from:) menukar daripada koordinat tetingkap apabila argumen paparannya ialah nil. Ini mengelakkan perbandingan titik tetingkap dengan segi empat tepat yang dinyatakan dalam koordinat setempat kanvas. Dokumentasi lokasi peristiwa, Dokumentasi penukaran titik
Kekalkan pengendalian peristiwa pada komponen yang sudah memiliki interaksi tersebut. Apple membezakan subkelas NSView langsung yang mengendalikan peristiwa daripada subkelas yang perlu membenarkan induknya mengendalikan peristiwa yang tidak digunakan. Ikuti tingkah laku responder yang didokumenkan itu; menambah logik peletakan tidak memberi alasan untuk memintas urutan view lain atau memproses pelepasan yang sama melalui dua pengendali berasingan. Panduan pengendalian peristiwa NSView
Ketahui bila segi empat tepat tidak dapat menjawab soalan tentang bentuk
Contoh ini memberikan jawapan tepat untuk segi empat tepat berisi yang sejajar dengan paksi. Bagi bulatan, garis luar cekung, lubang atau bentuk yang diputar, segi empat sempadan hanya memberikan ujian geometri awal. Kawasan persilangannya mungkin merangkumi ruang yang tidak diisi oleh kedua-dua bentuk yang kelihatan.
Ujian titik-dalam-laluan juga tidak dapat menentukan pembendungan penuh sesuatu bentuk terhadap bentuk lain. NSBezierPath.contains(_:) AppKit menerima satu titik dalam sistem koordinat laluan, dan Apple menyatakan bahawa ia tidak mengambil kira lebar strok. Oleh itu, menguji pusat item hanya menjawab sama ada pusat itu berada di dalam laluan. Dokumentasi pembendungan titik NSBezierPath
Bagi laluan berisi pada macOS 14 dan lebih baharu, SwiftUI mendokumenkan operasi boolean yang disokong: Path.intersection(_:eoFill:) menghasilkan kawasan berisi yang sama, manakala Path.subtracting(_:eoFill:) menghasilkan bahagian kawasan berisi pertama yang berada di luar kawasan kedua. Operasi ini menyediakan kaedah berasaskan laluan untuk menyiasat pertindihan dan bahagian item yang berada di luar sasaran. Selaraskan kedua-dua laluan dalam ruang koordinat yang sama dan gunakan peraturan isian yang sepadan dengan pemaparan anda. Dokumentasi persilangan Path, Dokumentasi penolakan Path
Operasi tersebut melibatkan kawasan berisi dan mengandaikan sublaluan yang tidak tertutup telah ditutup. Garis berstrok atau peraturan aplikasi tentang sentuhan sempadan memerlukan takrif geometri tersendiri. Kekalkan pengelas segi empat tepat untuk item segi empat tepat; kembangkan lapisan geometri dengan sengaja apabila kanvas mula melukis bentuk lain.
Hasil pelepasan yang boleh diharap bergantung pada tiga pilihan konkrit: kekalkan geometri permulaan seretan, kira item akhir dalam ruang koordinat sasaran dan gunakan dasar pembendungan serta pertindihan yang jelas. Dengan pilihan tersebut ditetapkan, antara muka boleh melaporkan hasil secara konsisten tanpa mengikat geometri pada penghalaan peristiwa atau pemilikan item.
Sumber
- CGRectContainsRect(::) — Apple
- CGRectIntersection(::) — Apple
- coordinateSpace(_:) — Apple
- translation — Apple
- Enumerations — Swift.org
- DragGesture — Apple
- GestureState — Apple
- onEnded(_:) — Apple
- mouseUp(with:) — Apple
- locationInWindow — Apple
- convert(_:from:) — Apple
- NSView — Apple
- contains(_:) — Apple
- intersection(_:eoFill:) — Apple
- subtracting(_:eoFill:) — Apple