.badge()

ๆ–นๆณ•ไธ€๏ผš.overlay(_:alignment)

extension View {
    
    /// โญ๏ธ add a badge to the view
    ///
    /// Example
    /// ```
    /// calendarImage
    ///   .badge(badge, alignment: .topLeading)
    /// ```
    /// ๆณจๆ„๏ผšSwiftUI ๅ…งๅปบไนŸๆœ‰่จฑๅคš `.badge(_:)` view modifier๏ผŒไพ‹ๅฆ‚๏ผš
    /// - [`.badge(_ label: Text?)`][1]
    /// - [`.badge<S: StringProtocol>(_ label: S?)`][2]
    /// - [`.badge(_ key: LocalizedStringKey?)`][3]
    /// - [`.badge(_ count: Int)`][4]
    ///
    /// ๆ‰€ไปฅๅœจ่‡ช่ฃฝ `View` extension ๆˆ– view modifier ็š„ๆ™‚ๅ€™๏ผŒ่ฆ้ฟๅ…
    /// ่ˆ‡ๅฎƒๅ€‘็›ธ่กใ€‚
    ///
    /// ๆณจๆ„๏ผš้€™ไบ›ๅ…งๅปบ็š„ `.badge(_:)` ๅช้ฉ็”จๆ–ผ `List` rows ่ˆ‡ iOS tab barsโ—๏ธ
    ///
    /// [1]: https://developer.apple.com/documentation/swiftui/navigationview/badge(_:)-3fnhw
    /// [2]: https://developer.apple.com/documentation/swiftui/navigationview/badge(_:)-3069i
    /// [3]: https://developer.apple.com/documentation/swiftui/navigationview/badge(_:)-cc1t
    /// [4]: https://developer.apple.com/documentation/swiftui/navigationview/badge(_:)-57jkr
    func badge<Badge: View>(_ badge: Badge, alignment: Alignment = .topTrailing) -> some View {
            overlay(alignment: alignment) {
                badge.alignmentGuide(alignment.horizontal) { dim in
                    dim[HorizontalAlignment.center]
                }.alignmentGuide(alignment.vertical) { dim in
                    dim[VerticalAlignment.center]
                }
            }
    }
}

ๆ–นๆณ•ไบŒ๏ผšview preference

ๅˆฉ็”จ view preference ไพ†้€š็ŸฅไธŠ(ๅค–)ๅฑค็š„ view ่‡ชๅทฑ็š„ size๏ผŒไธฆๅฐ‡่‡ชๅทฑ็š„ view ๆ”พๅœจๅณไธŠ่ง’ใ€‚

้›–็„ถๅฏไปฅ็”จ view preference ไพ†ๆŽ’็‰ˆ๏ผŒไฝ†ๆฏ”่ผƒๅปบ่ญฐ็š„ๆ–นๅผ้‚„ๆ˜ฏ็”จไธŠ้ข็š„ alignmentใ€‚

import SwiftUI

/// ๅ•๏ผšๅฏไปฅ็”จ anchor preference ่ฎ“ badge ็š„ๆ“บๆ”พๆ›ด็ฐกๅ–ฎๅ—Ž๏ผŸ

/// โญ๏ธ 1. define size preference key:
///       pass view size to its ancestors (by using preference key)
struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

/// โญ๏ธ 2. define a view modifier to set size preference key:
///       get view size and set size preference key
struct SetSizePreference: ViewModifier {
    /// body: `ViewModifier` requirement
    func body(content: Content) -> some View {
        /// โญ๏ธ 2.1 use `GeometryReader` to read view size
        content.background(GeometryReader { geo in
            /// โญ๏ธ 2.2 use .preference() method to set `SizePreferenceKey`
            ///        on a transparent background
            Color.clear.preference(
                key: SizePreferenceKey.self,
                value: geo.size
            )
        })
    }
}


/// โญ๏ธ 3. define a convenience extension to set size preference
extension View {
    func getSize() -> some View {
        modifier(SetSizePreference())
    }
}


/// โญ๏ธ 4. define a view modifier to add badge view
struct AddBadge<Badge: View>: ViewModifier {
    
    var badge: Badge
    var showBadge: Bool
    
    /// โญ๏ธ 4.1 prepare to read badge size from child's size preference
    @State private var badgeSize: CGSize = .zero
    
    func body(content: Content) -> some View {
        
        ZStack(alignment: .topTrailing) {
            
            /// original view
            content
            
            /// add a badge to it if `showBadge`
            if showBadge {
                badge
                    /// โญ๏ธ 4.2 get badge size and let ancestors know about it
                    ///        by setting size preference.
                    .getSize()
                    /// โญ๏ธ 4.3 when get badge size from child's size preference,
                    ///        update view state to reflect the change.
                    .onPreferenceChange(SizePreferenceKey.self) {
                        self.badgeSize = $0
                    }
                    /// โญ๏ธ 4.4 center badge to the parent's top right corner
                    ///        according to its size.
                    .offset(x: badgeSize.width / 2, y: -badgeSize.height / 2)
                    .shadow(radius: 4)
            }
        }
    }
}

/// โญ๏ธ 5. define a convenience extension to add badge
extension View {
    func addBadge<Badge: View>(_ badge: Badge,`if` showBadge: Bool) -> some View {
        modifier(AddBadge(badge: badge, showBadge: showBadge))
    }
}

Last updated