mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
add docs
This commit is contained in:
parent
6eef4e7df7
commit
3407cf4f11
5 changed files with 779 additions and 0 deletions
128
apple/docs/liquid-glass/appkit.md
Normal file
128
apple/docs/liquid-glass/appkit.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# Liquid Glass in AppKit
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Class | Purpose | Key Properties |
|
||||||
|
|-------|---------|---------------|
|
||||||
|
| `NSGlassEffectView` | Single glass effect | `contentView`, `cornerRadius`, `tintColor` |
|
||||||
|
| `NSGlassEffectContainerView` | Multiple glass effects | `contentView`, `spacing` |
|
||||||
|
|
||||||
|
## NSGlassEffectView
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
```swift
|
||||||
|
let glassView = NSGlassEffectView(frame: NSRect(x: 0, y: 0, width: 200, height: 100))
|
||||||
|
glassView.cornerRadius = 16.0
|
||||||
|
glassView.tintColor = NSColor.systemBlue.withAlphaComponent(0.3)
|
||||||
|
glassView.contentView = myContentView
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interactive Glass
|
||||||
|
```swift
|
||||||
|
class InteractiveGlass: NSGlassEffectView {
|
||||||
|
override func mouseEntered(with event: NSEvent) {
|
||||||
|
NSAnimationContext.runAnimationGroup { context in
|
||||||
|
context.duration = 0.2
|
||||||
|
animator().tintColor = NSColor.accent.withAlphaComponent(0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseExited(with event: NSEvent) {
|
||||||
|
NSAnimationContext.runAnimationGroup { _ in
|
||||||
|
animator().tintColor = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## NSGlassEffectContainerView
|
||||||
|
|
||||||
|
### Container Setup
|
||||||
|
```swift
|
||||||
|
let container = NSGlassEffectContainerView()
|
||||||
|
container.spacing = 40.0 // Merge distance
|
||||||
|
|
||||||
|
let contentView = NSView()
|
||||||
|
container.contentView = contentView
|
||||||
|
|
||||||
|
// Add multiple glass views to contentView
|
||||||
|
[glass1, glass2, glass3].forEach { contentView.addSubview($0) }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animated Merging
|
||||||
|
```swift
|
||||||
|
NSAnimationContext.runAnimationGroup { context in
|
||||||
|
context.duration = 0.5
|
||||||
|
// Move glass views closer to trigger merge
|
||||||
|
glass2.animator().frame.origin.x -= 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Components
|
||||||
|
|
||||||
|
### Glass Button
|
||||||
|
```swift
|
||||||
|
class GlassButton: NSButton {
|
||||||
|
private let glass = NSGlassEffectView()
|
||||||
|
|
||||||
|
override init(frame: NSRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setup() {
|
||||||
|
bezelStyle = .rounded
|
||||||
|
isBordered = false
|
||||||
|
glass.autoresizingMask = [.width, .height]
|
||||||
|
glass.cornerRadius = 8.0
|
||||||
|
addSubview(glass, positioned: .below, relativeTo: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Glass Toolbar
|
||||||
|
```swift
|
||||||
|
// Add glass to toolbar area
|
||||||
|
let toolbar = NSToolbar(identifier: "main")
|
||||||
|
let glassView = NSGlassEffectView()
|
||||||
|
glassView.frame = NSRect(x: 0, y: view.bounds.height - 50,
|
||||||
|
width: view.bounds.width, height: 50)
|
||||||
|
glassView.autoresizingMask = [.width, .minYMargin]
|
||||||
|
view.addSubview(glassView)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Z-Order
|
||||||
|
- Only `contentView` guaranteed inside effect
|
||||||
|
- Arbitrary subviews may render incorrectly
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- Batch in containers when possible
|
||||||
|
- Limit total glass views (5-10 max)
|
||||||
|
- Disable when scrolling for performance
|
||||||
|
|
||||||
|
### Animation
|
||||||
|
- Use `NSAnimationContext` for smooth transitions
|
||||||
|
- Standard duration: 0.2-0.5 seconds
|
||||||
|
- Animate `tintColor` for state changes
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Glass not visible | Check view hierarchy, ensure added to window |
|
||||||
|
| Performance lag | Use containers, reduce glass count |
|
||||||
|
| Merge not working | Check container spacing value |
|
||||||
|
| Content outside glass | Use contentView property exclusively |
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
- [Overview](overview.md)
|
||||||
|
- [SwiftUI Implementation](swiftui.md)
|
||||||
|
- [Common Patterns](patterns.md)
|
||||||
|
- [Apple Docs: NSGlassEffectView](https://developer.apple.com/documentation/AppKit/NSGlassEffectView)
|
||||||
60
apple/docs/liquid-glass/overview.md
Normal file
60
apple/docs/liquid-glass/overview.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Liquid Glass Design Overview
|
||||||
|
|
||||||
|
## What is Liquid Glass?
|
||||||
|
|
||||||
|
Dynamic material design combining optical glass properties with fluidity:
|
||||||
|
- **Blurs** content behind it
|
||||||
|
- **Reflects** color and light from surroundings
|
||||||
|
- **Reacts** to touch and pointer interactions
|
||||||
|
- **Morphs** between shapes during transitions
|
||||||
|
|
||||||
|
## Platform Availability
|
||||||
|
|
||||||
|
| Platform | Framework | Primary Class/Modifier | Min Version |
|
||||||
|
|----------|-----------|----------------------|-------------|
|
||||||
|
| macOS | AppKit | `NSGlassEffectView` | macOS 14.0+ |
|
||||||
|
| macOS | SwiftUI | `.glassEffect()` | macOS 14.0+ |
|
||||||
|
| iOS/iPadOS | SwiftUI | `.glassEffect()` | iOS 17.0+ |
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
### Glass Variants
|
||||||
|
- **Regular**: Standard glass effect
|
||||||
|
- **Prominent**: Enhanced visibility with tint
|
||||||
|
- **Interactive**: Responds to user input
|
||||||
|
|
||||||
|
### Container Optimization
|
||||||
|
Containers improve performance and enable effect merging:
|
||||||
|
- **AppKit**: `NSGlassEffectContainerView`
|
||||||
|
- **SwiftUI**: `GlassEffectContainer`
|
||||||
|
|
||||||
|
### Key Properties
|
||||||
|
- `cornerRadius` / `shape`: Visual appearance
|
||||||
|
- `tintColor` / `.tint()`: Color overlay
|
||||||
|
- `spacing`: Merge distance threshold
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### SwiftUI
|
||||||
|
```swift
|
||||||
|
Text("Hello").glassEffect()
|
||||||
|
Button("Click").buttonStyle(.glass)
|
||||||
|
```
|
||||||
|
|
||||||
|
### AppKit
|
||||||
|
```swift
|
||||||
|
let glass = NSGlassEffectView()
|
||||||
|
glass.cornerRadius = 12.0
|
||||||
|
glass.contentView = myView
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Guidelines
|
||||||
|
1. Use containers for multiple glass views
|
||||||
|
2. Limit total glass effects on screen
|
||||||
|
3. Consider GPU impact on older devices
|
||||||
|
4. Batch similar effects together
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
- [AppKit Implementation](appkit.md)
|
||||||
|
- [SwiftUI Implementation](swiftui.md)
|
||||||
|
- [Common Patterns](patterns.md)
|
||||||
188
apple/docs/liquid-glass/patterns.md
Normal file
188
apple/docs/liquid-glass/patterns.md
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
# Liquid Glass Common Patterns
|
||||||
|
|
||||||
|
## Cross-Platform Patterns
|
||||||
|
|
||||||
|
### State-Based Glass
|
||||||
|
**SwiftUI**
|
||||||
|
```swift
|
||||||
|
.glassEffect(.regular.tint(isActive ? .blue : .clear))
|
||||||
|
```
|
||||||
|
|
||||||
|
**AppKit**
|
||||||
|
```swift
|
||||||
|
glassView.tintColor = isActive ? NSColor.systemBlue.withAlphaComponent(0.3) : nil
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hover Effects
|
||||||
|
**SwiftUI**
|
||||||
|
```swift
|
||||||
|
@State private var isHovered = false
|
||||||
|
Text("Hover")
|
||||||
|
.glassEffect(.regular.tint(isHovered ? .blue : .clear))
|
||||||
|
.onHover { isHovered = $0 }
|
||||||
|
```
|
||||||
|
|
||||||
|
**AppKit**
|
||||||
|
```swift
|
||||||
|
override func mouseEntered(with: NSEvent) {
|
||||||
|
animator().tintColor = NSColor.systemBlue.withAlphaComponent(0.2)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animated Transitions
|
||||||
|
**SwiftUI**
|
||||||
|
```swift
|
||||||
|
.animation(.spring(duration: 0.3), value: glassState)
|
||||||
|
```
|
||||||
|
|
||||||
|
**AppKit**
|
||||||
|
```swift
|
||||||
|
NSAnimationContext.runAnimationGroup { context in
|
||||||
|
context.duration = 0.3
|
||||||
|
// animations
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI Component Patterns
|
||||||
|
|
||||||
|
### Glass Card
|
||||||
|
```swift
|
||||||
|
// SwiftUI
|
||||||
|
struct GlassCard<Content: View>: View {
|
||||||
|
let content: Content
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.padding()
|
||||||
|
.glassEffect(in: .rect(cornerRadius: 16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppKit
|
||||||
|
class GlassCard: NSView {
|
||||||
|
let glass = NSGlassEffectView()
|
||||||
|
init(content: NSView) {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
glass.cornerRadius = 16
|
||||||
|
glass.contentView = content
|
||||||
|
addSubview(glass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Glass Badge
|
||||||
|
```swift
|
||||||
|
// SwiftUI
|
||||||
|
struct GlassBadge: View {
|
||||||
|
let count: Int
|
||||||
|
var body: some View {
|
||||||
|
Text("\(count)")
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.glassEffect(.regular.tint(.red))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Glass Toolbar
|
||||||
|
```swift
|
||||||
|
// SwiftUI
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup {
|
||||||
|
Button("One") { }.buttonStyle(.glass)
|
||||||
|
Button("Two") { }.buttonStyle(.glass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout Patterns
|
||||||
|
|
||||||
|
### Grid of Glass Items
|
||||||
|
```swift
|
||||||
|
// SwiftUI
|
||||||
|
GlassEffectContainer(spacing: 20) {
|
||||||
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
|
||||||
|
ForEach(items) { item in
|
||||||
|
ItemView(item).glassEffect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Merging Glass Groups
|
||||||
|
```swift
|
||||||
|
// SwiftUI with union
|
||||||
|
@Namespace private var ns
|
||||||
|
ForEach(items.indices) { i in
|
||||||
|
ItemView(items[i])
|
||||||
|
.glassEffect()
|
||||||
|
.glassEffectUnion(id: groupID(for: i), namespace: ns)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Animation Patterns
|
||||||
|
|
||||||
|
### Pulse Effect
|
||||||
|
```swift
|
||||||
|
// SwiftUI
|
||||||
|
@State private var isPulsing = false
|
||||||
|
Circle()
|
||||||
|
.glassEffect(.regular.tint(isPulsing ? .blue : .clear))
|
||||||
|
.animation(.easeInOut(duration: 1).repeatForever(), value: isPulsing)
|
||||||
|
.onAppear { isPulsing = true }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Morphing Between States
|
||||||
|
```swift
|
||||||
|
// SwiftUI
|
||||||
|
@Namespace private var namespace
|
||||||
|
if expanded {
|
||||||
|
LargeView().glassEffect().glassEffectID("morph", in: namespace)
|
||||||
|
} else {
|
||||||
|
SmallView().glassEffect().glassEffectID("morph", in: namespace)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Patterns
|
||||||
|
|
||||||
|
### Lazy Loading Glass
|
||||||
|
```swift
|
||||||
|
// SwiftUI
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack {
|
||||||
|
ForEach(items) { item in
|
||||||
|
ItemView(item)
|
||||||
|
.glassEffect(isEnabled: item.isVisible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Processing
|
||||||
|
```swift
|
||||||
|
// AppKit
|
||||||
|
let container = NSGlassEffectContainerView()
|
||||||
|
container.spacing = 20
|
||||||
|
// Add all glass views to container's contentView
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decision Matrix
|
||||||
|
|
||||||
|
| Use Case | SwiftUI | AppKit |
|
||||||
|
|----------|---------|--------|
|
||||||
|
| Simple glass | `.glassEffect()` | `NSGlassEffectView` |
|
||||||
|
| Multiple glass | `GlassEffectContainer` | `NSGlassEffectContainerView` |
|
||||||
|
| Button styling | `.buttonStyle(.glass)` | Custom `GlassButton` class |
|
||||||
|
| Morphing | `.glassEffectID()` | Manual animation |
|
||||||
|
| Performance | Container + lazy | Container + batch |
|
||||||
|
|
||||||
|
## Tips & Tricks
|
||||||
|
|
||||||
|
1. **Merge Control**: Adjust container spacing to control merge distance
|
||||||
|
2. **State Changes**: Use tint color for visual feedback
|
||||||
|
3. **Touch Feedback**: Enable `.interactive()` for user interaction
|
||||||
|
4. **Performance**: Disable glass when off-screen
|
||||||
|
5. **Consistency**: Match corner radius across app
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
- [Overview](overview.md)
|
||||||
|
- [AppKit Implementation](appkit.md)
|
||||||
|
- [SwiftUI Implementation](swiftui.md)
|
||||||
190
apple/docs/liquid-glass/swiftui.md
Normal file
190
apple/docs/liquid-glass/swiftui.md
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
# Liquid Glass in SwiftUI
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Modifier/View | Purpose | Parameters |
|
||||||
|
|---------------|---------|------------|
|
||||||
|
| `.glassEffect()` | Apply glass to view | `Glass`, `Shape`, `isEnabled` |
|
||||||
|
| `GlassEffectContainer` | Optimize multiple glass | `spacing`, `content` |
|
||||||
|
| `.buttonStyle(.glass)` | Glass button style | N/A |
|
||||||
|
| `.glassEffectID()` | Morphing transitions | `id`, `namespace` |
|
||||||
|
|
||||||
|
## Basic Implementation
|
||||||
|
|
||||||
|
### Simple Glass Effect
|
||||||
|
```swift
|
||||||
|
Text("Hello")
|
||||||
|
.padding()
|
||||||
|
.glassEffect() // Default: regular glass, capsule shape
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Shape & Tint
|
||||||
|
```swift
|
||||||
|
Image(systemName: "star")
|
||||||
|
.glassEffect(
|
||||||
|
.regular.tint(.blue).interactive(),
|
||||||
|
in: .rect(cornerRadius: 12)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Glass Variants
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
```swift
|
||||||
|
// Regular glass
|
||||||
|
.glassEffect(.regular)
|
||||||
|
|
||||||
|
// With tint
|
||||||
|
.glassEffect(.regular.tint(.orange))
|
||||||
|
|
||||||
|
// Interactive (responds to touch)
|
||||||
|
.glassEffect(.regular.interactive())
|
||||||
|
|
||||||
|
// Combined
|
||||||
|
.glassEffect(.regular.tint(.blue).interactive())
|
||||||
|
```
|
||||||
|
|
||||||
|
## Container Usage
|
||||||
|
|
||||||
|
### Multiple Glass Views
|
||||||
|
```swift
|
||||||
|
GlassEffectContainer(spacing: 40) {
|
||||||
|
HStack(spacing: 40) {
|
||||||
|
ForEach(items) { item in
|
||||||
|
ItemView(item)
|
||||||
|
.glassEffect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Union Effect
|
||||||
|
```swift
|
||||||
|
@Namespace private var namespace
|
||||||
|
|
||||||
|
GlassEffectContainer {
|
||||||
|
HStack {
|
||||||
|
ForEach(items.indices, id: \.self) { index in
|
||||||
|
ItemView(items[index])
|
||||||
|
.glassEffect()
|
||||||
|
.glassEffectUnion(
|
||||||
|
id: index < 2 ? "group1" : "group2",
|
||||||
|
namespace: namespace
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Morphing Transitions
|
||||||
|
|
||||||
|
### Animated Morphing
|
||||||
|
```swift
|
||||||
|
@State private var expanded = false
|
||||||
|
@Namespace private var namespace
|
||||||
|
|
||||||
|
GlassEffectContainer {
|
||||||
|
if expanded {
|
||||||
|
LargeView()
|
||||||
|
.glassEffect()
|
||||||
|
.glassEffectID("item", in: namespace)
|
||||||
|
} else {
|
||||||
|
SmallView()
|
||||||
|
.glassEffect()
|
||||||
|
.glassEffectID("item", in: namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.animation(.spring(), value: expanded)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Button Styles
|
||||||
|
|
||||||
|
### Glass Buttons
|
||||||
|
```swift
|
||||||
|
// Standard glass button
|
||||||
|
Button("Action") { }
|
||||||
|
.buttonStyle(.glass)
|
||||||
|
|
||||||
|
// Prominent glass button
|
||||||
|
Button("Important") { }
|
||||||
|
.buttonStyle(.glassProminent)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Toolbar Integration
|
||||||
|
|
||||||
|
### Glass in Toolbar
|
||||||
|
```swift
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
Button("Save") { }
|
||||||
|
.buttonStyle(.glass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Toolbar Background
|
||||||
|
```swift
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
Text("Title")
|
||||||
|
.sharedBackgroundVisibility(.hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Techniques
|
||||||
|
|
||||||
|
### Conditional Glass
|
||||||
|
```swift
|
||||||
|
@State private var glassEnabled = true
|
||||||
|
|
||||||
|
Text("Dynamic")
|
||||||
|
.glassEffect(isEnabled: glassEnabled)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation Transitions
|
||||||
|
```swift
|
||||||
|
@Namespace private var namespace
|
||||||
|
|
||||||
|
NavigationStack {
|
||||||
|
Content()
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem {
|
||||||
|
Button("Open") { }
|
||||||
|
.matchedTransitionSource(id: "btn", in: namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $show) {
|
||||||
|
DetailView()
|
||||||
|
.navigationTransition(.zoom(sourceID: "btn", in: namespace))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
1. **Container Usage**: Always wrap multiple glass views
|
||||||
|
2. **Spacing**: Smaller = merge closer, larger = merge farther
|
||||||
|
3. **Limit Count**: 5-10 glass effects maximum
|
||||||
|
4. **Disable When Hidden**: Use `isEnabled` parameter
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
| Pattern | Implementation |
|
||||||
|
|---------|---------------|
|
||||||
|
| Toggle glass | `.glassEffect(isEnabled: condition)` |
|
||||||
|
| Group merge | `.glassEffectUnion(id:namespace:)` |
|
||||||
|
| Custom shape | `.glassEffect(in: .rect(cornerRadius: 20))` |
|
||||||
|
| State indication | `.tint(isActive ? .blue : .clear)` |
|
||||||
|
|
||||||
|
## Platform Notes
|
||||||
|
|
||||||
|
- **iOS**: Bottom bar placement works well
|
||||||
|
- **iPadOS**: Consider larger touch targets
|
||||||
|
- **macOS**: Toolbar customization expected
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
- [Overview](overview.md)
|
||||||
|
- [AppKit Implementation](appkit.md)
|
||||||
|
- [Common Patterns](patterns.md)
|
||||||
|
- [Apple Docs: glassEffect](https://developer.apple.com/documentation/SwiftUI/View/glassEffect)
|
||||||
213
apple/docs/toolbar/swiftui-features.md
Normal file
213
apple/docs/toolbar/swiftui-features.md
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
# SwiftUI Toolbar Features
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Feature | Modifier/Type | Purpose |
|
||||||
|
|---------|--------------|---------|
|
||||||
|
| `.toolbar(id:)` | Customizable toolbar | User can add/remove/reorder |
|
||||||
|
| `ToolbarSpacer` | Spacing control | Fixed or flexible spacing |
|
||||||
|
| `.searchToolbarBehavior()` | Search field display | Minimize/expand behavior |
|
||||||
|
| `DefaultToolbarItem` | System items | Reposition system controls |
|
||||||
|
| `.matchedTransitionSource()` | Transitions | Zoom from toolbar items |
|
||||||
|
|
||||||
|
## Customizable Toolbars
|
||||||
|
|
||||||
|
### Basic Setup
|
||||||
|
```swift
|
||||||
|
.toolbar(id: "main") {
|
||||||
|
ToolbarItem(id: "save") { SaveButton() }
|
||||||
|
ToolbarItem(id: "share") { ShareButton() }
|
||||||
|
ToolbarSpacer(.flexible)
|
||||||
|
ToolbarItem(id: "more") { MoreButton() }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spacer Types
|
||||||
|
```swift
|
||||||
|
ToolbarSpacer(.fixed) // Fixed width
|
||||||
|
ToolbarSpacer(.flexible) // Pushes items apart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Search Integration
|
||||||
|
|
||||||
|
### Minimize Behavior
|
||||||
|
```swift
|
||||||
|
@State private var searchText = ""
|
||||||
|
|
||||||
|
NavigationStack {
|
||||||
|
ContentView()
|
||||||
|
.searchable($searchText)
|
||||||
|
.searchToolbarBehavior(.minimize) // Compact search button
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Repositioning Search
|
||||||
|
```swift
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .bottomBar) { Button1() }
|
||||||
|
DefaultToolbarItem(kind: .search, placement: .bottomBar)
|
||||||
|
ToolbarItem(placement: .bottomBar) { Button2() }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Placement Options
|
||||||
|
|
||||||
|
### Common Placements
|
||||||
|
```swift
|
||||||
|
.toolbar {
|
||||||
|
// Navigation bar
|
||||||
|
ToolbarItem(placement: .navigationBarLeading) { }
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) { }
|
||||||
|
ToolbarItem(placement: .principal) { }
|
||||||
|
|
||||||
|
// Bottom bar (iOS)
|
||||||
|
ToolbarItem(placement: .bottomBar) { }
|
||||||
|
|
||||||
|
// Large title area
|
||||||
|
ToolbarItem(placement: .largeSubtitle) { CustomSubtitle() }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Large Subtitle
|
||||||
|
```swift
|
||||||
|
NavigationStack {
|
||||||
|
Content()
|
||||||
|
.navigationTitle("Title")
|
||||||
|
.navigationSubtitle("Subtitle")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .largeSubtitle) {
|
||||||
|
// Overrides navigationSubtitle
|
||||||
|
CustomSubtitleView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Visual Effects
|
||||||
|
|
||||||
|
### Matched Transitions
|
||||||
|
```swift
|
||||||
|
@State private var showDetail = false
|
||||||
|
@Namespace private var namespace
|
||||||
|
|
||||||
|
NavigationStack {
|
||||||
|
Content()
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem {
|
||||||
|
Button("Open") { showDetail = true }
|
||||||
|
.matchedTransitionSource(id: "btn", in: namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showDetail) {
|
||||||
|
DetailView()
|
||||||
|
.navigationTransition(.zoom(sourceID: "btn", in: namespace))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Background Visibility
|
||||||
|
```swift
|
||||||
|
.toolbar(id: "main") {
|
||||||
|
ToolbarItem(id: "status", placement: .principal) {
|
||||||
|
StatusView()
|
||||||
|
.sharedBackgroundVisibility(.hidden) // No glass background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## System Items
|
||||||
|
|
||||||
|
### Default Items
|
||||||
|
```swift
|
||||||
|
.toolbar {
|
||||||
|
// Reposition system search
|
||||||
|
DefaultToolbarItem(kind: .search, placement: .bottomBar)
|
||||||
|
|
||||||
|
// Sidebar toggle
|
||||||
|
DefaultToolbarItem(kind: .sidebar, placement: .navigationBarLeading)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Platform Considerations
|
||||||
|
|
||||||
|
### iOS/iPadOS
|
||||||
|
```swift
|
||||||
|
#if os(iOS)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .bottomBar) {
|
||||||
|
// Bottom bar items for iPhone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
```swift
|
||||||
|
#if os(macOS)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .automatic) {
|
||||||
|
// macOS toolbar items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Dynamic Toolbar
|
||||||
|
```swift
|
||||||
|
@State private var isEditing = false
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
if isEditing {
|
||||||
|
ToolbarItem(id: "done") { DoneButton() }
|
||||||
|
} else {
|
||||||
|
ToolbarItem(id: "edit") { EditButton() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grouped Actions
|
||||||
|
```swift
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .bottomBar) {
|
||||||
|
Button("One") { }
|
||||||
|
Button("Two") { }
|
||||||
|
Button("Three") { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contextual Items
|
||||||
|
```swift
|
||||||
|
@State private var selection: Item?
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
if selection != nil {
|
||||||
|
ToolbarItem { DeleteButton() }
|
||||||
|
ToolbarItem { ShareButton() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Unique IDs**: Use meaningful identifiers for customizable items
|
||||||
|
2. **Logical Groups**: Use spacers to group related actions
|
||||||
|
3. **Platform Awareness**: Test on all target platforms
|
||||||
|
4. **Consistent Placement**: Follow platform conventions
|
||||||
|
5. **Minimal Items**: Avoid overcrowding toolbars
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Items not customizable | Add `id` to toolbar and items |
|
||||||
|
| Search not minimizing | Apply `.searchToolbarBehavior(.minimize)` |
|
||||||
|
| Transition not working | Check namespace and ID match |
|
||||||
|
| Items hidden | Check placement compatibility |
|
||||||
|
|
||||||
|
## References
|
||||||
|
- [Apple Docs: ToolbarContent](https://developer.apple.com/documentation/SwiftUI/ToolbarContent)
|
||||||
|
- [Apple Docs: CustomizableToolbarContent](https://developer.apple.com/documentation/SwiftUI/CustomizableToolbarContent)
|
||||||
|
- [Liquid Glass Integration](../liquid-glass/swiftui.md)
|
||||||
Loading…
Reference in a new issue