🍎
ios.dev
ios.dev
  • 🍎ios.dev
    • 🛠️tools
    • 📰News
    • 📅Events
      • WWDC22
    • 🔲todo
      • 📖to read ...
      • 🧪研究中 ...
        • Formatter
        • Dependency Injection
        • 🔸Never
        • 🧪Google Sheet as JSON
      • 💈Examples
        • 💈MonthView
        • 💈CircleText
        • 💈Collapsible
      • 📓課程筆記
        • 📚SwiftUI Tutorials
          • Tutorials ⟩ Model
          • Tutorials ⟩ Views
          • Tutorials ⟩ Extensions
        • 📚Swift Animation Mastery
          • Triggers
            • 💈example ⟩ Picker
            • 💈example ⟩ SubButtons
        • Emoji Memory Game 📚
        • 設計卡片 📚
          • 排版
          • 元件化
          • 設定動畫
          • 設定手勢
          • 動畫的屬性
        • 新擬物風 (Neumorphic Design)
          • 漸層設計
            • Neu 🔸
          • 卡片設計
            • NeuCard 📦
          • 按鈕設計
            • IconCard 📦
            • ButtonUp, ButtonDown 📦
          • 設計進度條
            • NeuProgressBarTitle 📦
            • MyProgressBar2 📦
            • NeuProgressBar2 📦
      • 自訂型別
        • @KilometersPerHour
        • 🐶PlaygroundConsole
        • 👔EmojiTextFieldStyle
        • 🌅Outlined
        • 🌅BlurView
        • 🌅 SystemImage
        • 🌅 Fit
        • 🌅 Unwrap
        • ✨Custom Circle
    • 🔰templates
      • 🗒️page template
    • 🧪lab
    • 🔰terms
      • 🔰roles
        • 🔸implementer
        • 🔸user
      • 🔸state
      • 🔸data separation
      • 🔸data model
      • 🔸source of truth
      • 🔸user interaction
      • 🔸user interface
  • ⭐features
    • ⭐Swift
      • ⭐️ Swift 5.6
        • any
        • type placeholders
      • ⭐️ Swift 5.7
    • ⭐SwiftUI
      • ⭐SwiftUI 4.0
    • ⭐Xcode
      • Xcode 14
    • ⭐iOS
      • ⭐️ iOS 15
        • ⭐️ Live Text
      • ⭐️ iOS 16
    • Global Variables
    • Destructuring
    • Variadic Parameter
    • autoclosure
    • 🔰Key Path
      • Key Path Expressions as Functions
    • Calling Types As Functions
  • 👔custom
    • 🌀extension
      • 🌀View+
        • 👔view.if(_:then:)
        • 👔view.if(let:then:)
        • 👔view.inverseMask()
        • 👔view.overlayText()
        • 👔view.resizableFont()
        • 👔view.watermark()
        • 🚫view.foreground()
      • 🌀Color+
        • 🌀system colors
          • 🖼️SystemColorsView
          • 🖼️Swatch_0
        • 🌀Color.rgb()
        • 🌀color.hue
      • 🌀Gradient+
        • 🌀Gradient.linear()
        • 🌀Gradient + View
      • 🌀Image+
        • 🌀Image(playground:)
      • 🌀Formatter+
      • 🌀RoundedRectangle+
      • 🌀Text+
        • 🌀Text(symbol:)
    • 🔰snippets
      • 💾view + previews
      • 💾view modifier (template)
    • 🎁package
      • 📕如何自製 Swift package
      • 🏛️GeometryKit
        • 🅿️MetricSpace
        • 🅿️Vector
        • 🅿️ComplexNumber
        • 🅿️Vector2D
        • 🅿️Frame
    • 🅿️protocol
      • 🅿️Repeatable
    • 🖼️view
      • 👔HLine / VLine
      • 👔Swatch
      • 👔ShapeStyleView
    • 🎛️control
      • 🎛️SlidersForSize
      • 🎛️SliderWithLabel
  • 🐦Swift
    • 🎲math functions
      • abs()
      • .rounded()
      • .squareRoot()
      • pow()
    • 🔰scope
      • 🔸global scope
      • 🔸local scope
      • 🔸module
      • 🔹import
      • 🔸framework
        • 🧩built-in frameworks
          • 🧩Foundation
            • 📦Calendar
            • 📅Date
              • .sevenDaysOut
              • Date.from(year:month:day:)
              • Date.roundedHoursFromNow(hours)
            • 📦URL
              • 🌀 URL+ext
              • .isImage
            • 🗃️FileManager
              • 🌀 FileManager+ext
              • .documentDirectory
            • 🎁Bundle
            • 🅿️FormatStyle
          • 🧩Core Graphics
            • 🌀CGRect
              • rect.inset(by:)
            • 🌀CGSize
            • 🌀CGVector
            • 🌀CGPoint
          • 🧩SpriteKit
          • 🧩Combine
            • 🅿️Publisher
              • 🐶Future
              • 📦URLSession.DataTaskPublisher
            • ⚙️ Promise
            • 🔸Result
            • ⏰Timer
              • ✨cancellable timer
              • 👔TimerView
              • 👔TimerView (scheduledTimer)
          • 🧩AVFoundation
          • 🧩Swift Charts
          • 🧩Metal
          • 🧩WeatherKit
    • ➕operator
      • 🔸compound assignment operator
      • ✖️Character * Int
      • ✖️a^^n
      • ➕ternary conditional operator (a ? b : c)
    • 🍀type
      • 🍀type category
        • 🍀basic types
          • ✅Bool
            • .iOS14
          • ⁉️Optional
            • optional binding
            • optional chaining
            • nil coalescing
            • compare with Optional
          • 🔤String
            • 🔰String Interpolation
              • Custom String Interpolation
              • Expressible by String Interpolation
              • specifier / formatter
            • 📦AttributedString
            • 😃Emojis
            • String.Index
            • str.capitalized
            • str.split()
            • str.trim()
            • str[i], str[i..<j], str[i...j]
            • str.pad()
          • 📦Int
          • 📦Array
            • 🌀arr.split(size:)
            • 🌀arr.first()
            • arr.split(where:)
            • arr.index(of:)
            • arr.allElementsEqual
            • .allElementsSameLength
          • 📦enum
            • ⭐Comparable Enums
            • compound cases
            • 💡filter cases
            • 💡用 enum 封裝不同型別
          • 🚥tuple
          • 🔰function
            • 🔸default parameter
            • 🔸argument label
            • 🔸return value
              • #️discardable result
            • 🔰function as variable
            • 🔰fully qualified name
          • 🔰closure
            • 🔰closure expression
            • 🔰escaping closure
          • 🍄class
            • 🔰weak self
          • 🔢Numbers
            • 🅿️FloatingPoint
              • 🌀floating +−⨉÷ int
              • .decimalPlaces()
            • 🅿️BinaryFloatingPoint
              • .binaryBitPattern
              • 🖼️BinaryBitPatternView
            • 🅿️BinaryInteger
              • .binary(digits:)
            • range.contains()
          • ⚖️struct vs. class
        • 🅿️protocol
          • 🔰protocol inheritance
            • 🔰protocol hierarchy
            • 🚫can't change builtin hierarchy
          • 🔰protocol requirements
          • 🔴Protocol Extensions
          • 🔰generic protocol
            • 📘some, any
          • 🔰associated type
          • 🔴Conditional Conformance
          • 🔴Combining Protocols
          • 🔰Typecasting with Protocols
          • ⚠️retroactive conformance
          • 🅿️Any
          • 🅿️Comparable
            • .clamped(in:)
          • 🅿️Hashable
          • 🅿️Identifiable
          • 🅿️CaseIterable+ext
          • 🎬POP 介紹
          • ExpressibleByLiteral
        • ⚖️some╱any╱generics
          • 🔸some (opaque type)
          • 🔸any (boxed protocol type)
        • 🍀nested types
          • extension of nested types
          • ❓to nest or not to nest❓
        • 🍀generics
          • ⚠️generics & subtypes
          • ❗specialize generic function
          • ❓default type parameter
        • 🍀non-nominal types
      • 🔰property
        • 🔸public property
        • 🔸stored property
          • 🔸property observer (willSet/didSet)
        • 🔰computed property (get/set)
          • local computed variables
          • mutating getter / nonmutating setter
        • ⚖️stored vs. computed
        • ⚖️willSet/didSet vs. get/set
        • 🔰property wrapper
          • ✨Clamped
          • ✨Trimmed
          • ✨UnitInterval
      • 🐣inheritance
        • 🔸Self
      • 🔰type alias
      • 🔰extension
        • extension of typealias❓
      • ⭐implicit return
      • 🔰type erasure
      • 🔰initialization
        • 🔰initializers
          • 🔸required initializer
        • 🔰class initializer inheritance
          • 兩階段初始化
          • 自動繼承初始化程序
    • 🔰statement
      • 📘do
      • 🔤semicolon (;)
      • 🔰simple statement
        • 🔰expression
          • 🔰primary expressions
        • 🔰declaration
      • 🔰compiler control statement
    • 🔰flow control
      • 🔄loop
        • 📘for...in
        • 🔰for...in vs. forEach
      • 🔀branch
        • 📘if
        • 📘guard
        • 🔹switch
      • ↩️control transfer
        • 📘break
        • 📘continue
        • 📘return
        • 📘throw
        • 📘fallthrough
    • 🔰Collections
      • 🅿️Sequence
        • seq.forEach()
        • seq.reduce(_:_:)
        • seq.reduce(into:_:)
        • reduce() vs. reduce(into:)
        • seq.grouped(by:)
        • seq.sorted()
        • seq.sorted(by:)
        • seq.sorted(_:)
        • seq.foldMap(_:_:)
        • seq.stateMap(_:_:)
        • seq.sum
      • 🅿️Collection
        • collection[safe: index]
        • collection.allElementsEqual
        • collection.allElementsSameLength
        • collection.lengthsOfElements
        • collection.columnWidths
      • 🅿️IteratorProtocol
      • 🅿️OptionSet
      • Collection Types
    • 🔰Subscripts
      • Subscripts with Default Arguments
    • 🔰Attributes
      • ⭐Result Builders
        • result-building methods
        • various result types
        • result builder transform
        • custom result-builder attributes
    • 🔰Pattern Matching
      • 🔴Sentence Patterns
        • ... as! ...
        • (... as? ...)?.method()
        • if let ... as?
        • if/guard case let
        • for case let ... where
        • switch case
        • switch case is ...
        • switch case let ... where
        • switch case let ... as
        • switch on other types
      • operator (~=)
      • enum case pattern
    • 🔰Exceptions
    • 🏛️Standard Library
      • 🅿️Codable
    • 🔰Concurrency
      • 🅿️Sendable
        • 🐞has non-Sendable type
    • 🔰File System
    • 🔰Networking
    • 🐞Debugging
      • 🔮Mirror
        • Mirror.handleChildren()
      • 👔Logger
      • 👔HasMirrors
      • 👔log()
      • 👔CaseReflectable
      • 👔NonNominalTypeWrapper
      • Metatype
    • 🔰Input/Output
    • 🔰Regex
    • 🔰Access Control
  • 🔰SwiftUI
    • 🔰SwiftUI terms
      • 🔸built-in view
    • 🔰introduction
      • SwiftUI Essentials
    • 🚩app
      • 🅿️App
      • 🔰scenes
        • 🅿️Scene
      • 🔰windows
        • 📦WindowGroup
    • 🔰views
      • 🔰view hierarchy
        • 🔸container view
        • 🔸composed view
        • 🔸root view
        • 🔸child view
        • 🔸parent view
        • 🔸destination view
      • 🔰view state
        • 🔸state value
          • @State
            • 🔰init State
            • ✨dynamic underline
        • 🔸state object
          • @StateObject
        • 🔸observable object
          • 🅿️ObservableObject
          • 🔸published value
          • 🔸observer
          • @Published
        • 🔰animate state changes
        • 🔸binding
          • @Binding
            • 🔰Binding State
            • 🔰Binding from StateObject
            • 🔰init Binding
            • ❗return Binding
      • 🔰view layout
        • 🔹.padding( )
        • 🔴Position
        • 🔸frame
          • 🌀view + .frame()
          • 🔹.frame()
            • ✨TestIdealSizeView
            • ✨TestFrameView
        • 🔸view size
          • 🔴Size Classes
          • 🔴Dynamic Type Sizes
            • 🌀DynamicTypeSize+ext
          • 🔸ideal size
        • 🎁stacks
          • 📦ZStack
          • 👔AdaptiveHStack
          • 👔StackForEach
        • 🎁grids
          • 📦LazyVGrid
            • 🖼️StandardGradientGrid
          • 🟠grid layout algorithm
          • 📦GridItem
            • 🔰adaptive column
            • 🔰flexible column
            • ✨NColumns
          • 👔VGridForEach
            • ✨TestLittleSquares
          • 👔ScrollVGridForEach
            • ✨TestSVGFE
          • ✨Grids ⟩ examples
            • ✨Smileys
            • ✨Grid
              • 📦GridLayout
            • ✨MyGrid
              • ⛔Generic parameter '...' could not be inferred.
            • 🐞problem with .readSize()
            • ✨ItemsView
              • 🖼️TestItemsView
              • 📦RatioRetainingLayout
              • 🎛️SizeRatioControl
              • 🅿️IndexedGridLayout
        • Adaptive Layout
        • Alignment
          • Implicit Alignment
          • Custom Alignment within ZStack
          • 🔰各種對齊 (alignment)
        • 🌿Layout ⟩ Types
          • 📦TimelineView
        • ⛔Cannot convert value of type 'Self' to expected argument type 'Binding<C>'
      • 🔰view actions
        • 🔰delete item from list
        • 🔰add item to list
      • 🔰view environment
        • 🔸environment object
        • 🔸environment value
          • 📦dismiss
        • @Environment
          • .editMode
        • @EnvironmentObject
      • 🔰view groupings
        • 🖼️ForEach
          • 🔹.onDelete
          • ⚖️List vs. ForEach
          • ✨ForEach examples
      • 🔰navigation
        • 🔸navigation bar
        • 📦NavigationStack
        • 📦NavigationSplitView
        • 📦NavigationLink
      • 🔰view modifier
        • ✨examples
          • .nsfw()
          • .roundedBorder()
          • .neumorphic()
        • 🔰add modifiers to Xcode
        • 🔰conditional modifier
        • 🔰layout modifier
        • 🔰rendering modifier
        • 🔰adjusting text
        • 🅿️ViewModifier
      • 🔰drawing views
        • 🔸foreground
          • 🔸foreground element
          • 🔸foreground style
            • 🔹view.foregroundStyle()
            • 🔹ShapeStyle.foreground
        • 🔸background
          • 🔸background style
          • 🔹.background()
        • 🔸blend mode
          • ✨.destinationOut
        • 🔸mask
          • 🔹view.mask()
        • 🔸shadow
          • 🔰container + shadow
          • 📦ShadowStyle
          • 🔹view.shadow()
        • 🔹view.clipShape()
          • 🐞mismatching types
        • 🔹veiw.compositingGroup()
      • 🔰measuring views
        • 📦GeometryProxy
          • 🌀GeometryProxy+ext
        • 📦 GeometryReader
          • 🔰GeometryReader 的對齊方式
      • 🔰Configuring Views
      • 🔰events
        • 🔹.allowsHitTesting()
      • 📦ViewBuilder
        • ViewBuilder transforms
        • support for stored properties
      • 🔰Optional Views
      • 🅿️View
        • .dimension()
          • 🌀Path+
          • 👔DimensionPositions
          • 👔WidthDimension
          • 👔HeightDimension
          • 👔LeftArrow
          • 👔LeftArrowShape
          • 👔UpArrow
          • 👔UpArrowShape
        • id(_:)
        • .badge()
        • .border(), .borderLeft() ...
        • .hideIf()
        • .logType()
        • .getSize()
        • .grids()
        • .offset()
        • .onChangeSize()
        • .onDrag
        • ❌.readSize()
        • resizableFont()
        • .show(if:)
        • .shadowedBorder()
        • .testFrame()
        • view.actOnSelfWidth()
      • 📦Link
      • 🔰Swipe Actions
      • 🔰3D transform
      • ⭐Live Activites
      • 🔰life cycle
    • 🔰styles
      • 🔸current style
    • 🔰lists
      • 📦List
        • 🅿️ListStyle
        • ⭐list background color
    • 🎛️controls
      • 🔰pickers
        • 🎛️Picker
        • 🎛️DatePicker
      • 🔰toolbar
      • 🔰FocusState
      • 📦Divider
      • 📦Text
        • 📦Font
          • 🔰custom font
        • 🔰text formats
          • 🔸no wrap
        • 🔰Markdown
        • ❓math equations❓
        • ⭐number of lines
      • 📦Image
        • 🔹image.renderingMode()
        • 🔸template image
        • 🔰SF Symbols
        • 🍄ImageRenderer
      • 📦Label
        • Label Styles
          • Label ⟩ custom styles
      • 📦GroupBox
      • 📦ControlGroup
      • 📦Section
      • 📦ScrollView
      • ⭐ShareLink
      • 🎛️Button
        • 🔰Button Roles
        • 🔰Button Styles
          • 🅿️PrimitiveButtonStyle
          • 🔰Button with Materials
          • Button ⟩ custom styles
      • 🎛️EditButton
      • 🎛️ProgressView
      • 🎛️TextField
        • TextField ⟩ formats
          • ✨.currency
        • TextField ⟩ styles
          • textField.style(_:)
      • 🎛️Toggle
        • 🔰Toggle Styles
      • 🎛️SecureField
      • 🎛️Slider
        • 🔰Slider label position
      • 🎛️Gauge
    • 🔰shapes
      • 🔰transform shape
        • 🔹shape.stroke()
          • 🌀StrokeStyle
          • ✨MarchingAnts
      • 💈正圓變成正五邊形
      • 🅿️Shape
        • 📦AnyShape
        • 🔸fill
          • ✨even-odd fill mode
          • .evenOddFill
        • 🌀Shape + ext
          • shape.outlined()
        • ✨RoundedCorners
        • ✨Helper Shapes
          • 💜LineShape
          • 💜GridLinesShape
          • 🖼️GridLines
          • 🖼️Line
          • 🖼️Pin
          • 🖼️Point
        • ✨Sports Car in Sunset
          • 💜Vehicle.CarBodyShape
          • 🖼️Vehicle
        • 👔Polygon
      • 🅿️ShapeStyle
        • 📦Color
          • 🔸color.gradient
          • 🔰Theme
        • 📦Gradient
        • 📦ImagePaint
        • ♦️.shadow()
        • .white()
        • ShapeStyle+LinearGradient
        • 📦Material
      • 🔰Matched Geometry Effect
        • ✨MGE ⟩ animation
      • 📦Path
        • path.fit()
        • path.line()
        • path.transform
      • 📦Canvas
      • 📦Anchor<T>
    • 🎁container
      • 🎁collection
      • 🎁layout
        • ⭐Grid
        • ⭐ViewThatFits
        • ⭐AnyLayout
        • ⭐Layout
      • 🎁presentation
        • ⭐bottom sheet
        • 🎁TabView
          • ✨tabs with tag
    • 🚦data flow
      • 🚥Preferences
        • 👔ViewPreference
        • 🅿️PreferenceKey
          • FirstNonNil<T>
          • AllValues<T>
          • MaxValue<T>
        • 🚥Anchor Preferences
        • ✨Preferences ⟩ examples
          • ✨views with same width
            • .reportWidth() ...
          • ✨selected button with underline
    • 🏎️animations
      • 🔰implicit vs. explicit
      • 🅿️Animatable
        • 🅿️GeometryEffect
        • 🔸Animatable Modifiers
          • ✨.tapToShake()
      • 📦Animation
      • 🔰delay
      • 🔸Animation Curves
        • 🔸Bezier Curves
        • easeInOutExp
      • ✨Animations ⟩ examples
        • ✨Spinner
        • ✨Wheel of Fortune
        • ✨Loading Indicators
      • 🔰Transitions
        • 🔰custom transitions
        • 📦AnyTransition
          • AnyTransition.combined()
        • ✨transition ⟩ scale
        • ✨combining transitions
    • 👆Gestures
      • 🔰update gesture states
      • 🔰update view states
      • 🚥GestureState
      • 👆Long Press
      • 👆Drag
        • 👔.draggable()
          • 🖼️HandlePoint
      • 🔰Combining Gestures
        • 👆.sequenced()
          • ✨long press + drag
    • 🔰Presentations
      • 🔰Launch Screen
      • 🔰External Screens
      • 🔰Refreshable Views
      • 🔰Alert
      • 🎛️Confirmation Dialog
    • 🔰Debugging in SwiftUI
    • 🎹keyboard
    • 🔰for mac
    • 🚫deprecated
      • 🎁NavigationView
        • .navigationViewStyle(_:)
  • 🎛️widgets
  • 🎁Data Structures
    • Graph
      • Tree
  • 🧠Algorithms
    • Searching
      • Binary Search
    • Hashing
    • Sorting
      • sort using key paths
      • ranking list
      • Merge Sort
    • Filtering
    • Path Finding
      • A*
  • ⛱️Swift Playgrounds
    • Playground Book
      • Structuring Content
    • 檔案結構
      • 💾Manifest.plist
    • 使用模組
    • 📚Learn to Code 2
      • 型別
        • 🔸CharacterName
        • 🍄NodeWrapper
        • 👔ItemID
        • 🅿️Learn to Code 2 ⟩ Animatable
        • 🅿️Item
        • 🍄Actor
        • 🍄Expert
        • 🍄Portal
      • 關卡
        • Variables
          • 檢查相等值
          • 計算開關數量
          • 收集總數
        • Types
          • 開啟與關閉傳送門
          • 關閉傳送門
        • 創造世界
    • 編碼社群
    • Swift Playgrounds Subscription
  • 🥁Audio / Video
    • .speak()
    • Language Tags
  • ⛔Errors
    • ⛔CompileDylibError: Failed to build XXX.swift
    • ⛔Fatal error: String index is out of bounds
    • ❌Internal error: missingPackageDescriptionModule
    • ❌Argument type 'xxx' does not conform to expected type '_FormatSpecifiable'
    • ⛔️ escaping closure captures mutating 'self' parameter
    • ❓iPad/iPhone is Busy: Fetching debug symbols for ...
    • ❓Class _PointQueue is implemented in both ...
    • ⛔Protocol '...' can only be used as a generic constraint because it has Self or associated type
    • ⛔non-nominal type `...` cannot be extended
    • ⛔cannot assign value of type 'T' to type 'T'
  • 附錄
    • 🧩Swift Package
      • Package.swift
      • version number
      • Swift Package Index
      • 3rd party packages
    • 🔰Design Patterns
      • MVC
      • MVVM
      • DIP
    • 🛠️Xcode
      • 🔰build schemes
      • 🔧Instruments
      • 🤖Compiler Directives
        • @available, #available
        • #if ... #else ... #endif
        • #file, #line, #function
      • 🎹shortcuts
      • 🔰Documentation
        • 🔰DocC
      • Source Control
        • Merge
      • Preview
        • Preview Devices
      • App Icon
      • Simulator
    • 💼Projects
    • ✅Testing System
      • ✅Swift Testing
      • ✅XCTest
        • XCTAssertEqual (==)
        • XCTUnwrap
      • Fixtures
    • Prototyping
    • 💡Tips
      • iOS version
      • Swift version
    • JSON
      • Load JSON
    • Terms
      • reachability
    • 其他
      • 待解問題❓
      • 測試用 🧪
      • 關注對象
      • 重要語錄
      • 圖例
    • 雜記 📥
      • 📦Measurement
      • Replicating Types in Swift
      • 字型專有名詞
      • State Machine
