// // SwiftUIUtilities.swift // NewsBlur // // Created by David Sinclair on 2023-02-01. // Copyright © 2023 NewsBlur. All rights reserved. // import SwiftUI /// Some useful SwiftUI extensions. extension View { @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { if condition { transform(self) } else { self } } } extension View { @ViewBuilder func modify(@ViewBuilder _ transform: (Self) -> Content?) -> some View { if let view = transform(self), !(view is EmptyView) { view } else { self } } } extension View { func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { clipShape(RoundedCorner(radius: radius, corners: corners)) } } struct RoundedCorner: Shape { var radius: CGFloat = .infinity var corners: UIRectCorner = .allCorners func path(in rect: CGRect) -> Path { let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) return Path(path.cgPath) } } extension Color { static var random: Color { return Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1) ) } static func themed(_ hex: [NSNumber]) -> Color { return Color(ThemeManager.color(fromRGB: hex)) } } extension NSArray { @objc(safeObjectAtIndex:) func safeObject(at index: Int) -> Any? { if index >= 0, index < count { return self[index] } else { return nil } } } // From https://www.swiftbysundell.com/articles/observing-swiftui-scrollview-content-offset/ struct PositionObservingView: View { var coordinateSpace: CoordinateSpace @Binding var position: CGPoint @ViewBuilder var content: () -> Content var body: some View { content() .background(GeometryReader { geometry in Color.clear.preference( key: PreferenceKey.self, value: geometry.frame(in: coordinateSpace).origin ) }) .onPreferenceChange(PreferenceKey.self) { position in self.position = position } } } private extension PositionObservingView { struct PreferenceKey: SwiftUI.PreferenceKey { static var defaultValue: CGPoint { .zero } static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { // No-op } } } struct OffsetObservingScrollView: View { var axes: Axis.Set = [.vertical] var showsIndicators = true @Binding var offset: CGPoint @ViewBuilder var content: () -> Content // The name of our coordinate space doesn't have to be // stable between view updates (it just needs to be // consistent within this view), so we'll simply use a // plain UUID for it: private let coordinateSpaceName = UUID() var body: some View { ScrollView(axes, showsIndicators: showsIndicators) { PositionObservingView( coordinateSpace: .named(coordinateSpaceName), position: Binding( get: { offset }, set: { newOffset in offset = CGPoint( x: -newOffset.x, y: -newOffset.y ) } ), content: content ) } .coordinateSpace(name: coordinateSpaceName) } }