I have created custom TabBar.
Everything works fine, until I embed one of the tab's view in to NavigationView, than result - appearance animation of a View from very top left corner to right corner (screen with text and yellow color):
How tabBar is done: basically View's body created using GeometryReader VStack/Zstack:
var body: some View {
GeometryReader { geometry in
let height = geometry.size.height
let width = geometry.size.width
let bottomSafeAreaInset = geometry.safeAreaInsets.bottom
let topSafeAreaInset = geometry.safeAreaInsets.top
let verticalSafeAreaInset = bottomSafeAreaInset + topSafeAreaInset
VStack(spacing: 0) {
// content
mainContentBody
.frame(width: width, height: height - heightOfTabBar)
.zIndex(0)
// some calculation ...
// tabBar
Spacer(minLength: 0)
BottomBar(barButtonItems: buttons)
.frame(width: width, height: tabBarHeightWithOffset)
.background(Color.gray)
.offset(y: isMenuShown ? tabBarHeightWithOffset : 0)
.edgesIgnoringSafeArea(.all)
.opacity(isMenuShown ? 0 : 1)
.tabContainerAnimation() // simple wrapper for animation with duration
//... other view in ZStack
// button
ZStack {
overlayButton
}
.offset(y: -initialButtonOffset + additionalOffsetForButton)
.tabContainerAnimation(delay: 0.25)
}
}
}
And Code for view on tab1:
struct Tab1View: View {
var body: some View {
NavigationView {
VStack {
Text("sdsd")
Color.orange
}
}
}
}
If I remove NavigationView this effect is removed also. So my question is - why do i have this unexpected animation? what done wrong?
Here is fix (tested with Xcode 12.1 / iOS 14.1)
struct Tab1View: View {
var body: some View {
GeometryReader { g in
NavigationView {
VStack {
Text("sdsd")
Color.orange
}
}
.animation(.none) // << this one !!
}
}
}
Related
I am trying to add a PKCanvasView to a scrollview with SwiftUI.
But when I try to draw in the PKCanvasView it scrolls the scrollview instead.
In other words, how could I deactivate the scrolling when the user interacts with a specific view inside the scrollview?
I parsed through a lot of examples and blog articles and almost similar things but they were either too old or not for SwiftUI...
EDIT
To give further details (and based on Guillermo answer), here is an additional sample:
struct ContentView: View {
#State private var scrollDisabled = false
var body: some View {
VStack {
ScrollView {
VStack {
ForEach(1..<50) { i in
Rectangle()
.fill((i & 1 == 0) ? .blue: .yellow)
.frame(width: 300, height: 50)
.overlay {
Text("\(i)")
}
}
}.frame(maxWidth: .infinity)
}
.scrollDisabled(scrollDisabled)
}
.frame(width: 300)
}
}
Note that you cannot scroll touching the leading/trailing white borders.
I'd like the same behavior for yellow items: if you try to move up/down while touching a blue cell it scrolls, but if you try the same with yellow the scrollview shouldn't scroll!
Sorry, I'm really trying my best to be clear... ^^
You can control how your elements respond to the drag gesture with the following:
.gesture(DragGesture(minimumDistance:))
For instance:
struct ContentView: View {
var body: some View {
VStack {
ScrollView {
VStack {
ForEach(1..<50) { i in
Rectangle()
.fill((i & 1 == 0) ? .blue: .yellow)
.frame(width: 300, height: 50)
.overlay {
Text("\(i)")
}
.gesture(DragGesture(minimumDistance: i & 1 == 0 ? 100 : 0))
}
}.frame(maxWidth: .infinity)
}
}
.frame(width: 300)
}
}
will block the scrolling for the yellow elements!
In iOS 16 was added a modifier scrollDisabled to achieve what you need here's an example:
struct Scroll: View {
#State private var scrollDisabled = false
var body: some View {
VStack {
Button("\(scrollDisabled ? "Enable" : "Disable") Scroll") {
scrollDisabled.toggle()
}
ScrollView {
VStack {
ForEach(1..<50) { i in
Rectangle()
.fill(.blue)
.frame(width: 50, height: 50)
.overlay {
Text("\(i)")
}
}
}.frame(maxWidth: .infinity)
}
.scrollDisabled(scrollDisabled)
}
}
}
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.
I'm having trouble placing a Slider as a view in the trailing of navigation bar. Since it's hooked to a state, changing the state of it is very jumpy and not smooth at all.
Here's my sample code:
struct ContentView: View {
#State var opacityValue: Double = 1
var body: some View {
NavigationView {
GeometryReader { geometry in
VStack {
Slider(value: self.$opacityValue, in: 0...100, step: 5) { changed in
}
Image(systemName: "photo.fill")
.foregroundColor(.blue)
.padding()
.opacity(opacityValue / 100)
}.navigationBarItems(trailing: HStack(spacing: 20) {
Slider(value: self.$opacityValue, in: 0...100, step: 5) { _ in
}.frame(width: 100)
}).frame(width: geometry.size.width / 2)
}
}
}
}
The slider that I have in the regular VStack changes the opacity very smooth (and it even moves the slider that is in the nav bar.) But on the other hand, moving the slider that is in the nav bar is very glitchy/jumpy.
Is there a way to make this work?
You can make new View model for Image with #Binding property and pass your opacity through it, which will stop glitching movement on slider when #State property changes and View have to be refreshed
struct ContentView: View {
#State var opacityValue: Double = 1
var body: some View {
NavigationView {
GeometryReader { geometry in
VStack {
Slider(value: self.$opacityValue, in: 0...100, step: 5) { _ in
}
ImageOpacityView(opacity: $opacityValue)
}.navigationBarItems(trailing: HStack(spacing: 20) {
Slider(value: self.$opacityValue, in: 0...100, step: 5) { _ in
}.frame(width: 100)
}).frame(width: geometry.size.width / 2)
}
}
}
struct ImageOpacityView: View {
#Binding var opacity: Double
var body: some View {
Image(systemName: "photo.fill")
.foregroundColor(.blue)
.padding()
.opacity(opacity / 100)
}
}
}
I have a SwiftUI View that is meant to be sort of a "warning" background that flashes on and off. It appears inside another View with a size that changes depending on device orientation. Everything works properly upon the first appearance, but if the device is rotated while the warning view is active it incorporates the frame of the previous orientation into the animation, making it not only flash on and off, but also move in and out of the view its positioned in, as well as change to and from the size it was on the previous orientation. Why is this happening and how can I fix it? Generic code:
struct PulsatingWarningBackground: View {
let color : Color
let speed : Double
#State private var opacity : Double = 1
var body: some View {
GeometryReader {geometry in
self.color
.opacity(self.opacity)
.animation(
Animation
.linear(duration: self.speed)
.repeatForever(autoreverses: true)
)
.onAppear(perform: {self.opacity = 0})
}
}
}
struct PulsatingWarningViewHolder: View {
var body: some View {
GeometryReader {geometry in
ZStack {
Color.white
ZStack {
Color.black
PulsatingWarningBackground(color: .red, speed: 1/4)
}.frame(width: geometry.frame(in: .local).width/5, height: geometry.frame(in: .local).height/10, alignment: .center)
}
}
}
}
You can apply animation only to opacity by using withAnimation(_:_:) inside onAppear(perform:). It works properly as you want.
struct PulsatingWarningBackground: View {
let color : Color
let speed : Double
#State private var opacity : Double = 1
var body: some View {
self.color
.opacity(self.opacity)
.onAppear(perform: {
withAnimation(Animation.linear(duration: self.speed).repeatForever()) {
self.opacity = 0
}
})
}
}
struct PulsatingWarningViewHolder: View {
var body: some View {
GeometryReader {geometry in
ZStack {
Color.white
ZStack {
Color.black
PulsatingWarningBackground(color: .red, speed: 1/4)
}
.frame(width: geometry.frame(in: .local).width/5, height: geometry.frame(in: .local).height/10, alignment: .center)
}
}
}
}
Using SwiftUI, I am trying to center a View on the screen and then give it a header and/or footer of variable heights.
Using constraints it would look something like this:
let view = ...
let header = ...
let footer = ...
view.centerInParent()
header.pinBottomToTop(of: view)
footer.pinTopToBottom(of: view)
This way, the view would always be centered on the screen, regardless of the size of the header and footer.
I cannot figure out how to accomplish this with SwiftUI. Using any type of HStack or VStack means the sizes of the header and footer push around the view. I would like to avoid hardcoding any heights since the center view may vary in size as well.
Any ideas? New to SwiftUI so advice is appreciated!
If I correctly understood your goal (because, as #nayem commented, at first time seems I missed), the following approach should be helpful.
Code snapshot:
extension VerticalAlignment {
private enum CenteredMiddleView: AlignmentID {
static func defaultValue(in dimensions: ViewDimensions) -> CGFloat {
return dimensions[VerticalAlignment.center]
}
}
static let centeredMiddleView = VerticalAlignment(CenteredMiddleView.self)
}
extension Alignment {
static let centeredView = Alignment(horizontal: HorizontalAlignment.center,
vertical: VerticalAlignment.centeredMiddleView)
}
struct TestHeaderFooter: View {
var body: some View {
ZStack(alignment: .centeredView) {
Rectangle().fill(Color.clear) // !! Extends ZStack to full screen
VStack {
Header()
Text("I'm on center")
.alignmentGuide(.centeredMiddleView) {
$0[VerticalAlignment.center]
}
Footer()
}
}
// .edgesIgnoringSafeArea(.top) // uncomment if needed
}
}
struct Header: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 40)
}
}
struct Footer: View {
var body: some View {
Rectangle()
.fill(Color.green)
.frame(height: 200)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
TestHeaderFooter()
}
}
Here's the code:
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
Rectangle()
.fill(Color.gray)
.frame(width: geometry.size.width, height: geometry.size.height * 0.1, alignment: .center)
Text("Center")
.frame(width: geometry.size.width, height: geometry.size.height * 0.2, alignment: .center)
Rectangle()
.fill(Color.gray)
.frame(width: geometry.size.width, height: geometry.size.height * 0.1, alignment: .center)
}
}
}
}
using GeometryReader you can apply the dynamic size for your views.
also here is screenshot for above code
put Spacer() between header view and footer view.
headerview()
Spacer()
footerview()