Powered by GitBook
On this page
  • 程式碼
  • 使用 Veiw Preference
  • 使用 Anchor
  • 使用 Anchor + View extension

Was this helpful?

  1. ios.dev
  2. todo
  3. Examples

MonthView

這個例子用到 PreferenceKey (Frames) 來記錄所有 MonthView 的 frames,然後利用這些 frames 與 currentIndex 來動態決定「圓角框線」(roundedBorder) 的位置。

PreviousExamplesNextCircleText

Last updated 3 years ago

Was this helpful?

程式碼

使用 Veiw Preference

import SwiftUI
import PlaygroundSupport

// 月份名稱
let monthNames = [
    "ㄧ月", "二月", "三月","四月", "五月", "六月", 
    "七月", "八月", "九月","十月", "十一月", "十二月"
]

// ⭐️ 收集與處理所有的 MonthView's frame
typealias Frames = AllValues<CGRect>    // 📦 AllValues<T>

// live view
struct ContentView: View {
    let r: CGFloat = 1
    // view body
    var body: some View {
        YearView()   // 🌅 YearView
            .shadow(color: .black, radius: r, x: r, y: r)
    }
}

PlaygroundPage.current.setLiveView(ContentView())
// 🌅 YearView
struct YearView: View {
    
    // ⭐️ 當前月份
    @State private var currentIndex = 0
    // ⭐️ 準備接收來自 Frames 的資料,用於更新 MonthView
    @State private var frames = [CGRect](repeating: .zero, count: 12)
    
