This commit is contained in:
Peter Steinberger 2025-08-06 18:42:11 +02:00
parent 6eef4e7df7
commit 3407cf4f11
5 changed files with 779 additions and 0 deletions

View 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)

View 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)

View 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)

View 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)

View 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)