SwiftUI - animating View opacity in ZStack with .easeInOut - animation

I have a view sitting on top of a mapView (in a ZStack) and want to be able to have the green, upper view fade in and out with the .easeInOut animation modifier applied to the view's opacity. As you can see in the gif, it fades in nicely but disappears abruptly.
If I remove the mapView then all is good. If I replace the mapView with a simple Rectangle() then the problem returns so I believe it has something to do with the ZStack rather than the map. Funnily enough I'm actually using Mapbox rather than MapKit (as in the code below for simplicity) and the fade/abrupt disappear behaviour is reversed.
import SwiftUI
import MapKit
struct ContentView: View {
#State private var show = false
var body: some View {
VStack {
ZStack {
MapView()
if show {
LabelView()
.transition(AnyTransition.opacity.animation(.easeInOut(duration: 1.0)))
}
}
Button("Animate") {
self.show.toggle()
}.padding(20)
}
}
}
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.mapType = .standard
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) { }
}
struct LabelView: View {
var body: some View {
Text("Hi there!")
.padding(10)
.font(.title)
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 8).fill(Color.green).shadow(color: .gray, radius: 3))
}
}
I have tried using alternative animation code, replacing the LabelView transition with:
.transition(.opacity)
and changing the button code to:
Button("Animate") {
withAnimation(.easeInOut(duration: 1.0)) {
self.show.toggle()
}
}
but the same behaviour appears each time. I'm guessing this is a SwiftUI bug but couldn't find any previous reference.

Here is working solution. Tested with Xcode 11.4 / iOS 13.4.
As it seen in demo presence of transparent label does not affect functionality of map view.
var body: some View {
VStack {
ZStack {
MapView()
LabelView().opacity(show ? 1 : 0) // here !!
}.animation(.easeInOut(duration: 1.0))
Button("Animate") {
self.show.toggle()
}.padding(20)
}
}
Another alternate, actually with the same visual effect is to embed LabelView into container and apply transition to it (it must be left something to render view disappearing)
var body: some View {
VStack {
ZStack {
MapView()
VStack { // << here !!
if show {
LabelView()
}
}.transition(.opacity).animation(.easeInOut(duration: 1.0))
}
Button("Animate") {
self.show.toggle()
}.padding(20)
}
}

try this: -> just added zIndex ...everything else the same
struct ContentView: View {
#State private var show = false
var body: some View {
VStack {
ZStack {
MapView().zIndex(0)
if show {
LabelView()
.zIndex(1)
.transition(AnyTransition.opacity.animation(.easeInOut(duration: 1.0)))
}
}
Button("Animate") {
self.show.toggle()
}.padding(20)
}
}
}
and read this:
Transition animation not working in SwiftUI

Related

Animation with offset lags behind

I want a navigation bar to stick down to the scrollview when scrolling up beyond the "regular scrollview". I use .offset() and GeometryReader for that and it's working. However, the navigation bar noticeably lags behind: Video.
Is there another approach to achieving the sticky navigation bar or something that can be changed about this one? Am I using too many views?
struct V_Home: View {
var previewData = PreviewData()
#State var size: CGRect = .zero
var body: some View {
GeometryReader { geometry in
ZStack {
ScrollView {
VStack {
// used to read the scroll position
GeometryReader { proxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: proxy.frame(in: .named("scrollView")))
}
.frame(height: 0)
.onPreferenceChange(SizePreferenceKey.self) { preferences in
self.size = preferences
}
// List
ForEach(previewData.ScoreSessionList) { scoreSession in
NavigationLink(destination: V_SessionDetail(scoreSession: scoreSession)) {
HStack(spacing: 0) {
V_ScoreSessionListItem(scoreSession: scoreSession)
}
}.padding(.top, 10)
}
.padding([.leading, .trailing], 25)
}
}
.coordinateSpace(name: "scrollView")
// NavBar
VStack {
// This Rectangle is offset to match the scroll position
// Is is lagging behind noticably
Rectangle()
.fill(Color(.green))
.frame(height: 80)
.offset(y: self.size.minY > 0 ? self.size.minY : 0)
.padding(0)
Spacer()
}
}
.edgesIgnoringSafeArea(.all)
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
// used to make scrollview position accessible to other view
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGRect
static var defaultValue: Value = .zero
static func reduce(value _: inout Value, nextValue: () -> Value) {
_ = nextValue()
}
}

SwiftUI ScrollView content height

I have a problem trying to determine the height of a view inside a ScrollView
struct ContentView: View {
var body: some View {
ScrollView{
GeometryReader { geometry in
VStack{
ForEach(0..<90) { index in
Text("test \(geometry.size.height)")
}
}
}
}
}
}
geometry.size.height is always 10.000.
how can i correct this unexpected result?
Thanks so much!
I'm not sure where you're going with this.
GeometryReader reads the size offered to the view, not the size of the view. You can find out how big the view is by putting a GeometryReader in an .overlay of the VStack, because by the time the overlay is created, the size of the VStack will have been established and that size will be offered to the overlay.
struct ContentView: View {
var body: some View {
ScrollView{
VStack{
ForEach(0..<90) { index in
Text("test 000000000000")
.opacity(0)
}
}
.overlay(
GeometryReader { geometry in
VStack {
ForEach(0..<90) { index in
Text("test \(geometry.size.height)")
}
}
}
)
}
}
}

SWIFTUI: Take out a gray rectangle on top of the TabBar

I have a problem related on my List view. The question is simple: how can I get rid of that wierd gray rectangle showing on top of the TabBar? I didn't code that, I just implemented a controller with a List and NavigationBar and then it showed that thing.
For more clear explanation I post the images:
ItemRow.swift code:
import SwiftUI
struct ItemRow: View {
static let colors: [String: Color] = ["D": .purple, "G": .orange, "N": .red, "S": .yellow, "V": .pink]
var item: MenuItem
var body: some View {
NavigationLink(destination: Text(item.name)) {
HStack {
Image(item.thumbnailImage)
.clipShape(Circle())
.overlay(Circle().stroke(Color("IkeaBlu"), lineWidth: 2))
VStack(alignment: .leading){
Text(item.name)
.font(.headline)
Text("€ \(item.price)")
}.layoutPriority(1)
Spacer()
ForEach(item.restrictions, id: \.self) { restriction in
Text(restriction)
.font(.caption)
.fontWeight(.black)
.padding(5)
.background(Self.colors[restriction, default: .black])
.clipShape(Circle())
.foregroundColor(.white)
}
}
}
}
}
struct ItemRow_Previews: PreviewProvider {
static var previews: some View {
ItemRow(item: MenuItem.example)
}
}
thanks a lot for the help
Remove the marked part of hack from TabBar view and that glitch will go.
Tested with Xcode 11.4 / iOS 13.4
} .onAppear {
// UITabBar.appearance().isTranslucent = false // << this one !!
UITabBar.appearance().barTintColor = UIColor(named: "IkeaBlu")
}.accentColor(Color(.white))