    // ⭐️ 當前月份的「圓角框線」
    var roundedBorder: some View {
        
        // ⭐️ 當前月份的 frame
        let rect = frames[currentIndex]
        let rounded = RoundedRectangle(cornerRadius: 4)
        
        return rounded
            .fill(Color.yellow.opacity(0.2))
            .overlay(rounded.stroke(Color.pink, lineWidth: 3))
            // ⭐️ 設定尺寸
            .frame(rect.size)    // 🌀View + frame
            // ⭐️ 設定位移 (注意:要配合 ZStack 的「對齊方式:.topLeading」才有效)
            .offset(x: rect.minX, y: rect.minY)
            .animation(.default)
    }
    
    // view body
    var body: some View {
        // ⭐️ 對齊「左上角」:
        ZStack(alignment: .topLeading) {
            // 📦 StackForEach
            HStackForEach(0..<4, spacing: 16) { j in     // 一年四季
                VStackForEach(0..<3, spacing: 8) { i in  // 每季三月
                    // 🌅 MonthView
                    MonthView(index: i + 3*j, current: self.$currentIndex)
                        .border(Color.gray.opacity(0.1))
                }
            }// HStackForEach (container)
                // ⭐️ 在這裡定義座標系統 "container"
                .coordinateSpace(name: "container")
            
            // ⭐️ current month view's border
            roundedBorder
            
        }// ZStack
            .padding()
            .background(Color.gray)
            .animation(.spring())
            // ⭐️ 根據收集來的 Frames,更新 self.frames
            .onPreferenceChange(Frames.self) { self.frames = $0 }
    }// body
}
// 🌅 MonthView
struct MonthView: View {
    
