问题
I'm trying to make a View that will put a Blur() at the bottom of an iPhone layout, respecting safeareas and that I can easily reuse.
Something like this:
import SwiftUI
struct SafeBottomBlurContainer: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
ZStack {
Blur(style: self.colorScheme == .dark ? .systemThinMaterialDark : .systemThinMaterialLight)
.frame(
width: geometry.size.width,
height: geometry.safeAreaInsets.bottom + 50
)
Holder()
.padding(.bottom, geometry.safeAreaInsets.bottom)
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
}
struct Holder: View {
var body: some View {
Rectangle().opacity(0.5)
.frame(height: 50)
}
}
struct SafeBottomBlurContainer_Previews: PreviewProvider {
static var previews: some View {
SafeBottomBlurContainer()
}
}
Here is the ~~blue~~ blur extension, by the way:
import SwiftUI
struct Blur: UIViewRepresentable {
var style: UIBlurEffect.Style = .systemMaterial
func makeUIView(context: Context) -> UIVisualEffectView {
return UIVisualEffectView(effect: UIBlurEffect(style: style))
}
func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
uiView.effect = UIBlurEffect(style: style)
}
}
Now, what I'd like to do is somehow pass in Holder()
so that I can adjust the height of the Blur (which here is + 50). I feel like I should be using AnyView in some manner, but can't figure it out. That might be me on the wrong track.
Here is how I'd like to use it in a ContentView() example:
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.pink.edgesIgnoringSafeArea(.all)
SafeBottomBlurContainer(containedView: MyCustomViewWithWhateverHeight)
}
}
}
回答1:
Here is possible approach, that gives possibility to use container like
struct ContentView: View {
var body: some View {
ZStack {
Color.pink.edgesIgnoringSafeArea(.all)
// holder is not needed, because you can pass any view directly
SafeBottomBlurContainer(containedView: Text("Demo"))
}
}
}
and generic blur container
struct SafeBottomBlurContainer<V: View>: View {
@Environment(\.colorScheme) var colorScheme
var containedView: V
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
ZStack {
Blur(style: self.colorScheme == .dark ? .systemThinMaterialDark : .systemThinMaterialLight)
.frame(
width: geometry.size.width,
height: geometry.safeAreaInsets.bottom + 50
)
containedView
.padding(.bottom, geometry.safeAreaInsets.bottom)
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
}
回答2:
Taking the lead from @Asperi who really put me on the right track, I now have this, trying to also accmomodate whether this Blur()
view should be at the top or bottom of the screen:
import SwiftUI
struct SafeEdgesBlurContainer<V: View>: View {
@Environment(\.colorScheme) var colorScheme
@State var containedViewSize: CGSize = .zero
var containedView: V
var isPlacedAtTop: Bool
var body: some View {
GeometryReader { geometry in
VStack {
if !isPlacedAtTop {
Spacer()
}
ZStack {
Blur(style: self.colorScheme == .dark ? .systemThinMaterialDark : .systemThinMaterialLight)
.frame(
width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing,
height: (isPlacedAtTop ? geometry.safeAreaInsets.top : geometry.safeAreaInsets.bottom ) + containedViewSize.height
)
.edgesIgnoringSafeArea([.leading])
HStack {
ChildSizeReader(size: $containedViewSize) {
containedView
}
.frame(width: geometry.size.width) // ADDED This line to keep the ContainedView centered in Landscape
.padding(isPlacedAtTop ? .top : .bottom, isPlacedAtTop ? geometry.safeAreaInsets.top : geometry.safeAreaInsets.bottom)
}
}
if isPlacedAtTop {
Spacer()
}
}
.edgesIgnoringSafeArea(isPlacedAtTop ? .top : .bottom)
}
}
}
It turns out to be not that hard to get the height of the ContainedView(). I found this solution from here: SwiftUI - Get size of child?
struct ChildSizeReader<Content: View>: View {
@Binding var size: CGSize
let content: () -> Content
var body: some View {
ZStack {
content()
.background(
GeometryReader { proxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: proxy.size)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) { preferences in
self.size = preferences
}
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value _: inout Value, nextValue: () -> Value) {
_ = nextValue()
}
}
The remaining problem I see is that the ContainedView() is not being centered in landscape view. But hopefully, I can come back soon and edit in a solution for that.
EDIT: This might be that solution: .frame(width: geometry.size.width)
on the HStack.
来源:https://stackoverflow.com/questions/62860307/how-do-i-pass-a-view-into-a-struct-while-getting-its-height-also