SwiftUI: Custom Modal Animation

I made a custom modal using SwiftUI. It works fine, but the animation is wonky.
When played in slow motion, you can see that the ModalContent's background disappears immediately after triggering ModalOverlay's tap action. However, ModalContent's Text views stay visible the entire time.
Can anyone tell me how I can prevent ModalContent's background from prematurely disappearing?
Slow-mo video and code below:
import SwiftUI
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation { self.isShowingModal = false } })
ModalContent().transition(.move(edge: .bottom))
}
}.edgesIgnoringSafeArea(.all)
}
}
}
}
struct ModalOverlay: View {
var color = Color.black.opacity(0.4)
var tapAction: (() -> Void)? = nil
var body: some View {
color.onTapGesture { self.tapAction?() }
}
}
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.white)
}
}
}
}
The solution (thanks to #JWK):
It's probably a bug. It seems that, during the transition animation (when the views are disappearing) the zIndex of the two views involved (the ModalContent and the ModalOverlay) is not respected. The ModalContent (that is supposed to be in front of the ModalOverlay) is actually moved under the ModalOverlay at the beginning of the animation. To fix this we can manually set the zIndex to, for example, 1 on the ModalContent view.
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = false } })
ModalContent()
.transition(.move(edge: .bottom))
.zIndex(1)
}
}.edgesIgnoringSafeArea(.all)
}
}
}
}
The investigation that brings to a solution
Transition animations in SwiftUI have still some issues. I think this is a bug. I'm quite sure because:
1) Have you tried to change the background color of your ModalContent from white to green?
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.green)
}
}
}
}
This way it works (see the following GIF):
2) Another way to make the bug occur is to change the background color of your ContentView to, for example, green, leaving the ModalContent to white:
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = false } })
ModalContent().transition(.move(edge: .bottom))
}
}
}
}
.background(Color.green)
.edgesIgnoringSafeArea(.all)
}
}
struct ModalOverlay: View {
var color = Color.black.opacity(0.4)
var tapAction: (() -> Void)? = nil
var body: some View {
color.onTapGesture { self.tapAction?() }
}
}
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.white)
}
}
}
}
Even in this case it works as expected:
3) But if you change your ModalContent background color to green (so you have both the ContentView and the ModalContent green), the problem occurs again (I won't post another GIF but you can easily try it yourself).
4) Yet another example: if you change the appearance of you iPhone to Dark Appearance (the new feature of iOS 13) your ContentView will automatically become black and, since your ModalView is white, the problem won't occur and everything goes fine.

SwiftUI fullscreen image background

I want to have a full screen image in the background. I have implemented this:
struct LoginView: View {
var body: some View {
VStack {
Spacer();
Text("Hallo");
Text("Hallo2");
Text("Hallo2");
Text("Hallo2");
Text("Hallo2");
Text("Hallo2");
Text("Hallo2");
Spacer();
}.background(Image("Background LaunchScreen")
.resizable()
.aspectRatio(UIImage(named: "Background LaunchScreen")!.size, contentMode: .fill)
.clipped())
.edgesIgnoringSafeArea(.all)
}
}
When I remove the spacers, the image is no longer displayed in full screen mode.
Surely that can be solved differently?
And if I turn the iPhone in the simulator to the side, I have left and right white stripes.
How can I change this?
Here's a possible solution using GeometryReader and ZStack:
import SwiftUI
struct LoginView: View {
var body: some View {
GeometryReader { geometry in
ZStack {
Image("LaunchImage")
.resizable()
.aspectRatio(geometry.size, contentMode: .fill)
.edgesIgnoringSafeArea(.all)
VStack {
ForEach (1...10, id: \.self) { _ in
Text("Hallo")
.foregroundColor(Color.white)
}
}
}
}
}
}
#if DEBUG
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
#endif
Results

Resources