    @Binding var currentIndex: Int  // 接收與更新當前月份 (⭐️ @Binding)
    let index                : Int  // 自己的月份
    
    // init
    init(index: Int, current: Binding<Int>) {
        self._currentIndex = current
        self.index         = index
    }
    
    // view body
    var body: some View {
        Text(monthNames[index])
            .padding(.horizontal, 12)
            .padding(.vertical, 4)
            // ⭐️ 將自己的 frame 加到 Frames
            .appendFrame(to: Frames.self, in: .named("container"))  // 🌀View + ref
            .animation(.default)
            // ⭐️ 當按到時,主動更新 currentIndex
            .onTapGesture { self.currentIndex = self.index }
    }
}
// 初版: 2020.10.15

import SwiftUI
import PlaygroundSupport

// 月份名稱
let monthNames = [
    "ㄧ月", "二月", "三月","四月", "五月", "六月", 
    "七月", "八月", "九月","十月", "十一月", "十二月"
]

// ⭐️ 收集與處理所有的 MonthView's frame
typealias Frames = AllValues<CGRect>

// 🌅 MonthView
struct MonthView: View {
    
    @Binding var currentIndex: Int  // 接收與更新當前月份 (⭐️ @Binding)
    let index                : Int  // 自己的月份
    
    // init
    init(index: Int, current: Binding<Int>) {
        self._currentIndex = current
        self.index         = index
    }
    
    // view body
    var body: some View {
        Text(monthNames[index])
            .padding(.horizontal, 12)
            .padding(.vertical, 4)
            // ⭐️ 將自己的 frame 加到 Frames
            .appendFrame(to: Frames.self, in: .named("container"))  // 🌀View + ref
            .animation(.default)
            // ⭐️ 當按到時,主動更新 currentIndex
            .onTapGesture { self.currentIndex = self.index }
    }
}


// 🌅 YearView
struct YearView: View {
    
    // ⭐️ 當前月份
    @State private var currentIndex = 0
    // ⭐️ 準備接收來自 Frames 的資料,用於更新 MonthView
    @State private var frames = [CGRect](repeating: .zero, count: 12)
    
    // ⭐️ 當前月份的「圓角框線」
    var roundedBorder: some View {
        
        // ⭐️ 當前月份的 frame
        let rect = frames[currentIndex]
        let rounded = RoundedRectangle(cornerRadius: 4)
        
        return rounded
            .fill(Color.yellow.opacity(0.2))
            .overlay(rounded.stroke(Color.pink, lineWidth: 3))
            // ⭐️ 設定尺寸
            .frame(rect.size)    // 🌀View + frame
            // ⭐️ 設定位移 (注意:要配合 ZStack 的「對齊方式:.topLeading」才有效)
            .offset(x: rect.minX, y: rect.minY)
            .animation(.default)
    }
    
