Skip to content

Instantly share code, notes, and snippets.

@HarshilShah
Last active January 4, 2024 14:07
Show Gist options
  • Save HarshilShah/edafeb47e0cc951e5f23b6c317dd9dcb to your computer and use it in GitHub Desktop.
Save HarshilShah/edafeb47e0cc951e5f23b6c317dd9dcb to your computer and use it in GitHub Desktop.
Trying to backport the `safeAreaInset` modifier to iOS 14
struct ContentView: View {
@State private var text = ""
var body: some View {
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(1 ..< 20) { number in
Text("Row number \(number)")
.padding()
}
}
}
.background(
LinearGradient(
gradient: Gradient(colors: [.blue, .green]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
)
// .overlay(
// searchField,
// alignment: .bottom
// )
.safeAreaInset(edge: .bottom) {
searchField
}
}
var searchField: some View {
TextField("Some Text Goes Here", text: $text)
.padding()
.background(RoundedRectangle(cornerRadius: 16).fill(Color.white))
.padding()
}
}
import SwiftUI
struct HeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
enum HorizontalEdge {
case top, bottom
}
extension EdgeInsets {
init(_ edge: HorizontalEdge, _ inset: CGFloat) {
switch edge {
case .top: self.init(top: inset, leading: 0, bottom: 0, trailing: 0)
case .bottom: self.init(top: 0, leading: 0, bottom: inset, trailing: 0)
}
}
func uiEdgeInsets(in direction: LayoutDirection) -> UIEdgeInsets {
if direction == .rightToLeft {
return UIEdgeInsets(top: top, left: leading, bottom: bottom, right: trailing)
} else {
return UIEdgeInsets(top: top, left: leading, bottom: bottom, right: trailing)
}
}
}
struct AdditionalSafeAreaInsetsView<Content: View>: UIViewControllerRepresentable {
let insets: EdgeInsets
@ViewBuilder var content: () -> Content
@Environment(\.layoutDirection) private var layoutDirection
func makeUIViewController(context: Context) -> UIHostingController<Content> {
let viewController = UIHostingController(rootView: content())
viewController.additionalSafeAreaInsets = insets.uiEdgeInsets(in: layoutDirection)
return viewController
}
func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: Context) {
uiViewController.additionalSafeAreaInsets = insets.uiEdgeInsets(in: layoutDirection)
uiViewController.rootView = content()
}
}
struct SafeAreaInsetView<Base: View, Bar: View>: View {
let edge: HorizontalEdge
let base: Base
@ViewBuilder let content: () -> Bar
@State private var barHeight: CGFloat = 0
var body: some View {
let alignment: Alignment = {
switch edge {
case .top: return .top
case .bottom: return .bottom
}
}()
AdditionalSafeAreaInsetsView(insets: EdgeInsets(edge, barHeight)) {
base
}
.overlay(
content()
.background(
GeometryReader { proxy in
Color.clear.preference(key: HeightPreferenceKey.self, value: proxy.size.height)
}
.onPreferenceChange(HeightPreferenceKey.self) { value in
self.barHeight = value
}
),
alignment: alignment
)
}
}
extension View {
func safeAreaInset<Content: View>(
edge: HorizontalEdge,
@ViewBuilder _ content: @escaping () -> Content
) -> some View {
SafeAreaInsetView(edge: edge, base: self, content: content)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment