Animation in ScrollView is not working ? Using Xcode 11 beta 6 - animation

I was trying to to do transition animation Scrollview but found out that how things work differently in scrollView. But still unable to do animation in it. I am providing code, please have a look. Using Xcode 11 beta 6
import SwiftUI
struct ContentView : View {
#State private var isButtonVisible = false
var body: some View {
NavigationView {
ScrollView{
VStack {
Button(action: {
// withAnimation {
self.isButtonVisible.toggle()
// }
}) {
Text("Press me")
}
if isButtonVisible {
Text("sss")
.frame(height: true ? 50 : 0, alignment: .center)
.background(Color.red)
.animation(.linear(duration: 2))
// .transition(.move(edge: .top))
}
}
}
}
}}

This must be a bug, and I suggest you file a bug report with Apple. I find a workaround (see code below), but it unfortunately uncovers another bug!
In order to make the animation inside the ScrollView to work, you can encapsulate the contents in a custom view. That will fix that problem.
This will uncover a new issue, which is made evident by the borders I added to your code: when the Text view is added, it shifts parts of the contents outside the ScrollView.
You will see that this is not correct. Try starting your app with a default value isButtonVisible = true, and you will see it renders it differently.
struct ContentView : View {
var body: some View {
NavigationView {
ScrollView {
EncapsulatedView().border(Color.green)
}.border(Color.blue)
}
}
}
struct EncapsulatedView: View {
#State private var isButtonVisible = false
var body: some View {
VStack {
Text("Filler")
Button(action: {
withAnimation(.easeInOut(duration: 2.0)) {
self.isButtonVisible.toggle()
}
}) {
Text("Press me")
}
if isButtonVisible {
Text("sss")
.frame(height: true ? 50 : 0, alignment: .center)
.transition(.opacity)
.background(Color.red)
}
Spacer()
}.border(Color.red)
}
}

Related

Progress View doesn't show on second + load when trying to do pagination SwiftUI

Progress View doesn't show on second + load when trying to do pagination. When I scroll to the bottom the progress view will appear once. But all the other times it doesn't. This only seems to occur when im using some sort of animation.
If I just have a static text like "Loading..." it works as expected. I added the section group where it checks a condition to verify if it should be presented or not. Not sure if I'm supposed to use something like "stop animating" like the loading indicator has in UIKit
struct ContentView: View {
#State var movies: [Movie] = []
#State var currentPage = 1
#State private var isLoading = false
var body: some View {
NavigationView {
List {
Section {
} header: {
Text("Top Movies")
}
ForEach(movies) { movie in
HStack(spacing: 8) {
AsyncImage(url: movie.posterURL, scale: 5) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
.cornerRadius(10)
} placeholder: {
ProgressView()
.frame(width: 100)
}
VStack(alignment: .leading, spacing: 10) {
Text(movie.title)
.font(.headline)
Text(movie.overview)
.lineLimit(5)
.font(.subheadline)
Spacer()
}
.padding(.top, 10)
}
.onAppear {
Task {
//Implementing infinite scroll
if movie == movies.last {
isLoading = true
currentPage += 1
movies += await loadMovies(page: currentPage)
isLoading = false
}
}
}
}
Section {
} footer: {
if isLoading {
HStack {
Spacer()
ProgressView()
.tint(.green)
Spacer()
}
} else {
EmptyView()
}
}
}
.navigationTitle("Movies")
.navigationBarTitleDisplayMode(.inline)
.listStyle(.grouped)
.task {
movies = await loadMovies()
}
.refreshable {
movies = await loadMovies()
}
}
}
}
even when I look at the view hierarchy its like the progress view square is there but the icon / loading indicator isn't showing:
If I add the overlay modifier it works but I don't like doing this because when I scroll back up before the content finishes loading the spinner is above the list view:?
.overlay(alignment: .bottom, content: {
if isLoading {
HStack {
Spacer()
ProgressView()
.tint(.green)
Spacer()
}
} else {
EmptyView()
}
})
We also had this problem, we think it is a bug of ProgressView. Our temporary correction is to identify the ProgressView with a unique id in order to render it again.
struct ContentView: View {
#State var id = 0
var body: some View {
ProgressView()
.id(id)
.onAppear {
...
id += 1
}
}
}