    // view body
    var body: some View {
        // ⭐️ 對齊「左上角」:
        ZStack(alignment: .topLeading) {
            // 📦 StackForEach
            HStackForEach(0..<4, spacing: 16) { j in     // 一年四季
                VStackForEach(0..<3, spacing: 8) { i in  // 每季三月
                    // 🌅 MonthView
                    MonthView(index: i + 3*j, current: self.$currentIndex)
                        .border(Color.gray.opacity(0.1))
                }
            }// HStackForEach (container)
                // ⭐️ 在這裡定義座標系統 "container"
                .coordinateSpace(name: "container")
            
            // ⭐️ current month view's border
            roundedBorder
            
        }// ZStack
            .padding()
            .background(Color.gray)
            .animation(.spring())
            // ⭐️ 根據收集來的 Frames,更新 self.frames
            .onPreferenceChange(Frames.self) { self.frames = $0 }
    }// body
}

struct ContentView: View {
    let r: CGFloat = 1
    // view body
    var body: some View {
        YearView()   // 🌅 YearView
            .shadow(color: .black, radius: r, x: r, y: r)
    }
}

// live view
PlaygroundPage.current.setLiveView(ContentView())
  • AllValues<T>

使用 Anchor

  • 省掉用 geo.frame(in: space) 換算座標。

  • 省掉用 .coordinateSpace(name:) 定義座標系統。

  • 省掉用 @State 變數來管理畫面更新。

import SwiftUI
import PlaygroundSupport

// ⭐️ 收集與處理所有的 MonthView's frame
typealias FrameAnchors = AllValues<Anchor<CGRect>>

// live view
struct ContentView: View {
    let r: CGFloat = 1
    // view body
    var body: some View {
        YearView()   // 🌅 YearView
            .shadow(color: .black, radius: r, x: r, y: r)
    }
}

PlaygroundPage.current.setLiveView(ContentView())
// 🌅 YearView
struct YearView: View {
    
    // ⭐️ 當前月份
    @State private var currentIndex = 0
    
    // ------------------------------------------------------
    // ⭐️ 由於使用 Anchor<CGRect> 來管理 MonthView 的 frames,
    //    所以 YearView 並不需要多一個 @State 變數來負責更新的工作。
    // ------------------------------------------------------
    
    // ⭐️ 當前月份的「圓角框線」
    func roundedBorder(anchors: FrameAnchors.Value) -> some View {
        
        // ⭐️ 當前月份的 anchor
        let anchor = anchors[currentIndex]                  // Anchor<CGRect>
        let rounded = RoundedRectangle(cornerRadius: 4)
        
        // ⭐️ 利用此輔助函數做計算,並傳回「圓角框線」給 GeometryReader 用
        func makeView(with geo: GeometryProxy) -> some View {
            
            // -------------------------------------------
            // ⭐️ 將 anchor 轉為這個座標系統的 frame (CGRect) 
            let rect = geo[anchor]
            // -------------------------------------------
            
            // 傳回「圓角框線」
            return rounded
                .fill(Color.yellow.opacity(0.2))
                .overlay(rounded.stroke(Color.pink, lineWidth: 3))
                // ⭐️ 設定尺寸
                .frame(rect.size)    // 🌀View + frame
                // ⭐️ 設定位移 (注意:要配合 ZStack 的「對齊方式:.topLeading」才有效)
                .offset(x: rect.minX, y: rect.minY)
                .animation(.default)
        }
        
        return GeometryReader { makeView(with: $0) }
    }
    
    // view body
    var body: some View {
        
        // 📦 StackForEach
        HStackForEach(0..<4, spacing: 16) { j in     // 一年四季
            VStackForEach(0..<3, spacing: 8) { i in  // 每季三月
                // 🌅 MonthView
                MonthView(index: i + 3*j, current: self.$currentIndex)
                    .border(Color.gray.opacity(0.1))
            }
        }// HStackForEach (container)
            // ------------------------------------------------------
            // ⭐️ 利用 FrameAnchors 的資料,在這個座標系統中畫「圓角框線」
            .overlayPreferenceValue(FrameAnchors.self) { (anchors) in
                self.roundedBorder(anchors: anchors)   }
            // ------------------------------------------------------
            .padding()
            .background(Color.gray)
            .animation(.spring())
            
    }// body
}
// 🌅 MonthView
struct MonthView: View {
    
    @Binding var currentIndex: Int  // 接收與更新當前月份 (⭐️ @Binding)
    let index                : Int  // 自己的月份
    
    // 月份名稱
    let monthNames = [
        "ㄧ月", "二月", "三月","四月", "五月", "六月", 
        "七月", "八月", "九月","十月", "十一月", "十二月"
    ]
    
    // init
    init(index: Int, current: Binding<Int>) {
        self._currentIndex = current
        self.index         = index
    }
    
    // view body
    var body: some View {
        Text(monthNames[index])
            .padding(.horizontal, 12)
            .padding(.vertical, 4)
            
            // ---------------------------------------------------------------
            // ⭐️ 將自己的 frame 加到 FrameAnchors
            .anchorPreference(key: FrameAnchors.self, value: .bounds) { [$0] }
            // ---------------------------------------------------------------
            
            .animation(.default)
            // ⭐️ 當按到時,主動更新 currentIndex
            .onTapGesture { self.currentIndex = self.index }
    }
}
import SwiftUI
import PlaygroundSupport

// ⭐️ 收集與處理所有的 MonthView's frame
typealias FrameAnchors = AllValues<Anchor<CGRect>>

// 🌅 MonthView
struct MonthView: View {
    
    @Binding var currentIndex: Int  // 接收與更新當前月份 (⭐️ @Binding)
    let index                : Int  // 自己的月份
    
    // 月份名稱
    let monthNames = [
        "ㄧ月", "二月", "三月","四月", "五月", "六月", 
        "七月", "八月", "九月","十月", "十一月", "十二月"
    ]
    
