Pastikan lukisan segi empat pantas kekal responsif dengan memisahkan pratonton seretan aktif daripada bentuk kanvas yang telah dimuktamadkan. Dalam app lukisan Mac, biarkan satu permukaan gerak isyarat mengendalikan input, simpan geometri sementara secara setempat, dan tambahkan bentuk pada koleksi hanya apabila seretan selesai dengan jayanya.
Panduan ini ditujukan kepada pembangun yang membina alat segi empat ringkas klik dan seret dalam app macOS mereka sendiri. Semua bentuk contoh disimpan dalam memori. Matlamatnya ialah mengelakkan pergerakan penuding daripada berulang kali mengubah keseluruhan model lukisan, kemudian menggunakan pemprofilan untuk mengenal pasti sebarang kos pemaparan yang masih ada.
Mengapa lukisan menjadi perlahan apabila bentuk yang dimuktamadkan semakin bertambah?
Mulakan dengan membezakan tiga operasi: menerima input, mengemas kini keadaan dan memaparkan geometri. Seretan boleh kerap mengubah geometri sementara. Jika perubahan itu turut menggantikan koleksi yang telah dimuktamadkan, membina semula setiap laluan atau membatalkan hierarki paparan yang luas, kerja bagi setiap pergerakan boleh meningkat seiring dengan lukisan.
Apple menerangkan bahawa SwiftUI menjejak kebergantungan dan mengemas kini paparan apabila kebergantungan tersebut berubah. Kemas kini paparan individu yang perlahan serta kemas kini yang tidak perlu dalam jumlah besar boleh menyebabkan tarikh akhir bingkai terlepas. Mengasingkan keadaan pratonton yang kerap berubah daripada kandungan yang telah dimuktamadkan ialah penerapan seni bina bagi model yang didokumenkan itu. Optimumkan prestasi SwiftUI dengan Instruments
Canvas menyediakan lukisan mod segera melalui konteks grafik. Apple menyatakannya sebagai kemungkinan peningkatan prestasi untuk lukisan dinamik yang kompleks, tetapi ia tidak menyediakan interaksi atau kebolehcapaian bagi setiap elemen yang dilukis. Lampirkan pengendalian input pada permukaan lukisan yang ditentukan dengan jelas; laksanakan sokongan pemilihan dan kebolehcapaian yang berasingan jika app kelak memerlukan bentuk yang boleh dikendalikan secara individu. Canvas
Oleh itu, sempadan yang berguna adalah jelas: permukaan input mengendalikan gerak isyarat, keadaan sementara mengendalikan pratonton, dan paparan induk mengendalikan segi empat yang telah dimuktamadkan. Pemisahan tanggungjawab ini mengurangkan kebergantungan yang boleh dielakkan. Ia tidak menjamin bahawa sistem tidak akan memaparkan semula kanvas yang telah dimuktamadkan.
Asingkan pratonton daripada lukisan yang telah dimuktamadkan
Gunakan rancangan pemilikan ini sebelum memilih pengoptimuman pemaparan:
| Tanggungjawab | Pemilik | Bila berubah |
|---|---|---|
| Segi empat seretan semasa | Permukaan gerak isyarat | Semasa seretan aktif |
| Koleksi segi empat yang telah dimuktamadkan | Paparan lukisan induk | Sekali bagi setiap seretan yang berjaya |
| Melukis segi empat yang telah dimuktamadkan | Kanvas latar belakang | Apabila inputnya memerlukan kemas kini |
| Pengecaman penuding | Permukaan gerak isyarat | Sepanjang urutan gerak isyarat |
Untuk geometri sementara, @GestureState berguna kerana SwiftUI menetapkan semula nilainya apabila gerak isyarat menjadi tidak aktif. Sahkan komit melalui onEnded, yang panggil baliknya menerima nilai akhir gerak isyarat. Apple menyatakan bahawa panggil balik ini hanya berjalan apabila gerak isyarat tamat dengan jayanya; keadaan gerak isyarat mengendalikan pembersihan tanpa mengira cara ia berakhir. GestureState, onEnded(_:)
Bagi segi empat, geometri akhir hanya memerlukan lokasi mula dan lokasi semasa. Tidak perlu menyimpan setiap lokasi penuding perantaraan. Apple mendokumenkan kedudukan tersebut secara berasingan daripada titik akhir ramalan; gunakan lokasi akhir sebenar apabila mengesahkan komit segi empat. DragGesture.Value
Bina alat segi empat dalam memori yang lengkap
Dua blok kod berikut membentuk satu fail sumber Swift yang lengkap. Dalam projek App SwiftUI macOS yang menyasarkan macOS 12 atau lebih baharu, gantikan fail entri app yang dijana dengan kedua-dua blok dan buang sebarang pengisytiharan entri app yang lain. Canvas tersedia mulai macOS 12. Contoh ini telah diperiksa, tetapi tidak dapat dikompil di sini kerana persekitaran ini tidak mempunyai rantai alat Swift. Canvas
Blok pertama mentakrifkan app, koleksi yang telah dimuktamadkan dan pemaparnya. Paparan induk hanya menerima segi empat yang telah siap; ia tidak membaca keadaan seretan aktif.
import SwiftUI
@main
struct RectangleDrawingApp: App {
var body: some Scene {
WindowGroup {
DrawingBoard()
}
}
}
struct DrawingBoard: View {
@State private var rectangles: [CGRect] = []
var body: some View {
VStack(spacing: 8) {
Text("Drag to draw a rectangle")
Text("Committed rectangles: \(rectangles.count)")
ZStack {
Color.white
CommittedRectangles(rectangles: rectangles)
.allowsHitTesting(false)
RectangleGestureSurface { rectangle in
rectangles.append(rectangle)
}
}
.clipped()
.frame(minWidth: 480, minHeight: 320)
}
.padding()
}
}
struct CommittedRectangles: View {
let rectangles: [CGRect]
var body: some View {
Canvas { context, _ in
for rectangle in rectangles {
context.stroke(
Path(rectangle),
with: .color(.blue),
lineWidth: 2
)
}
}
}
}
Tambahkan blok berikut pada fail yang sama. Permukaan lutsinar meliputi kawasan lukisan, memberikan bentuk segi empat yang jelas untuk ujian hit, dan mengendalikan satu-satunya gerak isyarat lukisan. Apple mendokumenkan contentShape sebagai penentu bentuk untuk ujian hit dan allowsHitTesting(false) sebagai pengecualian paparan daripada ujian hit. contentShape(_:eoFill:), allowsHitTesting(_:)
struct RectangleGestureSurface: View {
let commit: (CGRect) -> Void
@GestureState private var preview: CGRect? = nil
var body: some View {
ZStack {
Color.clear
Canvas { context, _ in
if let rectangle = preview {
context.stroke(
Path(rectangle),
with: .color(.orange),
lineWidth: 2
)
}
}
.allowsHitTesting(false)
}
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.updating($preview) { value, state, _ in
state = Self.rectangle(
from: value.startLocation,
to: value.location
)
}
.onEnded { value in
let rectangle = Self.rectangle(
from: value.startLocation,
to: value.location
)
commit(rectangle)
}
)
}
private static func rectangle(
from start: CGPoint,
to end: CGPoint
) -> CGRect {
CGRect(
x: min(start.x, end.x),
y: min(start.y, end.y),
width: abs(end.x - start.x),
height: abs(end.y - start.y)
)
}
}
minimumDistance: 0 menghapuskan keperluan jarak minimum untuk pengecaman. Ruang koordinat setempat memastikan kedudukan gerak isyarat sejajar dengan kanvas pratonton yang bersaiz sama. Ini ialah pilihan konfigurasi gerak isyarat yang didokumenkan, bukannya jaminan tentang kekerapan peristiwa atau kependaman paparan. DragGesture
Fungsi pembantu segi empat menormalkan keempat-empat arah seretan. Sebagai semakan ilustrasi, seretan dari (90, 80) ke (30, 20) menghasilkan titik asal (30, 20) dan saiz (60, 60). Pratonton dan komit menggunakan pengiraan yang sama, jadi melepaskan penuding tidak mengubah formula geometri.
Alat minimum ini turut mengesahkan komit bagi segi empat yang lebarnya sifar atau tingginya sifar. Oleh itu, klik boleh menambah kiraan tanpa menghasilkan segi empat yang kelihatan. Jika produk memerlukan saiz minimum, tambah peraturan itu secara jelas semasa komit dan berikan maklum balas yang sesuai kepada pengguna. Contoh ini menyimpan setiap gerak isyarat yang selesai dengan jayanya dan bukannya menapis hasil kecil secara senyap.
Kawasan yang kelihatan dipotong, manakala geometri yang disimpan kekal tidak berubah. Oleh itu, segi empat yang menjangkaui papan boleh mengandungi koordinat di luar papan. Tentukan sama ada perubahan saiz pada masa hadapan patut mendedahkan geometri itu sebelum menambah pembatasan koordinat.
Diagnosis kos yang masih ada sebelum menambah cache
Contoh ini mengeluarkan mutasi model yang telah dimuktamadkan daripada laluan kemas kini seretan. Pemapar yang telah dimuktamadkan masih mengulang seluruh koleksi setiap kali pemapar itu berjalan. Bezakan pengurangan penyebaran keadaan daripada pengurangan kerja pemaparan: paparan yang berasingan bukan cache imej kekal yang didokumenkan.
Gunakan perbandingan terkawal untuk mengetahui kos yang masih ada. Pilih populasi dalam memori yang bersifat ilustrasi, seperti 0, 500 dan 5,000 segi empat. Kekalkan dimensi segi empat, saiz tetingkap, skala paparan dan konfigurasi binaan yang konsisten. Bilangan ini ialah input ujian, bukannya had kapasiti atau hasil pengukuran.
Rekodkan tiga sela masa secara berasingan: menyeret semasa koleksi kekal tidak berubah, melepaskan untuk membuat komit dan mengubah saiz tetingkap. Kelembapan berterusan semasa seretan menunjukkan kerja yang berbeza daripada jeda yang hanya berlaku semasa komit. Tingkah laku semasa perubahan saiz boleh mendedahkan pembinaan semula geometri atau cache yang tidak berlaku semasa lukisan biasa.
Sesi pemprofilan SwiftUI oleh Apple menerangkan alat Long View Body Updates, Long Representable Updates dan Cause & Effect Graph, bersama Time Profiler. Gunakan graf kebergantungan untuk menyiasat sebab paparan yang telah dimuktamadkan dikemas kini semasa seretan; gunakan sampel CPU untuk mencari pembinaan geometri atau kerja pemaparan yang mahal. Apple memperkenalkan aliran kerja instrumen itu dalam Instruments 26. Optimumkan prestasi SwiftUI dengan Instruments
Bagi gangguan yang lebih meluas, sesi WWDC26 Apple tentang responsiviti bermula dengan Time Profiler dan membezakan pelaksanaan yang sibuk daripada masa yang dihabiskan untuk menunggu. Gunakan perbezaan itu sebelum memindahkan kerja antara pelaksana atau menggantikan tindanan pemaparan. Profil, baiki dan sahkan: Tingkatkan responsiviti app dengan Instruments
Panduan keputusan berikut ialah inferens kejuruteraan daripada alat pemprofilan tersebut dan reka bentuk pemilikan di atas:
| Tingkah laku yang diperhatikan | Siasat dahulu | Perubahan yang boleh dipertimbangkan |
|---|---|---|
| Keadaan lukisan induk berubah semasa penuding bergerak | Panggil balik kemas kini gerak isyarat dan keadaan bersama | Kekalkan keadaan pratonton dalam permukaan gerak isyarat |
| Lukisan yang telah dimuktamadkan mendominasi sampel semasa pergerakan | Traversal berulang dan penciptaan laluan | Prakira geometri atau perkenalkan cache kandungan yang telah dimuktamadkan |
| Jeda pelepasan semakin lama apabila koleksi bertambah | Mutasi tatasusunan dan pembinaan semula lukisan penuh | Kurangkan syot kilat yang disimpan dan bina semula kandungan terbitan yang terjejas sahaja |
| Lukisan segi empat responsif, tetapi strok bebas yang panjang perlahan | Saiz laluan aktif dan pembinaan semula | Profilkan strok aktif yang semakin berkembang secara berasingan |
| Jeda kebanyakannya berlaku semasa perubahan saiz | Pemaparan yang bergantung pada saiz dan pembatalan cache | Tetapkan tingkah laku perubahan saiz yang jelas untuk kandungan terbitan |
Cache menambah satu lagi sempadan pemilikan: geometri segi empat kekal sebagai sumber autoritatif, manakala cache ialah kandungan terbitan yang boleh diganti. Sertakan saiz lukisan, skala paparan dan gaya yang berkaitan dalam reka bentuk pembatalan cache. Bagi lukisan tambah sahaja, kemas kini cache secara beransur-ansur ialah pengoptimuman yang boleh dipertimbangkan; pengeditan dan pembuangan memerlukan strategi pembinaan semula atau pembatalan cache mengikut kawasan yang jelas. Sahkan hasil dengan populasi input yang sama sebelum mengekalkan kerumitan tambahan itu.
Periksa penyalinan tatasusunan pada sempadan komit
Tatasusunan Swift mempunyai semantik nilai dan menggunakan storan salin semasa tulis. Salinan boleh berkongsi storan sehingga mutasi memerlukan storan yang berasingan; tatasusunan yang mengandungi tika kelas masih berkongsi rujukan kepada tika tersebut. Pemindahan tatasusunan kepada paparan mahupun penambahan pada tatasusunan tidak patut digambarkan sebagai sentiasa menyalin setiap elemen. Array
Bagi alat ini, elakkan menyimpan syot kilat lukisan penuh yang baharu pada setiap kemas kini penuding. Jika pemprofilan mengenal pasti penyalinan semasa komit, periksa syot kilat yang masih hidup dan cara model lukisan dikongsi. Menggantikan jenis nilai dengan objek boleh ubah kongsi semata-mata untuk mengelakkan penyalinan akan mengubah semantik pemilikan dan memerlukan keputusan reka bentuk yang berasingan.
Anggap pemaparan tak segerak sebagai pilihan yang perlu diukur
Apple mentakrifkan rendersAsynchronously sebagai membolehkan kanvas memaparkan kandungannya kepada paparan induknya secara tak segerak. Huraian itu tidak menjanjikan penghantaran gerak isyarat yang lebih pantas, tidak memindahkan pengendali gerak isyarat ke pelaksana latar belakang dan tidak menghapuskan traversal koleksi. Nilai pilihan ini secara berasingan selepas menetapkan sempadan input dan keadaan. rendersAsynchronously
Bilakah AppKit patut mengendalikan permukaan lukisan?
Gunakan permukaan AppKit tersuai apabila tugasan memerlukan pengendalian kaedah responder secara langsung atau pembatalan kawasan lukisan secara jelas. Apple mendokumenkan NSView sebagai infrastruktur untuk melukis dan mengendalikan peristiwa, serta menyokong subkelasannya untuk tingkah laku tersuai. Ini ialah alternatif yang disokong dengan tanggungjawab pemaparannya sendiri. NSView
Bagi pelaksanaan AppKit, kekalkan urutan yang sama: mulakan geometri sementara dalam mouseDown(with:), kemas kini dalam mouseDragged(with:) dan buat komit dalam mouseUp(with:). Tukarkan event.locationInWindow kepada koordinat paparan penerima sebelum mengira geometri. Panduan peristiwa tetikus arkib Apple mendokumenkan urutan ini dan penukaran koordinat; rujukan NSView semasa mendokumenkan kaedah responder yang disokong. Mengendalikan Peristiwa Tetikus, NSView
Minta paparan dikemas kini dengan menetapkan needsDisplay = true; Apple menyatakan bahawa paparan itu kemudian dipaparkan semula pada gelung peristiwa yang seterusnya. Bagi reka bentuk berasaskan kawasan, batalkan gabungan sempadan pratonton lama dan baharu, dengan keluasan tambahan untuk mengambil kira strok. Pembatalan mengikut kawasan hanya mengurangkan traversal jika pelaksanaan lukisan turut mengelakkan pemprosesan kandungan yang tidak terjejas. needsDisplay, Mengendalikan Peristiwa Tetikus
Benamkan permukaan dengan NSViewRepresentable: cipta dalam makeNSView, selaraskan keadaan luaran dalam updateNSView dan gunakan penyelaras untuk menyampaikan perubahan kembali kepada SwiftUI. Segarkan penghalaan panggil balik semasa kemas kini dan elakkan menetapkan semula draf aktif hanya kerana SwiftUI membekalkan nilai representable yang lain. Apple turut menyatakan bahawa SwiftUI mengawal susun atur paparan yang diwakilkan; jangan tulis terus ganti bingkai atau sempadan yang diurusnya. NSViewRepresentable
Pilih satu pemilik untuk input lukisan. Jika AppKit mengendalikan urutan lukisan, jangan turut membuat komit bagi urutan itu melalui gerak isyarat seretan SwiftUI yang bertindih. Bagi subkelas NSView langsung, Apple mengarahkan pelaksanaan yang mengendalikan peristiwa tetikus yang disenaraikan supaya tidak memanggil pelaksanaan kelas induk untuk peristiwa yang dikendalikan itu. NSView
Pastikan penambahbaikan pertama kecil dan mudah diperhatikan: keadaan pratonton setempat, satu sempadan komit dan pemapar yang telah dimuktamadkan dengan input yang tidak berubah semasa pergerakan. Kemudian bandingkan lukisan sintetik yang semakin besar dalam Instruments. Jejak yang terhasil menunjukkan sama ada perubahan berguna yang seterusnya berkaitan dengan pengurusan kebergantungan, pemilikan koleksi, penyediaan geometri atau pemaparan.
Sumber
Canvas — Dokumentasi Pembangun Apple.
GestureState — Dokumentasi Pembangun Apple.
onEnded(_:) — Dokumentasi Pembangun Apple.
DragGesture — Dokumentasi Pembangun Apple.
DragGesture.Value — Dokumentasi Pembangun Apple.
contentShape(_:eoFill:) — Dokumentasi Pembangun Apple.
allowsHitTesting(_:) — Dokumentasi Pembangun Apple.
Array — Dokumentasi Pembangun Apple.
rendersAsynchronously — Dokumentasi Pembangun Apple.
Optimumkan prestasi SwiftUI dengan Instruments — Pembangun Apple.
Profil, baiki dan sahkan: Tingkatkan responsiviti app dengan Instruments — Pembangun Apple.
NSView — Dokumentasi Pembangun Apple.
needsDisplay — Dokumentasi Pembangun Apple.
NSViewRepresentable — Dokumentasi Pembangun Apple.
Mengendalikan Peristiwa Tetikus — Arkib Dokumentasi Apple.