Maak de inhoud van de rij zo breed als de beschikbare ruimte en pas een rechthoekige content shape toe om lege ruimte in een SwiftUI-rij klikbaar te maken op de Mac. Plaats beide modifiers in het label van een Button als de rij een actie uitvoert.
In deze tutorial bouwen we een kleine macOS-app waarvan de rijen synthetische vormen en tekst bevatten. Als je op een vorm, het bijbehorende label of de lege ruimte ernaast klikt, wordt dezelfde rijactie uitgevoerd. Ruimte buiten de rij valt buiten het doelgebied van die actie.
Waarom reageert de tekst wel, maar de lege ruimte niet?
Lay-out en hit testing beantwoorden verschillende vragen. Een frame bepaalt hoe een weergave de voorgestelde ruimte inneemt; een content shape bepaalt welk gebied wordt gebruikt voor hit testing. Apple documenteert deze afzonderlijk in de modifier voor flexibele frames en de modifier voor content shapes.
Een rij kan bijvoorbeeld breed lijken omdat de container de ruimte in een venster vult, terwijl de interactie alleen aan een korte tekstweergave is gekoppeld. Ook geeft lege lay-outruimte op zichzelf niet expliciet aan dat de hele rij moet meedoen aan hit testing. De praktische oplossing is om eerst de gewenste grenzen van de rij vast te stellen en dat gebied vervolgens een interactievorm te geven.
Gebruik .frame(maxWidth: .infinity, alignment: .leading) om het label te laten uitvouwen wanneer de bovenliggende weergave extra breedte voorstelt. Pas vervolgens .contentShape(.interaction, Rectangle()) toe op dat uitgevouwen label. Apple definieert het soort content shape interaction specifiek voor hit testing en toegankelijkheid.
Hierdoor wordt de rij niet oneindig breed. Het frame blijft meedoen aan het lay-outvoorstel van de bovenliggende weergave. Een smalle bovenliggende weergave levert een smalle rij op, en een content shape kan geen ontbrekende lay-outruimte creëren. Deze combinatie voldoet aan twee vereisten: een label dat breed genoeg is en een expliciet hit-testgebied.
Bouw een volledig voorbeeld van een rij met knop
Maak in Xcode een macOS-appproject met SwiftUI en Swift. Stel het implementatiedoel in op macOS 12 of hoger voor de expliciete content-shape-overload .interaction, die volgens Apple beschikbaar is vanaf macOS 12. Vervang de gegenereerde appdeclaratie en contentweergave door het onderstaande programma in één bestand; houd slechts één appdeclaratie met @main over. Raadpleeg Apples documentatie over de content-shape-overload voor de beschikbaarheid.
Het voorbeeld gebruikt een Button, omdat elke rij een actie uitvoert. Apple raadt knoppen aan voor interface-elementen die acties uitvoeren en ondersteunt expliciet willekeurige weergaven als knoplabels. In de documentatie over tikgebaren raadt Apple ook een aangepaste knop aan wanneer een gebarenbesturing functioneel hetzelfde zou doen.
import SwiftUI
@main
struct ClickableRowsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct SampleItem: Identifiable {
let id: Int
let title: String
let color: Color
}
struct ContentView: View {
private let items = [
SampleItem(id: 1, title: "Blue square", color: .blue),
SampleItem(id: 2, title: "Orange square", color: .orange),
SampleItem(id: 3, title: "Purple square", color: .purple)
]
@State private var lastActivated = "None"
@State private var activationCount = 0
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Choose a shape")
.font(.headline)
ScrollView {
VStack(spacing: 8) {
ForEach(items) { item in
Button {
lastActivated = item.title
activationCount += 1
} label: {
HStack(spacing: 12) {
Rectangle()
.fill(item.color)
.frame(width: 24, height: 24)
Text(item.title)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.frame(maxWidth: .infinity,
alignment: .leading)
.contentShape(.interaction, Rectangle())
}
.buttonStyle(.plain)
.background(Color.gray.opacity(0.12))
}
}
}
Text("Last activated: \(lastActivated)")
Text("Activations: \(activationCount)")
}
.padding(16)
.frame(minWidth: 360, minHeight: 280)
}
}
Alle items en de interactiestatus blijven in het geheugen. Elk item heeft een vaste integeridentiteit, in lijn met Swifts protocol Identifiable, dat een stabiele identiteit vertegenwoordigt. De bovenliggende weergave beheert de activeringsstatus, volgens Apples richtlijn om lokale State privé te houden in de weergave die deze status moet beheren.
Start de app en klik op de lege ruimte rechts van een titel. De activeringsteller moet één keer oplopen en de status moet de naam van die rij tonen. De grijze achtergrond maakt tijdens deze controle het bedoelde rijgebied zichtbaar; de expliciete content shape bepaalt het interactiegebied.
Dit programma is gecontroleerd aan de hand van de opgehaalde API-documentatie, maar is hier niet gecompileerd of uitgevoerd omdat er geen Swift-toolchain beschikbaar was. Controleer het gedrag met muisaanwijzer en toetsenbord in je macOS-doelomgeving voordat je het integreert.
Waar moeten het frame, de opvulling en de content shape komen?
In dit voorbeeld is de volgorde bewust gekozen: bouw de inhoud van het label op, voeg de interne opvulling toe, laat het resulterende label uitvouwen en definieer vervolgens de interactievorm over dat gebied. Dit gebeurt allemaal binnen de closure van het knoplabel. Apple documenteert dat de volgorde van modifiers ertoe doet.
Als je de opvulling binnen dit label met de content shape plaatst, valt het opgevulde gebied binnen het beoogde doelgebied. Opvulling buiten de knop creëert daarentegen omringende lay-outruimte; ga er niet vanuit dat die ruimte bij het interactieve knoplabel hoort. Ook levert het uitvouwen van alleen een buitenste container niet dezelfde labelgeometrie op als het uitvouwen van de inhoud binnen de knop.
De modifier .buttonStyle(.plain) houdt het voorbeeld visueel eenvoudig. Apple beschrijft de eenvoudige knopstijl als onopgemaakt wanneer de knop niet actief is, met mogelijke effecten voor ingedrukte, gefocuste of ingeschakelde statussen. Het is een stijlkeuze, geen vervanging voor het definiëren van de breedte en content shape van het label.
Vervang voor een implementatiedoel ouder dan macOS 12 .contentShape(.interaction, Rectangle()) door .contentShape(Rectangle()). Apple documenteert de oudere overload als een overload die de hit-testvorm definieert en vermeldt dat deze beschikbaar is vanaf macOS 10.15. Deze vervanging wijzigt de vereiste content-shape-API; controleer de overige API's van je app afzonderlijk.
Moet een rij een knop, lijstselectie of gebaar gebruiken?
Kies de interactie op basis van de betekenis van een klik op de rij. Door lege ruimte te laten reageren, moet die betekenis behouden blijven, evenals het eigenaarschap van eventuele bedieningselementen in de rij.
| Gedrag voor de gebruiker | Geschikt startpunt | Te behouden grens |
|---|---|---|
| De hele rij voert één actie uit | Een Button met een uitgevouwen label met een content shape |
Houd één eigenaar voor de actie van de rij aan |
| De rij staat voor de huidige selectie | Een List met een selectiebinding |
Laat de lijst het selectiegedrag beheren |
| Een aangepaste weergave heeft een afzonderlijk klikgebaar nodig | Een weergave met een content shape en onTapGesture |
Koppel het gebaar aan het bedoelde gebied |
| De rij bevat meerdere onafhankelijke bedieningselementen | Afzonderlijke naast elkaar geplaatste bedieningselementen | Geef elk bedieningselement een eigen doelgebied |
Gebruik voor lijstselectie eerst het selectiemodel van de container voordat je een klikafhandeling voor de hele rij toevoegt. Apples documentatie over List beschrijft een binding aan een optionele itemidentificatie voor een enkele selectie en een set identificaties voor meerdere selecties. Een knop die een actie uitvoert en een lijstregel die wordt geselecteerd, zijn verschillende interactievereisten.
Een gebaar is geschikt wanneer je specifiek gebarenherkenning op een aangepaste weergave nodig hebt. Pas dezelfde volgorde voor de geometrie toe: eerst opvulling en frame, daarna de content shape en vervolgens onTapGesture. Apple documenteert de parameter count als het vereiste aantal tikken of klikken. Een content shape bepaalt het doelgebied, maar levert op zichzelf geen actie. Zie onTapGesture(count:perform:).
Als de rij ook een afzonderlijke knop of schakelaar bevat, houd die dan buiten het label van de primaire knop. Een bruikbare structuur is een horizontale stack met een uitvouwbare primaire knop en een afzonderlijk bedieningselement aan het uiteinde. De lege ruimte in het primaire label hoort bij de actie ervan; het bedieningselement aan het uiteinde heeft een eigen gebied. Plaats geen interactieve bedieningselementen in een knop die de hele rij omvat.
Voeg geen modifiers voor gebarenprioriteit toe alleen om klikken op lege ruimte te repareren. Apple documenteert highPriorityGesture als een manier om de voorrang te wijzigen, terwijl simultaneousGesture gebaren gelijktijdig laat verwerken. Die bewerkingen wijzigen de onderlinge relatie tussen gebaren en kunnen interacties met onderliggende weergaven beïnvloeden; ze definiëren geen ontbrekende rijgeometrie.
Stel vast waarom een rij klikken nog steeds mist
Volg deze stappen met de kleinst mogelijke versie van je rij. Zo kun je een probleem met de grootte onderscheiden van een probleem met de doelvorm of een overlappende weergave.
- Controleer de werkelijke grenzen van het label. Voeg tijdelijk
.border(Color.orange)direct na de framemodifier van het label toe. Als de rand naast de tekst eindigt, controleer dan het breedtevoorstel van de bovenliggende weergave en eventuele beperkingen voor een vaste breedte. - Controleer waar de modifiers staan. Bevestig dat de content shape het uitgevouwen label bedekt, inclusief de opvulling die je klikbaar wilt maken.
- Controleer wie de interactie beheert. Gebruik de actie van de knop voor een rij met een knop. Verwijder overbodige tikgebaren voor de rij terwijl je het probleem onderzoekt.
- Controleer op overlappende decoratie. Controleer overlays en naastgelegen weergaven boven de rij. Overweeg
.allowsHitTesting(false)uitsluitend toe te passen op decoratie die nooit invoer mag ontvangen. - Herstel andere bedieningselementen één voor één. Test het gebied van elk bedieningselement nadat je het hebt hersteld en test daarna de resterende lege ruimte.
Apple documenteert allowsHitTesting(_:) als een instelling die bepaalt of een weergave meedoet aan hit-testbewerkingen. Als je dit op de hele rij toepast, haalt dat de rij uit die bewerkingen. Richt je daarom op de specifieke decoratieve weergave wanneer dat het bedoelde gedrag is.
Klik op enkele verschillende plaatsen om het resultaat te controleren. De tabel beschrijft het verwachte gedrag van het meegeleverde programma en geen gemeten testresultaten.
| Kliklocatie | Verwacht resultaat |
|---|---|
| Gekleurd vierkant of rijtitel | Activeer die rij één keer |
| Lege ruimte rechts, binnen de grijze rij | Activeer die rij één keer |
| Interne opvulling boven of onder de titel | Activeer die rij één keer |
| Ruimte tussen rijen | Geen rijactivering |
| Buitenste opvulling of lege ruimte onder de rijen | Geen rijactivering |
Wijzig de grootte van het venster en controleer opnieuw of lege ruimte reageert. Controleer ook de toetsenbordfocus en activering met de toetsenbordnavigatie-instellingen van je Mac. Zo stel je vast of het uiteindelijke component zich gedraagt als het bedoelde bedieningselement en niet alleen reageert op een aanwijzer.
Wanneer is hit testing in AppKit relevant?
Begin voor een gewone SwiftUI-rij met de lay-out-, content-shape- en bedieningselement-API's van SwiftUI. AppKit wordt relevant wanneer je app aangepaste native weergaven bevat of een ingesloten weergave die de routering van gebeurtenissen wijzigt.
Apple documenteert NSView.hitTest(_:) als een functie die de verste afstammeling zoekt die een punt bevat. Een venster gebruikt deze functie voornamelijk om te bepalen welke weergave een muisknopindruk ontvangt. Het invoerpunt wordt uitgedrukt in het coördinatenstelsel van de bovenliggende weergave, en volgens Apple is directe aanroep zelden nodig.
Als je een aangepaste AppKit-weergave beheert, onderzoek dan op die grens de grenzen en eventuele hit-testing-override ervan. Een SwiftUI-content shape is geen opdracht om de eigenaar van gebeurtenissen in een afzonderlijke native weergave te omzeilen. Houd de oplossing binnen de weergavehiërarchie die verantwoordelijk is voor het ontvangen van de klik.
Voor de rij in deze tutorial is de ondersteunde aanpak eenvoudig: laat het knoplabel de bedoelde rijbreedte innemen, definieer de rechthoekige interactievorm en houd andere bedieningselementen in hun eigen gebieden. Zo wordt de nuttige lege ruimte klikbaar en blijven de overige interacties van het venster behouden.
Bronnen
- contentShape(_:eoFill:) — Apple
- contentShape(::eoFill:) — Apple
- interaction — Apple
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple
- Button — Apple
- plain — Apple
- padding(::) — Apple
- Identifiable — Apple
- State — Apple
- List — Apple
- onTapGesture(count:perform:) — Apple
- highPriorityGesture(_:including:) — Apple
- simultaneousGesture(_:including:) — Apple
- allowsHitTesting(_:) — Apple
- hitTest(_:) — Apple