How to undo by code a SwiftUI swipe action on a List cell? - list

My app has a SwiftUI List with cells that can be swiped (leading, trailing).
Problem:
Say, a cell is swiped so that the buttons are visible. Then the app is de-activated in this state, e.g. by a switch to another app, by locking the screen, etc.
When the app later is re-activated, the earlier swiped cell is still swiped, although the user might no longer be aware of the reason.
It would thus be better to undo the swipe by code, when the app is deactivated.
Question:
Is this possible?

It is a but of a brute-force approach but redrawing with this approach works.
import SwiftUI
#available(iOS 15.0, *)
struct ResetSwipeView: View {
#Environment(\.scenePhase) var scenePhase
#State var id: UUID = .init()
var body: some View {
List(1...10){n in
Text(n, format: .number)
.swipeActions {
Button {
print("Button :: \(n)")
} label: {
Text("print")
}
}
}.id(id)
.onChange(of: scenePhase, perform: { newValue in
if newValue == .inactive{
id = .init()
}
})
}
}
#available(iOS 15.0, *)
struct ResetSwipeView_Previews: PreviewProvider {
static var previews: some View {
ResetSwipeView()
}
}

Related

Why is my preview crashing when I try to press a button to go to another view in SwiftUI?

Using iOS 16.0 and Xcode 14.2
I am super new to SwiftUI and it honestly still hasn't really clicked yet, so this code probably is very inefficient. But basically I just want a button that when you press it, you go to another view. But when I try it like this, the preview crashes. Is there a better way to do this? And also what is causing the preview to crash? I've tried a bunch of different things and it either causes the preview to crash or the button just doesn't do anything.
Homescreen (had other buttons that were working that I redacted for clarity)
import SwiftUI
struct WordAndArrows: View {
#State private var showLibrary = false
var body: some View {
LibraryButton (action: {
self.navigateToLibraryScreen()
})
}
VStack {
if showLibrary {
LibraryView()
}
}
}
func navigateToLibraryScreen() {
self.showLibrary = true
}
}
struct WordAndArrows_Previews: PreviewProvider {
static var previews: some View {
ZStack {
GradientBackground()
WordAndArrows()
}
}
}
Library Button View
import SwiftUI
struct LibraryButton: View {
var action: () -> Void
var body: some View {
//Button("Check out my library") {
Button(action: action) {
Text("Library")
}
.padding()
.background(Color("Lime"))
.clipShape(Capsule())
.buttonStyle(SquishButtonStyle(fadeOnPress: false))
.font(Font.custom("Quicksand-Bold", size: 15))
.foregroundColor(Color("Indigo"))
.shadow(color: .gray, radius: 2.5, x: 0, y: 5)
}
}
I tried:
Making a navigatetolibraryscreen function that would be triggered when the buttn was hit
Using a navigationLink
3 Wrapping the navigation link in a Vstack
And it either caused the button to not do anything or the preview to crash

NavigationStack and TabView in Swiftui iOS 16: bug or improper usage?

