diff --git a/apple/docs/liquid-glass/appkit.md b/apple/docs/liquid-glass/appkit.md new file mode 100644 index 00000000..82af8b9a --- /dev/null +++ b/apple/docs/liquid-glass/appkit.md @@ -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) \ No newline at end of file diff --git a/apple/docs/liquid-glass/overview.md b/apple/docs/liquid-glass/overview.md new file mode 100644 index 00000000..d59892a2 --- /dev/null +++ b/apple/docs/liquid-glass/overview.md @@ -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) \ No newline at end of file diff --git a/apple/docs/liquid-glass/patterns.md b/apple/docs/liquid-glass/patterns.md new file mode 100644 index 00000000..4eba1a66 --- /dev/null +++ b/apple/docs/liquid-glass/patterns.md @@ -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: 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) \ No newline at end of file diff --git a/apple/docs/liquid-glass/swiftui.md b/apple/docs/liquid-glass/swiftui.md new file mode 100644 index 00000000..50e7bbe7 --- /dev/null +++ b/apple/docs/liquid-glass/swiftui.md @@ -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) \ No newline at end of file diff --git a/apple/docs/toolbar/swiftui-features.md b/apple/docs/toolbar/swiftui-features.md new file mode 100644 index 00000000..085713e6 --- /dev/null +++ b/apple/docs/toolbar/swiftui-features.md @@ -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) \ No newline at end of file