SwiftUI graphic issues on setting offset - ios

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()
}
}

Related

swiftui -- tabview has error when use offset

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.

How to put view in specific location in ZStack based on another view?

I have a view in a ScrollView and base on an offset value. There is another view that needs to animate out.
This view is under the gray box but it places its base on a hardcoded value. How to pin the green box under the gray area without a hardcoded value? Using the hard coded value will not be consistent with other devices.
struct ContentView: View {
#State private var contentOffset = CGFloat(0)
var body: some View {
NavigationView {
ZStack {
VStack(spacing: 0) {
TrackableScrollView { offset in
contentOffset = offset.y
} content: {
VStack(spacing: 0) {
Text("Hello World")
.padding()
.frame(maxWidth: .infinity)
.background(Color.gray)
}
}
}
Text("HELLO")
.frame(maxWidth: .infinity)
.background(Color.green)
.opacity(contentOffset < -16 ? 0 : 1)
.animation(.easeIn(duration: 0.2), value: contentOffset)
.offset(y: -280)
}
.ignoresSafeArea()
.frame(maxHeight: .infinity, alignment: .top)
.background(AccountBackground())
.navigationBarHidden(true)
}
}
}
What it looks like:
When you scroll up the green box will disappear.
To do this you need to an overlay to your ZStack. If you have a navigation bar the offset that you need to give it is 0 as it will be placed automatically below it. The TrackableScrollView is a preference key that is commonly found online to track the scrolling position of the scroll view.
struct ContentView: View {
#State private var contentOffset = CGFloat(0)
#State private var offsetPositionValue: CGFloat = 0
#State private var heightOfFrame: CGFloat = 0
#State private var isShyHeaderVisible = false
var body: some View {
NavigationView {
ZStack {
TrackableScrollView { offset in
withAnimation {
contentOffset = offset.y
}
} content: {
Text("Hello World")
}
.overlay(
ZStack {
HStack {
Text("Total number of points")
.foregroundColor(.white)
.lineLimit(1)
Spacer()
Text("20,000 pts")
.foregroundColor(.white)
.padding(.leading, 50)
}
.padding(.horizontal)
.padding(.vertical, 8)
.frame(width: UIScreen.main.bounds.width)
.background(Color.green)
.offset(y: contentOffset < -16 ? 0 : heightOfFrame - 5)
.opacity(contentOffset < -16 ? 1 : 0)
.transition(.move(edge: .top))
}
.frame(maxHeight: .infinity, alignment: .top)
)
}
.navigationTitle("Hello")
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarTitleDisplayMode(.inline)
.frame(maxHeight: .infinity, alignment: .top)
.background(AccountBackground())
}
}
}

How to create dragable view from top in SwiftUI?

I am trying to create dragable view from top which is kind of overlay .. I am attaching something same but having bottom sheet but i am looking for Top sheet. When overlay come background should be blur.
I am not sure about how to implement hence dont have code..I tried parent child for List in SwiftUI but that is having diff. Behaviour.
Any example or suggestion is appropriated.
something like this?
struct SheetView: View {
let title: String
var body: some View {
NavigationView {
List {
ForEach(0..<30) { item in
Text("Item \(item)")
}
}
.listStyle(.plain)
.navigationTitle(title)
}
}
}
struct ContentView: View {
#State private var currentDrag: CGFloat = 0
#State private var stateDrag: CGFloat = -300
let minDrag: CGFloat = -UIScreen.main.bounds.height + 120
var body: some View {
ZStack {
VStack(spacing: 0) {
Spacer(minLength: 36)
SheetView(title: "Bottom Sheet")
.background(.primary)
.opacity(stateDrag + currentDrag > minDrag ? 0.5 : 1)
.blur(radius: stateDrag + currentDrag > minDrag ? 5 : 0)
}
VStack(spacing: 0) {
SheetView(title: "Top Sheet")
.background(.background)
.offset(x: 0, y: stateDrag + currentDrag)
Image(systemName: "line.3.horizontal").foregroundColor(.secondary)
.padding()
.frame(maxWidth: .infinity)
.background(.background)
.contentShape(Rectangle())
.offset(x: 0, y: stateDrag + currentDrag)
.gesture(DragGesture()
.onChanged { value in
withAnimation {
currentDrag = value.translation.height
}
}
.onEnded { value in
stateDrag += value.translation.height
stateDrag = max(stateDrag, minDrag)
currentDrag = .zero
})
}
}
}
}

Jumpy performance issue when offset and opacity used

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!

How to tease adjacent pages in PageTabView?

I am working with SwiftUI 2 and using a TabView with PageTabViewStyle.
Now, I am searching for a way to "tease" the pages adjacent to the current page like so:
Is it possible to achieve this effect with TabView and PageTabViewStyle?
I already tried to reduce the width of my TabView to be windowWidth-50. However, this did not lead to the adjacent pages being visible at the sides. Instead, this change introduced a hard vertical edge 50px left of the right window border, where new pages would slide in.
Here is a simple implementation. You can use the struct with the AnyView array or use the logic directly in your own implementation.
struct ContentView: View {
#State private var selected = 4
var body: some View {
// the trailing closure takes an Array of AnyView type erased views
TeasingTabView(selectedTab: $selected, spacing: 20) {
[
AnyView(TabContentView(title: "First", color: .yellow)),
AnyView(TabContentView(title: "Second", color: .orange)),
AnyView(TabContentView(title: "Fourth", color: .green)),
AnyView(TabContentView(title: "Fifth", color: .blue)),
AnyView(
Image(systemName: "lizard")
.resizable().scaledToFit()
.padding()
.frame(maxHeight: .infinity)
.border(.red)
)
]
}
}
}
struct TeasingTabView: View {
#Binding var selectedTab: Int
let spacing: CGFloat
let views: () -> [AnyView]
#State private var offset = CGFloat.zero
var viewCount: Int { views().count }
var body: some View {
VStack(spacing: spacing) {
GeometryReader { geo in
let width = geo.size.width * 0.7
LazyHStack(spacing: spacing) {
Color.clear
.frame(width: geo.size.width * 0.15 - spacing)
ForEach(0..<viewCount, id: \.self) { idx in
views()[idx]
.frame(width: width)
.padding(.vertical)
}
}
.offset(x: CGFloat(-selectedTab) * (width + spacing) + offset)
.animation(.easeOut, value: selectedTab)
.gesture(
DragGesture()
.onChanged { value in
offset = value.translation.width
}
.onEnded { value in
withAnimation(.easeOut) {
offset = value.predictedEndTranslation.width
selectedTab -= Int((offset / width).rounded())
selectedTab = max(0, min(selectedTab, viewCount-1))
offset = 0
}
}
)
}
//
HStack {
ForEach(0..<viewCount, id: \.self) { idx in
Circle().frame(width: 8)
.foregroundColor(idx == selectedTab ? .primary : .secondary.opacity(0.5))
.onTapGesture {
selectedTab = idx
}
}
}
}
}
}
struct TabContentView: View {
let title: String
let color: Color
var body: some View {
Text(title).font(.title)
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(color.opacity(0.4), ignoresSafeAreaEdges: .all)
.clipShape(RoundedRectangle(cornerRadius: 20))
}
}

Resources