[Xcode 14.1, iOS 16.1]
I have a NavigationStack with a navigationTitle and a TabView with 2 Views. Each View has a ScrollView (see image below):
NavigationStack and TabView problem image
When I tap on Tab1 (#1 in red on the image above), then swipe up, the behavior is as expected (#2), i.e. the big navigationTitle move to the center, and my view passes below and becomes blurry. Perfect.
However, when I tap ton Tab2 (#3) and then swipe up (#4), the big title stays big, and the view doesn't become blurry.
Then I tap on Tab1 again (#5) and it works as expected.
Please help!
Here is my code:
ContentView:
import SwiftUI
struct ContentView: View {
#State private var selection: Tab = .tab1
enum Tab {
case tab1
case tab2
}
#State private var mainTitle = "Tab1"
var body: some View {
NavigationStack {
TabView(selection: $selection) {
Tab1(mainTitle: $mainTitle)
.tabItem {
Label("Tab1", systemImage: "wrench.adjustable.fill")
}
.tag(Tab.tab1)
Tab2(mainTitle: $mainTitle)
.tabItem {
Label("Tab2", systemImage: "wrench.adjustable.fill")
}
.tag(Tab.tab2)
} .navigationTitle(mainTitle)
}
}
}
Tab1:
import SwiftUI
struct Tab1: View {
#Binding var mainTitle : String
var body: some View {
ScrollView {
Text("Text tab 1")
.padding(.all,100)
.background(.blue)
} .onAppear {
mainTitle = "Tab1"
}
}
}
Tab2:
import SwiftUI
struct Tab2: View {
#Binding var mainTitle : String
var body: some View {
ScrollView {
Text("Text tab 2")
.padding(.all,100)
.background(.green)
} .onAppear {
mainTitle = "Tab2"
}
}
}
I tried a hack that is supposed to fix the transparency bug for Tab bars, but it doesn't work.
.onAppear {
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithOpaqueBackground()
UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance
}
TabViews are designed to sit at the top of the navigation hierarchy. They're intended to allow users to switch between independent sections of your app at any time.
You would generally put a separate navigation stack within each tab that then handles pushing and popping of views. And then, you can use the navigationTitle modifier to manage the screen's title.
So your structure (which might be split over multiple custom views) should look something like:
TabView {
NavigationStack {
ScrollView {
}
.navigationTitle("Tab 1")
}
.tabItem { Label("Tab1", ...) }
NavigationStack {
ScrollView {
}
.navigationTitle("Tab 2")
}
.tabItem { Label("Tab2", ...) }
}
This structure is by design, to align with Apple's Human Interface Guidelines. It's worth reading the HIG to get a handle on where Apple are coming from, and how working on the same principles can really help your app feel like it belongs on your users' device.

Sheet is Only Presented Once in SwiftUI

I have an app which presents a sheet. It works for the first time but when I click on it again it does not work. I am making isPresented false when you dismiss a sheet but when I tap on the Filter button again, it does not show the sheet.
ContentView
struct ContentView: View {
#State private var isPresented: Bool = false
var body: some View {
NavigationView {
List(1...20, id: \.self) { index in
Text("\(index)")
}.listStyle(.plain)
.navigationTitle("Hotels")
.toolbar {
Button("Filters") {
isPresented = true
}
}
.sheet(isPresented: $isPresented) {
isPresented = false
} content: {
FilterView()
}
}
}
}
FilterView:
import SwiftUI
struct FilterView: View {
#Environment(\.presentationMode) private var presentationMode
var body: some View {
ZStack {
Text("FilterView")
Button {
// action
presentationMode.wrappedValue.dismiss()
} label: {
Text("Dismiss")
}
}
}
}
struct FilterView_Previews: PreviewProvider {
static var previews: some View {
FilterView()
}
}
A couple of things to note from my experience.
Firstly, when using the isPresented binding to show a sheet, you don't need to reset the bound value in a custom onDismiss handler to reset it to false - that's handled for you internally by SwiftUI as part of the dismiss action.
So your modifier can be simplified a little:
.sheet(isPresented: $isPresented) {
FilterView()
}
Secondly, when running an app in the Simulator I've noticed that when you come back to the main view after dismissing a sheet you have to interact with the app somehow before clicking on the toolbar button, or the action won't trigger.
In cases like this, just scrolling the list up or down a little would be enough, and then the toolbar button works as you'd expect.
I've not encountered the same thing while running apps on a physical device – whether that's because the bug isn't present, or just that it's a lot easier to interact with the app in some microscopic form of gesture, I couldn't say.

View resizing when hiding tab bar with Introspect in SwiftUI

I'm using Introspect to hide the tab bar on child navigation link pages. However, I've noticed some odd behavior when the app is backgrounded and then brought back to the foreground.
It seems like initially, the hidden tab bar is still taking up some space, but this disappears when cycling the app back to the foreground. I'm not sure if this is SwiftUI behavior or has to do with how I'm using Introspect / UIKit.
It's causing layout issues in my app, so I'd like to make the spacing consistent if possible.
Here's a minimal example that shows the behavior:
import SwiftUI
import Introspect
struct ContentView: View {
var body: some View {
TabView {
VStack {
Spacer()
Text("Hello, world!")
}
}
.border(Color.red)
.introspectTabBarController { tabBarController in
tabBarController.tabBar.isHidden = true
}
}
}
Here is the late answer. Basically add tabbar height to current view frame. And onDissappear restore view frame size
import SwiftUI
import Introspect
#State var uiTabarController: UITabBarController?
#State var tabBarFrame: CGRect?
struct ContentView: View {
var body: some View {
TabView {
VStack {
Spacer()
Text("Hello, world!")
}
}
.border(Color.red)
.introspectTabBarController { (UITabBarController) in
uiTabarController = UITabBarController
self.tabBarFrame = uiTabarController?.view.frame
uiTabarController?.tabBar.isHidden = true
uiTabarController?.view.frame = CGRect(x:0, y:0, width:tabBarFrame!.width, height:tabBarFrame!.height+UITabBarController.tabBar.frame.height);
}
.onDisappear {
if let frame = self.tabBarFrame {
self.uiTabarController?.tabBar.isHidden = false
uiTabarController?.view.frame = frame
}
}
}
}

Navigating to a new screen using a button in swiftUI

I am new to swiftUI and am having difficulty moving from one UIViewController to another UIViewController. Rights now I have a state called navigate and a button, but I am not sure how to move this screen to a new screen. Here is the code.
import SwiftUI
struct ContentView: View {
#State var navigate = false
var body: some View {
Button(action: { self.navigate.toggle() }) {
Text("Get Involved")
.font(.custom("PlayfairDisplay-Regular", size: 18))
.foregroundColor(Color("Dark Text"))
}
}
}
Here is the code for the blank screen.
import SwiftUI
struct GoalIdeasView: View {
var body: some View {
Text("Hello")
}
}
How would I go about navigating to the second screen from the ContentView Controller once the button is pressed?
There is a view called Navigation that act like UINavigationViewController. Then you can use a NavigationLink to navigate between views. So it would be like:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("Get Involved", destination: GoalIdeasView())
.font(.custom("PlayfairDisplay-Regular", size: 18))
}
}
}