SwiftUI: ContextMenu flipping bug? - swiftui

I am implementing a chat in SwiftUI. I am using a double rotated list to show the most recent chat messages on the bottom. Each message has a context menu attached. But there is bug: when tapping on the context menu, the message does an erroneous rotation animation. It looks bad. Using XCode 12.2.
Any ideas how to get this working? I did file a bug report with Apple, but that typically takes months (years?).
Here is basic example:
import SwiftUI
struct FlippedContextMenuBug: View {
var body: some View {
ScrollView {
ForEach(0..<100) { i in
Text("Message \(i)")
.padding()
.contextMenu(menuItems: {
Text("Menu Item 1")
})
.rotationEffect(.pi)
}
}
.rotationEffect(.pi)
}
}
struct FlippedContextMenuBug_Previews: PreviewProvider {
static var previews: some View {
FlippedContextMenuBug()
}
}
Thank you!

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

SwiftUI - NavigationLink and NavigationStack is not working with a button for iOS 16

I am making an app where I need to navigate to the home page when the user clicks on the Login button and when the Login button is clicked, the navigation link code is not working and shows a warning as Result of 'NavigationLink<Label, Destination>' initializer is unused. FYI, please refer to the attached screenshot and the below code:
import SwiftUI
struct LoginView: View {
var nextButton: some View {
HStack {
Button("Next") {
NavigationLink {
HomeView(user: user)
} label: {
Text("Test")
}
}
.buttonStyle(PlainButtonStyle())
.font(.system(size: 24))
}
}
var body: some View {
NavigationStack {
nextButton
}
}
}
There are two problems here:
A NavigationLink is an alternative to a Button and can be used on its own for navigation. You can use a button to navigate programatically, but for this simple case a link works. Like Buttons, you can change the appearance of NavigationLinks if needed.
NavigationStack is a more powerful replacement for NavigationView. You should not mix them both. As the HomeView is your root view, the single NavigationStack for your app should be there (not in the LoginView).
struct HomeView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink {
LoginView()
} label: {
Text("Login")
}
}
}
}
}
The warning is because you are creating a NavigationLink in the button action code block, and returned NavigationLink is not assigned or used.

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.

SwiftUI navigationBarTitle not resetting to .large returning from Toolbar set to .inline

There are a few posts regarding SwiftUI .inline not resetting to .largeTitle when navigation returns to the parent:
For example:
Navigation bar title stays inline in iOS 15
and
Navigationbar title is inline on pushed view, but was set to large
While earlier posts seem to suggest this has been corrected, I'm running into the same problem, even in iOS 16, but I'm not using a < Back button, instead I'm using "Cancel" (and not show, "Save") on my DestinationView. My goal is to mimic Apple's practice of showing a modal view when adding data, but a show-style push on the navigation stack when viewing and editing existing data (e.g. Contacts app, Reminders app, Calendar app). The brief code below illustrates the problem without adding extra code to handle data updating (e.g. #EnviornmentObject).
When I run this in the Live Preview in Xcode 14.0.1, scheme set to iPhone 13 Pro, no problems. Click a NavLink, return from destination, and ContentView shows .large navigationBarTitle. BUT when I run in the simulator or on a 13 Pro device, returning to Home from a NavigationLink remains .inline unless I pull down on the list. If I switch to iPhone 14 Pro, the live preview looks fine, but the simulator shows a short of abrupt switch from inline back to large, not a smooth animation. Am I doing something wrong in the setup here or is there a bug in the implementation, noting that the behavior oddly holds to .inline on return home to ContentView, if I use this in either a simulator or device for iPhone 13 Pro. Thanks for guidance & insight!
struct ContentView: View {
#State private var sheetIsPresented = false
var items = ["Item1", "Item2", "Item3"]
var body: some View {
NavigationStack {
List {
ForEach(items, id: \.self) { item in
NavigationLink(item, destination: DestinationView(item: item))
.padding()
}
}
.navigationBarTitle("Home", displayMode: .large)
.listStyle(.plain)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
sheetIsPresented.toggle()
} label: {
Image(systemName: "plus")
}
}
}
}
.sheet(isPresented: $sheetIsPresented) {
NavigationStack {
DestinationView(item: "New!")
}
}
}
}
struct DestinationView: View {
var item: String
#Environment(.dismiss) private var dismiss
var body: some View {
List {
Text(item)
}
.toolbar {
ToolbarItem (placement: .navigationBarLeading) {
Button("Cancel") {
dismiss()
}
}
}
.listStyle(.plain)
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden()
}
}

How to hide/remove WatchOS 9's .sheet cancel button?

Previous to watchOS 9 you could present a sheet without any out of the box way to cancel or dismiss. However starting in watchOS 9 presenting a sheet also presents a cancel button in the top left of the navigation bar. How can I remove this and handle dismissing myself?
import SwiftUI
struct ContentView: View {
#State var isShowingSheet = false
var body: some View {
VStack {
Button("show sheet") {
isShowingSheet.toggle()
}
}
.sheet(isPresented: $isShowingSheet) {
Text("Sheet 1")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Same issue here, tried an empty view in a ToolBarItem but did not work.
I ended up using this:
NavigationView {
// Your stuff here
}
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
What you do is that you set an empty title, and you tell it to go at the same place as of the cancel button.
PS:it looks like #Kurt Lane was a bit faster in the comment. Credits to him.