    // init
    init(index: Int, current: Binding<Int>) {
        self._currentIndex = current
        self.index         = index
    }
    
    // view body
    var body: some View {
        Text(monthNames[index])
            .padding(.horizontal, 12)
            .padding(.vertical, 4)
            // ---------------------------------------------------------------
            // ⭐️ 將自己的 frame 加到 FrameAnchors
            .anchorPreference(key: FrameAnchors.self, value: .bounds) { [$0] }
            // ---------------------------------------------------------------
            .animation(.default)
            // ⭐️ 當按到時,主動更新 currentIndex
            .onTapGesture { self.currentIndex = self.index }
    }
}


// 🌅 YearView
struct YearView: View {
    
    // ⭐️ 當前月份
    @State private var currentIndex = 0
    
    // ------------------------------------------------------
    // ⭐️ 由於使用 Anchor<CGRect> 來管理 MonthView 的 frames,
    //    所以 YearView 並不需要多一個 @State 變數來負責更新的工作。
    // ------------------------------------------------------
    
    // ⭐️ 當前月份的「圓角框線」
    func roundedBorder(anchors: FrameAnchors.Value) -> some View {
        
        // ⭐️ 當前月份的 anchor
        let anchor = anchors[currentIndex]                  // Anchor<CGRect>
        let rounded = RoundedRectangle(cornerRadius: 4)
        
        // ⭐️ 利用此輔助函數做計算,並傳回「圓角框線」給 GeometryReader 用
        func makeView(with geo: GeometryProxy) -> some View {
            
            // ⭐️ 將 anchor 轉為這個座標系統的 frame (CGRect) 
            let rect = geo[anchor]
            
            // 傳回「圓角框線」
            return rounded
                .fill(Color.yellow.opacity(0.2))
                .overlay(rounded.stroke(Color.pink, lineWidth: 3))
                // ⭐️ 設定尺寸
                .frame(rect.size)    // 🌀View + frame
                // ⭐️ 設定位移 (注意:要配合 ZStack 的「對齊方式:.topLeading」才有效)
                .offset(x: rect.minX, y: rect.minY)
                .animation(.default)
        }
        
        return GeometryReader { makeView(with: $0) }
    }
    
    // view body
    var body: some View {
        
        // 📦 StackForEach
        HStackForEach(0..<4, spacing: 16) { j in     // 一年四季
            VStackForEach(0..<3, spacing: 8) { i in  // 每季三月
                // 🌅 MonthView
                MonthView(index: i + 3*j, current: self.$currentIndex)
                    .border(Color.gray.opacity(0.1))
            }
        }// HStackForEach (container)
            // ------------------------------------------------------
            // ⭐️ 利用 FrameAnchors 的資料,在這個座標系統中畫「圓角框線」
            .overlayPreferenceValue(FrameAnchors.self) { (anchors) in
                self.roundedBorder(anchors: anchors)   }
            // ------------------------------------------------------
            .padding()
            .background(Color.gray)
            .animation(.spring())
            
    }// body
}

struct ContentView: View {
    let r: CGFloat = 1
    // view body
    var body: some View {
        YearView()   // 🌅 YearView
            .shadow(color: .black, radius: r, x: r, y: r)
    }
}

// live view
PlaygroundPage.current.setLiveView(ContentView())
  • 📦 PreferenceKeys

使用 Anchor + View extension

import SwiftUI
import PlaygroundSupport

// ⭐️ 收集與處理所有的 MonthView's frame
typealias FrameAnchors = AllValues<Anchor<CGRect>>

// live view
struct ContentView: View {
    let r: CGFloat = 1
    // view body
    var body: some View {
        YearView()   // 🌅 YearView
            .shadow(color: .black, radius: r, x: r, y: r)
    }
}

PlaygroundPage.current.setLiveView(ContentView())
// 🌅 YearView
struct YearView: View {
    
    // ⭐️ 當前月份
    @State private var currentIndex = 0
    
    // ------------------------------------------------------
    // ⭐️ 由於使用 Anchor<CGRect> 來管理 MonthView 的 frames,
    //    所以 YearView 並不需要多一個 @State 變數來負責更新的工作。
    // ------------------------------------------------------
    
    // ⭐️ 當前月份的「圓角框線」
    func roundedBorder(anchors: FrameAnchors.Value) -> some View {
        
        // ⭐️ 利用此輔助函數做計算,並傳回「圓角框線」給 GeometryReader 用
        func makeView(with geo: GeometryProxy) -> some View {
            
            // ------------------------------------------
            // ⭐️ 將 anchor 轉為這個座標系統的 frame (CGRect) 
            let anchor = anchors[currentIndex]     // Anchor<CGRect>
            let rect   = geo[anchor]               // CGRect
            // ------------------------------------------
            
            let rounded = RoundedRectangle(cornerRadius: 4)
            
            // 傳回「圓角框線」
            return rounded
                .fill(Color.yellow.opacity(0.2))
                .overlay(rounded.stroke(Color.pink, lineWidth: 3))
                // ⭐️ 設定尺寸
                .frame(rect.size)    // 🌀View + frame
                .offset(x: rect.minX, y: rect.minY)
                .animation(.default)
        }
        
        return GeometryReader { makeView(with: $0) }
    }
    
    // view body
    var body: some View {
        
        // 📦 StackForEach
        HStackForEach(0..<4, spacing: 16) { j in     // 一年四季
            VStackForEach(0..<3, spacing: 8) { i in  // 每季三月
                // 🌅 MonthView
                MonthView(index: i + 3*j, current: self.$currentIndex)
                    .border(Color.gray.opacity(0.1))
            }
        }// HStackForEach (container)
        
            // --------------------------------------------------
            // ⭐️ 利用 FrameAnchors 的資料,在這個座標系統中畫「圓角框線」
            .overlay(with: FrameAnchors.self) { // 🌀View + preference
                self.roundedBorder(anchors: $0) }
            // --------------------------------------------------
            
            .padding()
            .background(Color.gray)
            .animation(.spring())
            
    }// body
}
// 🌅 MonthView
struct MonthView: View {
    