Xcode SwiftUI leading navigationBarItem not working but trailing navigationBarItem does work

I'm a new Xcode/SwiftUI programmer, and I've encountered a problem in my ThirdView code (below) whereby the exact same button code works as expected as a trailing navigationBarItem but does not appear at all when I move the button and associated code to the leading bar item spot. The following code illustrates the issue.
This code sample is virtually identical to the code for a previous question I posted (which a very helpful StackOverflow community member helped me resolve); my question here concerns a different problem in the same code.
struct ContentView: View {
#State private var activeLink: Bool = false
var body: some View {
NavigationView {
VStack {
Spacer()
NavigationLink("Show Second Screen",
destination: SecondView(active: $activeLink), isActive: $activeLink)
Spacer()
}.padding()
.navigationBarTitle("First view")
} // End of NavigationView
} // End of body
} // End of ContentView
struct SecondView: View {
#Binding var active: Bool
#State private var thirdViewLink: Bool = false
var body: some View {
VStack {
Spacer()
NavigationLink(destination: ThirdView(thirdViewActive: $thirdViewLink),
isActive: $thirdViewLink) {
EmptyView()
} // End of NavigationLink
Spacer()
}.padding() // End of VStack
.navigationBarTitle("Second View")
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button("Back to 1st View") {
self.active = false
}, trailing: Button("Show Third View") {
self.thirdViewLink = true
}
)
} // End of body
} // End of SecondView
struct ThirdView: View {
#Binding var thirdViewActive: Bool
var body: some View {
VStack(spacing: 15) {
Text("Third View")
Spacer()
}.padding() // End of VStack
.navigationBarBackButtonHidden(true)
//The following approach does NOT work
.navigationBarItems(leading: Button("Back to 2nd View") {
self.thirdViewActive = false
})
// This DOES work - why the difference?
/* .navigationBarItems(trailing: Button("Back to 2nd View") {
self.thirdViewActive = false
}) */
} // End of body
} // End of ThirdView
Why does the ThirdView button for "Back to 2nd View" only appear when I assign it to the trailing navigationBarItems() location? How can I make a button in this situation appear in the leading location? Many thanks for any insight you're able to provide!
your code works well for me on macos 12.beta, xcode 13.beta,
target ios 15 and macCatalyst 12, when I use this:
struct ContentView: View {
#State private var activeLink: Bool = false
var body: some View {
NavigationView {
VStack {
Spacer()
NavigationLink("Show Second Screen",
destination: SecondView(active: $activeLink), isActive: $activeLink)
Spacer()
}.padding()
.navigationBarTitle("First view")
} // End of NavigationView
.navigationViewStyle(.stack) // <--- here
} // End of body
} // End of ContentView
Tested on macOS 12 and iPhone and iPad ios15.

SwiftUI NavigationView on MacOS leaves padding above List

I have a NavigationView that leaves what looks like padding above the List of NavigationLink's and I can't work out how to get rid of it?
Here's the view that calls the NavigationView:
struct SearchView: View {
#EnvironmentObject var networkManager:NetworkManager
var body: some View {
VStack {
SearchBar()//<- This view houses the content above the list but contains no padding after the divider and when the filesToDisplay is false it shrinks to the divider as expected
if networkManager.filesToDisplay {
ResultsView()
}
}
}
}
Here's the NavigationView:
struct ResultsView: View {
#EnvironmentObject var networkManager:NetworkManager
var body: some View {
NavigationView {
List {
ForEach(networkManager.FileList!.items) { file in
NavigationLink(destination: FileDetail(fileDetail: file)) {
FileRow(fileRow: file)
}
}
}
Rectangle().frame(maxWidth: 0, maxHeight: .infinity)
}.frame(minHeight:500, idealHeight: 550, maxHeight: .infinity)
}
}
I thought the problem might be to do with navigationBarTitle but MacOS does not appear to use this? I've tried all sorts of different padding and listRowInsets but nothing is working.
Add explicit spacing
VStack(spacing: 0) { // << here !!
SearchBar()
if networkManager.filesToDisplay {
ResultsView()
}
}

