Untuk menyimpan bentuk SwiftUI yang berbeza dalam satu set sambil menguatkuasakan antara muka yang dikongsi, gunakan pembungkus Hashable konkrit dengan pemula yang dikekang oleh protokol. Tutorial ini membina palet bentuk Mac kecil dengan peraturan pendua yang jelas dan operasi bentuk yang boleh dipanggil.
Tugasnya khusus: kumpulkan komponen cakera dan jubin sintetik, tolak nilai yang tidak berkaitan semasa kompilasi, buang pendua dan paparkan komponen yang tinggal dalam tetingkap SwiftUI. Koleksi ini menerima berbilang jenis komponen melalui protokol bersama yang memperincikan Shape milik SwiftUI.
Mengapa set nilai protokol tidak memenuhi kontrak ini?
Set Swift memerlukan jenis elemennya mematuhi Hashable. Mengisytiharkan bahawa setiap nilai dalam eksistensial mematuhi Hashable tidak memberikan pematuhan itu kepada jenis eksistensial tersebut. Oleh itu, Set<any PaletteShape> tidak menjadi sah hanya kerana PaletteShape mewarisi Hashable. Apple mendokumenkan kekangan elemen koleksi itu, manakala Swift Evolution menerangkan perbezaan antara bekas eksistensial dengan nilai pematuhinya yang mendasari. Set, Implicitly Opened Existentials.
Eksistensial seperti any PaletteShape boleh menyimpan jenis pematuh yang berbeza. Kesukarannya ialah kesamaan: keperluan Equatable yang diwarisi membandingkan dua nilai daripada jenis Self yang sama, sedangkan dua bekas eksistensial mungkin menyimpan jenis konkrit yang berbeza. Sokongan Swift yang diperluas untuk nilai eksistensial tidak menyediakan operasi kesamaan sejagat untuk bekas tersebut. Unlock existentials for all protocols.
Pilihan berikut menyelesaikan bahagian masalah yang berbeza:
| Perwakilan | Jenis konkrit bercampur? | Antara muka bersama dikuatkuasakan? | Sesuai sebagai elemen set? |
|---|---|---|---|
[any PaletteShape] |
Ya | Ya | Tatasusunan tidak menyediakan keunikan set |
Set<S> dengan S: PaletteShape |
Satu S yang dipilih |
Ya | Ya, untuk satu jenis konkrit itu |
Set<AnyHashable> |
Ya | Tiada kekangan bentuk | Ya |
Set<AnyPaletteShape> di bawah |
Ya, dalam pembungkus | Ya, semasa pembinaan | Ya |
AnyHashable menyediakan kesamaan dan pencincangan terpadam, tetapi pemulanya menerima nilai hashable secara umum. Oleh itu, set yang hanya mengandungi jenis ini boleh menerima rentetan atau integer. Pembungkus tersuai di bawah mengehadkan kemasukan kepada protokol bentuk aplikasi dan mengekalkan antara muka bentuk bersama-sama nilai hashable yang terpadam. AnyHashable.
Apakah yang patut dianggap sebagai bentuk pendua?
Tentukan kesamaan sebelum melaksanakan pembungkus. Bagi palet ini, dua komponen dianggap pendua apabila jenis komponen konkritnya sama dan nilai tersimpannya sama. Cakera berlabel Sample dan jubin berlabel Sample kekal sebagai entri berasingan. Dua cakera dengan label yang sama digabungkan menjadi satu.
Label ialah satu-satunya sifat tersimpan dalam setiap komponen contoh, jadi kesamaan dan pencincangan tersintesis merangkuminya. Ini ialah pilihan model untuk tujuan ilustrasi: persamaan visual sahaja tidak mentakrifkan kesamaan. Apple mendokumenkan sintesis automatik Hashable bagi struktur yang sifat tersimpannya hashable, dan menghendaki nilai yang sama memasukkan komponen yang sama ke dalam pencincangnya. Hashable.
Pembungkus itu turut menyertakan pengecam jenis dalam kesamaan dan pencincahan. Ini menjadikan peraturan jenis sama dinyatakan secara jelas, bukannya bergantung pada andaian tentang perbandingan nilai terpadam. AnyHashable boleh membandingkan nilai tertentu merentasi jenis asas yang berbeza menggunakan perwakilan kanonik; Apple tidak mentakrifkannya sebagai pembeza jenis konkrit tanpa syarat. ObjectIdentifier menyokong metatipe, yang membolehkan contoh ini mengenal pasti jenis input generik. AnyHashable, ObjectIdentifier.
Bina contoh palet Mac yang lengkap
Gunakan projek aplikasi SwiftUI macOS dalam Xcode dengan pengkompil Swift 5.7 atau lebih baharu. Untuk garis dasar yang boleh dihasilkan semula, pilih mod bahasa Swift 5 dan sasaran penggunaan macOS 13.0 atau lebih baharu. Sasaran penggunaan ini ialah pilihan konservatif untuk tutorial ini, bukannya dakwaan bahawa setiap API di bawah memerlukan macOS 13.
Tahap minimum pengkompil penting kerana contoh ini menyimpan any PaletteShape, yang protokol diwarisinya merangkumi keperluan jenis berkaitan dan Self. SE-0309 merekodkan sokongan eksistensial yang diperluas ini sebagai dilaksanakan dalam Swift 5.7. Contoh ini tidak memerlukan ciri pematuhan eksistensial umum yang lebih baharu. Unlock existentials for all protocols.
Gantikan pengisytiharan aplikasi dan paparan kandungan yang dijana dengan fail sumber lengkap berikut. Kekalkan hanya satu pengisytiharan @main dalam sasaran aplikasi. Contoh ini merangkumi definisi komponennya, pembungkus, nilai contoh, semakan, paparan dan titik masuk aplikasi. Swift tidak tersedia dalam persekitaran penyelidikan, jadi kod ini telah diperiksa tetapi tidak dikompil atau dilaksanakan di sini.
import SwiftUI
protocol PaletteShape: Shape, Hashable {
var label: String { get }
}
struct Disk: PaletteShape {
let label: String
func path(in rect: CGRect) -> Path {
Path(ellipseIn: rect)
}
}
struct Tile: PaletteShape {
let label: String
func path(in rect: CGRect) -> Path {
var result = Path()
result.addRect(rect)
return result
}
}
struct AnyPaletteShape: Hashable {
private let base: any PaletteShape
private let typeID: ObjectIdentifier
private let key: AnyHashable
init<S: PaletteShape>(_ value: S) {
base = value
typeID = ObjectIdentifier(S.self)
key = AnyHashable(value)
}
var label: String { base.label }
func path(in rect: CGRect) -> Path {
base.path(in: rect)
}
static func == (
lhs: AnyPaletteShape,
rhs: AnyPaletteShape
) -> Bool {
lhs.typeID == rhs.typeID && lhs.key == rhs.key
}
func hash(into hasher: inout Hasher) {
hasher.combine(typeID)
hasher.combine(key)
}
}
func makePalette() -> Set<AnyPaletteShape> {
var result: Set<AnyPaletteShape> = [
AnyPaletteShape(Disk(label: "Disk")),
AnyPaletteShape(Tile(label: "Tile"))
]
let duplicate = result.insert(
AnyPaletteShape(Disk(label: "Disk"))
)
precondition(!duplicate.inserted)
precondition(result.count == 2)
precondition(result.contains(
AnyPaletteShape(Tile(label: "Tile"))
))
let sameLabel: Set<AnyPaletteShape> = [
AnyPaletteShape(Disk(label: "Sample")),
AnyPaletteShape(Tile(label: "Sample"))
]
precondition(sameLabel.count == 2)
return result
}
struct ContentView: View {
private let shapes = makePalette()
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Shape Palette")
.font(.title)
ForEach(
shapes.sorted { $0.label < $1.label },
id: \.self
) { item in
HStack(spacing: 16) {
GeometryReader { geometry in
item.path(in: CGRect(
origin: .zero,
size: geometry.size
))
.fill(Color.blue)
}
.frame(width: 56, height: 56)
Text(item.label)
}
}
}
.padding(24)
.frame(minWidth: 300, minHeight: 220)
}
}
@main
struct ShapePaletteApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
PaletteShape mewarisi protokol Shape sebenar milik SwiftUI. Cakera dan jubin menyediakan path(in:), iaitu keperluan lukisan yang didokumenkan oleh Apple. Pelaksanaannya menggunakan operasi Path yang didokumenkan untuk menghasilkan elips dan segi empat tepat. Bingkai paparan segi empat sama menjadikan elips contoh kelihatan bulat. Shape, Path.
Pemula terkekang ialah peraturan kemasukan: init<S: PaletteShape> hanya menerima jenis yang mematuhi protokol bersama. Sifat tersimpan adalah peribadi, jadi pemanggil tidak boleh menggantikan bentuk yang dikekalkan atau kunci kesamaannya secara berasingan. AnyPaletteShape("Disk") sepatutnya menghasilkan ralat pematuhan semasa kompilasi kerana String tidak mematuhi PaletteShape; jangan sertakan penolakan yang disengajakan ini dalam fail yang boleh dijalankan.
Walaupun set mempunyai satu jenis elemen konkrit, setiap pembungkus boleh mengekalkan bentuk konkrit yang berbeza. Ahli label dan path(in:) meneruskan operasi yang diperlukan oleh palet ini. Pembungkus itu sendiri mengisytiharkan Hashable; ia tidak mengisytiharkan Shape.
Bagaimanakah anda mengesahkan penyisipan, keahlian dan tertib paparan?
Jalankan aplikasi dalam binaan nyahpepijat. Semakan yang disertakan menyatakan tiga hasil yang dijangka: penyisipan cakera kedua yang sama melaporkan inserted sebagai false, keahlian menemui jubin yang sama, dan jenis komponen yang berbeza kekal walaupun labelnya sepadan. Apple mendokumenkan insert(_:) sebagai mengembalikan bendera penyisipan dan ahli yang terhasil. Set.
Tetingkap yang dijangka mengandungi dua baris, Disk diikuti Tile, dengan grafik biru. Pengisihan ialah operasi paparan yang berasingan kerana set tidak mempunyai tertib elemen yang ditakrifkan. Tatasusunan hasil juga memenuhi keperluan koleksi akses rawak ForEach; id: \.self menggunakan nilai hashable pembungkus sebagai pengecam baris. Set, ForEach.
Tertib ini memadai untuk nilai yang diberikan kerana labelnya berbeza. Jika komponen akan datang berkongsi label, tambahkan medan tertib kedua yang deterministik. Jika baris boleh disunting dan perlu mengekalkan identitinya apabila label berubah, perkenalkan pengecam yang stabil dan tentukan secara berasingan sama ada pengecam itu mengambil bahagian dalam kesamaan set.
Jangan jangkakan penyisipan ke dalam set akan menggantikan muatan komponen sedia ada yang sama. Apabila penggantian ialah operasi yang dimaksudkan, Apple menyediakan update(with:). Perbezaan ini penting jika model kemudian mengecualikan sifat persembahan daripada kesamaan: penyisipan nilai yang sama boleh mengekalkan ahli sedia ada dan bukannya menyegarkan rupanya. Set.
Apakah batasan pembungkus ini?
Storan Hashable tidak mengekalkan setiap keupayaan SwiftUI
Pembungkus ini meneruskan penjanaan laluan untuk palet statik. Ia tidak meneruskan data boleh animasi bentuk asas, operasi inset atau setiap ahli yang diwarisi daripada View. Oleh itu, melukis laluan yang dikembalikan tidak membuktikan bahawa pemadam bentuk itu mengekalkan animasi. Shape SwiftUI turut mewarisi Animatable dan View; melaksanakan satu kaedah lukisan yang diteruskan tidak membina semula keseluruhan kontrak itu. Shape.
Jika API lain memerlukan bentuk terpadam yang konkrit, SwiftUI menyediakan AnyShape, yang tersedia pada macOS 13 dan lebih baharu. Peranan yang didokumenkan ialah memadam jenis konkrit sesuatu bentuk, dan pematuhan yang disenaraikan tidak merangkumi Hashable. Ia boleh menangani sempadan pemaparan, tetapi tidak menyediakan dasar kesamaan atau elemen set yang dikekang oleh protokol seperti yang dibina di sini. AnyShape.
Sokongan pengkompil baharu tidak menghapuskan kekangan elemen set
SE-0352, yang dilaksanakan dalam Swift 5.7, membolehkan jenis asas eksistensial dibuka untuk panggilan generik yang sesuai. Teks semasanya turut merekodkan tingkah laku mod bahasa Swift 6. Membuka nilai untuk panggilan tidak menjadikan any PaletteShape sendiri jenis Hashable yang sesuai untuk parameter elemen set. Kekalkan pembungkus konkrit apabila menggunakan reka bentuk ini merentasi generasi pengkompil tersebut. Implicitly Opened Existentials.
Memperluas model memerlukan keputusan tentang kesamaan
Contoh ini menggunakan struktur tidak berubah. Apabila menambah dimensi atau konfigurasi tersimpan yang lain, tentukan sama ada perubahan padanya menghasilkan ahli set yang berbeza. Pencincahan tersintesis akan merangkumi sifat tersimpan tambahan yang hashable. Bagi kesamaan tersuai, cincang komponen penting yang sama, dalam tertib yang sama; nilai cincangan yang sepadan sahaja tidak membuktikan kesamaan. Hashable.
Jika aplikasi mempunyai set varian komponen yang tetap buat selama-lamanya, enum dengan nilai berkaitan yang hashable juga merupakan reka bentuk yang munasabah: ia memusatkan kes yang disokong. Pembungkus berguna apabila jenis komponen pematuh tambahan patut diterima melalui pemula yang sama tanpa mengembangkan senarai kes pusat. Pilihan itu bergantung pada keperluan peluasan aplikasi.
Bagi palet Mac ini, pelaksanaan memberikan tempat yang jelas kepada setiap keperluan: protokol mentakrifkan bentuk yang diterima, pemula menguatkuasakan kemasukan, kesamaan mentakrifkan pendua dan penerusan laluan menyediakan pemaparan. Tambahkan komponen baharu yang tidak berubah dengan membungkusnya semasa penyisipan, kemudian luaskan semakan apabila makna pendua berubah.
Sumber
- Set — Apple
- AnyHashable — Apple
- Hashable — Apple
- ObjectIdentifier — Apple
- Shape — Apple
- Path — Apple
- ForEach — Apple
- AnyShape — Apple
- Unlock existentials for all protocols — Swift Evolution
- Implicitly Opened Existentials — Swift Evolution