NewsBlur/clients/ios/Classes/SwiftUIUtilities.swift
David Sinclair 8e522075fa #1720 (Grid view)
- Implemented marking as read on scroll.
- Increased the font size of the feed title and date/author.
- The read state of stories is now indicated correctly, including dimming read stories.
- Selecting a story scrolls to it, with animation.
2023-03-21 21:54:21 -07:00

123 lines
3.4 KiB
Swift

//
// 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`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
self
}
}
}
extension View {
@ViewBuilder
func modify<Content: View>(@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))
}
}
// From https://www.swiftbysundell.com/articles/observing-swiftui-scrollview-content-offset/
struct PositionObservingView<Content: View>: 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<Content: View>: 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)
}
}