macOS SwiftUI Navigation for a Single View

I'm attempting to create a settings view for my macOS SwiftUI status bar app. My implementation so far has been using a NavigationView, and NavigationLink, but this solution produces a half view as the settings view pushes the parent view to the side. Screenshot and code example below.
Navigation Sidebar
struct ContentView: View {
var body: some View {
VStack{
NavigationView{
NavigationLink(destination: SecondView()){
Text("Go to next view")
}}
}.frame(width: 800, height: 600, alignment: .center)}
}
struct SecondView: View {
var body: some View {
VStack{
Text("This is the second view")
}.frame(width: 800, height: 600, alignment: .center)
}
}
The little information I can find suggests that this is unavoidable using SwiftUI on macOS, because the 'full screen' NavigationView on iOS (StackNavigationViewStyle) is not available on macOS.
Is there a simple or even complex way of implementing a transition to a settings view that takes up the whole frame in SwiftUI for macOS? And if not, is it possible to use AppKit to call a View object written in SwiftUI?
Also a Swift newbie - please be gentle.
Here is a simple demo of possible approach for custom navigation-like solution. Tested with Xcode 11.4 / macOS 10.15.4
Note: background colors are used for better visibility.
struct ContentView: View {
#State private var show = false
var body: some View {
VStack{
if !show {
RootView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if show {
NextView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
struct RootView: View {
#Binding var show: Bool
var body: some View {
VStack{
Button("Next") { self.show = true }
Text("This is the first view")
}
}
}
struct NextView: View {
#Binding var show: Bool
var body: some View {
VStack{
Button("Back") { self.show = false }
Text("This is the second view")
}
}
}
I've expanded upon Asperi's great suggestion and created a generic, reusable StackNavigationView for macOS (or even iOS, if you want). Some highlights:
It supports any number of subviews (in any layout).
It automatically adds a 'Back' button for each subview (just text for now, but you can swap in an icon if using macOS 11+).
Swift v5.2:
struct StackNavigationView<RootContent, SubviewContent>: View where RootContent: View, SubviewContent: View {
#Binding var currentSubviewIndex: Int
#Binding var showingSubview: Bool
let subviewByIndex: (Int) -> SubviewContent
let rootView: () -> RootContent
var body: some View {
VStack {
VStack{
if !showingSubview { // Root view
rootView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if showingSubview { // Correct subview for current index
StackNavigationSubview(isVisible: self.$showingSubview) {
self.subviewByIndex(self.currentSubviewIndex)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
init(currentSubviewIndex: Binding<Int>, showingSubview: Binding<Bool>, #ViewBuilder subviewByIndex: #escaping (Int) -> SubviewContent, #ViewBuilder rootView: #escaping () -> RootContent) {
self._currentSubviewIndex = currentSubviewIndex
self._showingSubview = showingSubview
self.subviewByIndex = subviewByIndex
self.rootView = rootView
}
private struct StackNavigationSubview<Content>: View where Content: View {
#Binding var isVisible: Bool
let contentView: () -> Content
var body: some View {
VStack {
HStack { // Back button
Button(action: {
self.isVisible = false
}) {
Text("< Back")
}.buttonStyle(BorderlessButtonStyle())
Spacer()
}
.padding(.horizontal).padding(.vertical, 4)
contentView() // Main view content
}
}
}
}
More info on #ViewBuilder and generics used can be found here.
Here's a basic example of it in use. The parent view tracks current selection and display status (using #State), allowing anything inside its subviews to trigger state changes.
struct ExampleView: View {
#State private var currentSubviewIndex = 0
#State private var showingSubview = false
var body: some View {
StackNavigationView(
currentSubviewIndex: self.$currentSubviewIndex,
showingSubview: self.$showingSubview,
subviewByIndex: { index in
self.subView(forIndex: index)
}
) {
VStack {
Button(action: { self.showSubview(withIndex: 0) }) {
Text("Show View 1")
}
Button(action: { self.showSubview(withIndex: 1) }) {
Text("Show View 2")
}
Button(action: { self.showSubview(withIndex: 2) }) {
Text("Show View 3")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
}
}
private func subView(forIndex index: Int) -> AnyView {
switch index {
case 0: return AnyView(Text("I'm View One").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.green))
case 1: return AnyView(Text("I'm View Two").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.yellow))
case 2: return AnyView(VStack {
Text("And I'm...")
Text("View Three")
}.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.orange))
default: return AnyView(Text("Inavlid Selection").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red))
}
}
private func showSubview(withIndex index: Int) {
currentSubviewIndex = index
showingSubview = true
}
}
Note: Generics like this require all subviews to be of the same type. If that's not so, you can wrap them in AnyView, like I've done here. The AnyView wrapper isn't required if you're using a consistent type for all subviews (the root view’s type doesn’t need to match).
Heyo, so a problem I had is that I wanted to have multiple navigationView-layers, I'm not sure if that's also your attempt, but if it is: MacOS DOES NOT inherit the NavigationView.
Meaning, you need to provide your DetailView (or SecondView in your case) with it's own NavigationView. So, just embedding like [...], destination: NavigationView { SecondView() }) [...] should do the trick.
But, careful! Doing the same for iOS targets will result in unexpected behaviour. So, if you target both make sure you use #if os(macOS)!
However, when making a settings view, I'd recommend you also look into the Settings Scene provided by Apple.
Seems this didn't get fixed in Xcode 13.
Tested on Xcode 13 Big Sur, not on Monterrey though...
You can get full screen navigation with
.navigationViewStyle(StackNavigationViewStyle())

Has anyone updated SwiftUI Popovers to work in Xcode beta-3?

Updated to Xcode beta-3, Popover was deprecated... having one hell of a time trying to figure out how to make it work again!?!?
It no longer "pops up" it slides up from the bottom.
It's no longer positioned or sized correctly, takes up the whole screen.
Once dismissed, it never wants to appear again.
This was the old code, that worked perfectly...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
#State private var showPositions = false
var body: some View {
HStack {
Spacer()
Button(action: { self.showPositions = true } ) {
Text("Position")
}
.presentation(showPositions ? Popover(content: MultiPicker(items: Exercise.Position.allCases, selected:$filter.positions),
dismissHandler: { self.showPositions = false })
: nil)
}
.padding()
}
}
And this is the new code...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
#State private var showPositions = false
var body: some View {
HStack {
Spacer()
Button(action: { self.showPositions = true } ) {
Text("Position")
}
.popover(isPresented: $showPositions) {
MultiPicker(items: Exercise.Position.allCases, selected:self.$filter.positions)
.onDisappear { self.showPositions = false }
}
}
.padding()
}
}
I ended up using PresentationLink just so I can move forward with everything else...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
var body: some View {
HStack {
Spacer()
PresentationLink(destination: MultiPicker(items: Exercise.Position.allCases, selected:$filter.positions)) {
Text("Position")
}
}
.padding()
}
}
It works, as far as testing is concerned, but it's not a popover.
Thanks for any suggestions!
BTW, this code is being in the iPad simulator.
On OSX the code below works fine
struct ContentView : View {
#State var poSelAbove = false
#State var poSelBelow = false
#State var pick : Int = 1
var body: some View {
let picker = Picker(selection: $pick, label: Text("Pick option"), content:
{
Text("Option 0").tag(0)
Text("Option 1").tag(1)
Text("Option 2").tag(2)
})
let popoverWithButtons =
VStack {
Button("Not Dismiss") {
}
Divider()
Button("Dismiss") {
self.poSelAbove = false
}
}
.padding()
return VStack {
Group {
Button("Show button popover above") {
self.poSelAbove = true
}.popover(isPresented: $poSelAbove, arrowEdge: .bottom) {
popoverWithButtons
}
Divider()
Button("Show picker popover below") {
self.poSelBelow = true
}.popover(isPresented: $poSelBelow, arrowEdge: .top) {
Group {
picker
}
}
}
Divider()
picker
.frame(width: 300, alignment: .center)
Text("Picked option: \(self.pick)")
.font(.subheadline)
}
// comment the line below for iOS
.frame(width: 800, height: 600)
}
On iOS (iPad) the popover will appear in a strange transparent full screen mode. I don't think this is intended. I have added the problem to my existing bug report.

Resources