    @Binding var currentIndex: Int  // 接收與更新當前月份 (⭐️ @Binding)
    let index                : Int  // 自己的月份
    
    // 月份名稱
    let monthNames = [
        "ㄧ月", "二月", "三月","四月", "五月", "六月", 
        "七月", "八月", "九月","十月", "十一月", "十二月"
    ]
    
    // init
    init(index: Int, current: Binding<Int>) {
        self._currentIndex = current
        self.index         = index
    }
    
    // view body
    var body: some View {
        Text(monthNames[index])
            .padding(.horizontal, 12)
            .padding(.vertical, 4)
            // ---------------------------------
            // ⭐️ 將自己的 frame 加到 FrameAnchors
            .register(to: FrameAnchors.self)  // 🌀View + preference
            // ---------------------------------
            .animation(.default)
            // ⭐️ 當按到時,主動更新 currentIndex
            .onTapGesture { self.currentIndex = self.index }
    }
}
/*
 Inspecting the View Tree – Part 2: AnchorPreferences
 https://swiftui-lab.com/communicating-with-the-view-tree-part-2/
 
 When using the Anchor<Value> as an index to the GeometryProxy, you get the represented CGRect or CGPoint value. And as a plus, you get it already translated to the coordinate space of the GeometryReader view.
 
 */

import SwiftUI
import PlaygroundSupport

// ⭐️ 收集與處理所有的 MonthView's frame
typealias FrameAnchors = AllValues<Anchor<CGRect>>

// 🌅 MonthView
struct MonthView: View {
    
    @Binding var currentIndex: Int  // 接收與更新當前月份 (⭐️ @Binding)
    let index                : Int  // 自己的月份
    
    // 月份名稱
    let monthNames = [
        "ㄧ月", "二月", "三月","四月", "五月", "六月", 
        "七月", "八月", "九月","十月", "十一月", "十二月"
    ]
    
    // init
    init(index: Int, current: Binding<Int>) {
        self._currentIndex = current
        self.index         = index
    }
    
    // view body
    var body: some View {
        Text(monthNames[index])
            .padding(.horizontal, 12)
            .padding(.vertical, 4)
            // ---------------------------------------------------------------
            // ⭐️ 將自己的 frame 加到 FrameAnchors
//              .anchorPreference(key: FrameAnchors.self, value: .bounds) { [$0] }
            .register(to: FrameAnchors.self)
            // ---------------------------------------------------------------
            .animation(.default)
            // ⭐️ 當按到時,主動更新 currentIndex
            .onTapGesture { self.currentIndex = self.index }
    }
}


// 🌅 YearView
struct YearView: View {
    
    // ⭐️ 當前月份
    @State private var currentIndex = 0
    
    // ------------------------------------------------------
    // ⭐️ 由於使用 Anchor<CGRect> 來管理 MonthView 的 frames,
    //    所以 YearView 並不需要多一個 @State 變數來負責更新的工作。
    // ------------------------------------------------------
    
    // ⭐️ 當前月份的「圓角框線」
    func roundedBorder(anchors: FrameAnchors.Value) -> some View {
        
        // ⭐️ 利用此輔助函數做計算,並傳回「圓角框線」給 GeometryReader 用
        func makeView(with geo: GeometryProxy) -> some View {
            
            // ------------------------------------------
            // ⭐️ 將 anchor 轉為這個座標系統的 frame (CGRect) 
            let anchor = anchors[currentIndex]             // Anchor<CGRect>
            let rect   = geo[anchor]                       // CGRect
            // ------------------------------------------
            
            let rounded = RoundedRectangle(cornerRadius: 4)
            
            // 傳回「圓角框線」
            return rounded
                .fill(Color.yellow.opacity(0.2))
                .overlay(rounded.stroke(Color.pink, lineWidth: 3))
                // ⭐️ 設定尺寸
                .frame(rect.size)    // 🌀View + frame
                // ⭐️ 設定位移 (注意:要配合 ZStack 的「對齊方式:.topLeading」才有效)
                .offset(x: rect.minX, y: rect.minY)
                .animation(.default)
        }
        
        return GeometryReader { makeView(with: $0) }
    }
    
    // view body
    var body: some View {
        
        // 📦 StackForEach
        HStackForEach(0..<4, spacing: 16) { j in     // 一年四季
            VStackForEach(0..<3, spacing: 8) { i in  // 每季三月
                // 🌅 MonthView
                MonthView(index: i + 3*j, current: self.$currentIndex)
                    .border(Color.gray.opacity(0.1))
            }
        }// HStackForEach (container)
            // --------------------------------------------------
            // ⭐️ 利用 FrameAnchors 的資料,在這個座標系統中畫「圓角框線」
            .overlay(with: FrameAnchors.self) { 
                self.roundedBorder(anchors: $0) }
            // --------------------------------------------------
            .padding()
            .background(Color.gray)
            .animation(.spring())
            
    }// body
}

struct ContentView: View {
    let r: CGFloat = 1
    // view body
    var body: some View {
        YearView()   // 🌅 YearView
            .shadow(color: .black, radius: r, x: r, y: r)
    }
}

// live view
PlaygroundPage.current.setLiveView(ContentView())

利用自製的 View extension 來收集所有 MonthView 的 frame,然後再由 parent view 的 .onPreferenceChange() 來更新 YearView 的 @State 變數 frames。

(Frames = AllValues<CGRect>)

+

(appendFrame)

- The SwiftUI Lab

使用自製的 🌀,可以讓語法更簡潔易懂。

🍎
🔲
💈
💈
.registerFrame(to: key, in: space)
💈 動態底線
🅿️ PreferenceKey
🌀 View
.frame(size)
🌀View + pref
📦 StackForEach
Inspecting the View Tree (Part 1): PreferenceKey
🌀 View
+ preference
View + preference
🌀 View
+ preference
📦 StackForEach
🔰 Anchor Preferences