I found the height was wrong when I'm dragging,how can i fix it?
The height in the tabview will be subtracted from the safe height when dragging.
work with tabview with background
work with tabview without background
work with backgroun without tabview
struct TabViewTest: View {
#State var offset: CGFloat = 0
#State var startY: CGFloat = 0
var body: some View {
ZStack(alignment: .top) {
VStack {
// work without tabview
// GeometryReader { proxy in
// ZStack {
// Color.green
// .frame(height: 100)
// Text("row height:\(proxy.size.height)")
// }
// }
// work with tabview without background
// TabView {
// ForEach(0..<5) { i in
// GeometryReader { proxy in
// Text("row\(i) height:\(proxy.size.height)")
// }
// }
// }
// work with tabview
TabView {
ForEach(0..<5) { i in
GeometryReader { proxy in
ZStack {
Color.green
.frame(height: 100)
Text("row\(i) height:\(proxy.size.height)")
}
}
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.frame(height: 150)
.background(.gray)
.offset(y: offset)
Spacer()
}
Color.brown
.frame(height: 300)
.offset(y: 200 + offset)
.gesture(
DragGesture()
.onChanged({ value in
offset = value.translation.height + startY
})
.onEnded({ value in
startY = offset
})
)
}
}
}
I hope the Tabview has a correct height,please help me.
Related
Is this the best way to move a stack in a view?
The HStack wrapped by ZStack is producing graphic issues when its becomes offset on drag gesture.
struct ContentView: View {
#State var offset: CGFloat = 0
var body: some View {
ZStack {
HStack {
VStack {
Text("Hello, world!")
Spacer(minLength: 0)
}
.background(Color.blue)
}
.offset(x: offset)
.gesture(
DragGesture()
.onChanged({ (value) in
withAnimation{
offset = value.translation.width
}
})
.onEnded({ (value) in
withAnimation{
offset = CGFloat(0)
}
})
)
}.padding()
}
}
I have a header that is fixed in place using an offset relative to the scroll position. Strangely enough though, when the contents of the scroll view has a dynamic opacity to the buttons, the offset is very jumpy:
This is the scroll view code, the HeaderView is "fixed" in place by pinning the offset to the scroll view's offset. The opacity seems to be causing the performance issue is on the MyButtonStyle style on the last line of code:
struct ContentView: View {
#State private var isPresented = false
#State private var offsetY: CGFloat = 0
#State private var headerHeight: CGFloat = 200
var body: some View {
GeometryReader { screenGeometry in
ZStack {
Color(.label)
.ignoresSafeArea()
ScrollView {
VStack(spacing: 0.0) {
Color.clear
.frame(height: headerHeight)
.overlay(
HeaderView(isPresented: $isPresented)
.offset(y: offsetY != 0 ? headerHeight + screenGeometry.safeAreaInsets.top - offsetY : 0)
)
VStack(spacing: 16) {
VStack(alignment: .leading) {
ForEach(0...10, id: \.self) { index in
Button("Button \(index)") {}
.buttonStyle(MyButtonStyle(icon: Image(systemName: "alarm")))
}
}
Spacer()
}
.frame(maxWidth: .infinity, minHeight: screenGeometry.size.height)
.padding()
.background(
GeometryReader { geometry in
Color.white
.cornerRadius(32)
.onChange(of: geometry.frame(in: .global).minY) { newValue in
offsetY = newValue
}
}
)
}
}
}
.alert(isPresented: $isPresented) { Alert(title: Text("Button tapped")) }
}
}
}
struct HeaderView: View {
#Binding var isPresented: Bool
var body: some View {
VStack {
Image(systemName: "bell")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(Color(.systemBackground))
Button(action: { isPresented = false }) {
Text("Press")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(16)
}
}
.padding()
}
}
struct MyButtonStyle: ButtonStyle {
let icon: Image
func makeBody(configuration: Configuration) -> some View {
Content(
configuration: configuration,
icon: icon
)
}
struct Content: View {
let configuration: Configuration
let icon: Image
var body: some View {
HStack(spacing: 18) {
Label(
title: { configuration.label },
icon: { icon.padding(.trailing, 8) }
)
Spacer()
Image(systemName: "chevron.right")
.accessibilityHidden(true)
}
.padding(18)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(8)
.opacity(configuration.isPressed ? 0.5 : 1) // <-- Comment this out and jumpiness goes away!!
}
}
}
Is there a performance improvement that can be done to use the opacity on the button press and make the jumpiness go away? Or a different way to approach this sticky offset because not sure if this is actually the source of the issue and I use opacity in a lot of places in my app (not just button presses)? The purpose of doing it this way is so the button can be tapped instead of putting it in the background of the scroll view. Thanks for any help or insight!
I'm trying to build a view where the header is fixed at the top of the view and it changes it's size according to the scroll offset, when the offset is 0 the header is bigger and when the user scrolls the header becomes smaller
struct ContentView : View {
#State var largeHeader = true
var body: some View {
VStack {
Text("HEADER").padding(.vertical, largeHeader ? 30 : 10)
Divider()
ScrollView {
VStack {
Text("Content0")
.padding()
Text("Content1")
.padding()
Text("Content2")
.padding()
}
.background(GeometryReader { geometryProxy -> Color in
DispatchQueue.main.async {
largeHeader = geometryProxy.frame(in: .named("myspace")).minY >= 0
}
return Color.clear
})
}
.coordinateSpace(name: "myspace")
}.animation(.default)
}
}
It works fine when the scroll content is longer, but when there is a little content, as in the code above I get this flickering (It's even worse on the device, but the gif quality is low)
Any idea how to fix it?
Looks like there is some interference going on.
I have found two workaround-solutions, it depends on what your desired effect is.
Solution 1
Idea: Using ZStack so that ScrollView and your header don't interfere.
struct ContentView: View {
#State var largeHeader = true
var body: some View {
ZStack {
ScrollView {
VStack {
ForEach(0..<3) { i in
Text("Content\(i)")
.padding()
}
}
.background(GeometryReader { geometryProxy -> Color in
DispatchQueue.main.async {
largeHeader = geometryProxy.frame(in: .named("1")).minY >= 0
}
return Color.clear
})
}
.coordinateSpace(name: "1")
.offset(y: largeHeader ? 100 : 60)
VStack {
VStack {
Spacer()
Text("HEADER")
.padding()
Divider()
}
.frame(maxWidth: .infinity)
.frame(height: largeHeader ? 140 : 100)
.background(Color.white)
Spacer()
}
.edgesIgnoringSafeArea(.all)
}
.animation(.default)
}
}
The header of this one would always change the height, no matter how large the content-height is.
Solution 2
Idea: Only change header height when there is enough content to scroll.
Solution:
Finding out the height of the scrollview-content.
struct ContentView: View {
#State var largeHeader = true
#State var scrollViewScrollable = false
var body: some View {
VStack {
Text("HEADER").padding(.vertical, largeHeader ? 30 : 10)
Divider()
ScrollView {
VStack {
ForEach(0..<3) { i in
Text("Content\(i)")
.padding()
}
}
.background(GeometryReader { geometryProxy -> Color in
if scrollViewScrollable {
DispatchQueue.main.async {
largeHeader = geometryProxy.frame(in: .named("1")).minY >= 0
}
}
return Color.clear
})
.background(
GeometryReader { proxy in
Color.clear.onAppear {
scrollViewScrollable = proxy.frame(in: .named("1")).size.height >= UIScreen.main.bounds.size.height - 100
}
}
)
}
.coordinateSpace(name: "1")
}
.animation(.default)
}
}
I'm trying to animate an image inside a "row" by it's y axis so that it appears that you would the view would slowly scroll vertically through the entire image. And when done, it backtracks. I hope that makes sense. I'm trying to do it in this code:
var body: some View {
ZStack {
HStack {
GeometryReader { geometry in
WebImage(url: self.url)
.renderingMode(.original)
.resizable()
.placeholder {
ImageStore.shared.image(name: "exploras-icon")
}
.aspectRatio(contentMode: .fill)
.animate {
// animate by y offset within bounds of HStack
}
}
}
.frame(height: 140)
.clipped()
}
}
Any help/guidance much appreciated!
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4
struct TestAutoScrollImage: View {
#State private var isActive = false
var body: some View {
GeometryReader { gp in
HStack {
Image("large_image")
}
.frame(width: gp.size.width, height: gp.size.height, alignment: self.isActive ? .bottom : .top)
}
.edgesIgnoringSafeArea([.top, .bottom])
.animation(Animation.linear(duration: 5).repeatForever())
.onAppear {
self.isActive = true
}
}
}
I am trying to make a transition from bottom edge to top when a view appears.
ZStack{
VStack {
.
.//other components
.
if self.modalShown {
HStack {
GeometryReader { geometry in
BottomSheetView(
isOpen: self.$modalShown,
maxHeight: geometry.size.height * 0.6
) {
ZStack(alignment: .bottom) {
FiltersView(hideControl: self.$modalShown)
.transition(.move(edge: .bottom))
}
.edgesIgnoringSafeArea(.bottom)
}
}
}
.accessibility(identifier: "modalFilterView")
}
}
The problem is that when this view appears, it performs a kind of alpha 0 to alpha 1 transition. And when the view is dismissed it does not perform any transition.
Could be related this undesired behavior with the zIndex?
The problem was that the transition modifier was in the wrong place:
ZStack{
VStack {
.
.//other components
.
if self.modalShown {
HStack {
GeometryReader { geometry in
BottomSheetView(
isOpen: self.$modalShown,
maxHeight: geometry.size.height * 0.6
) {
ZStack(alignment: .bottom) {
FiltersView(hideControl: self.$modalShown)
}
.edgesIgnoringSafeArea(.bottom)
}
}
}
.transition(.move(edge: .bottom))
.accessibility(identifier: "modalFilterView")
}
}