I have been playing around with the new toolbar in SwiftUI but it looks like it has some issues with the Catalyst (iPad works fine). Does anyone else experience the same issue?
Here's my code:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
List {
Text("1")
Text("2")
Text("3")
Text("4")
Text("5")
}.toolbar(id: "ytuj") {
ToolbarItem(id: "1", placement: .primaryAction) {
Image(systemName: "heart")
}
ToolbarItem(id: "2", placement: .primaryAction) {
Image(systemName: "heart")
}
ToolbarItem(id: "3", placement: .secondaryAction) {
Image(systemName: "heart.fill")
}
ToolbarItem(id: "4", placement: .secondaryAction, showsByDefault: false) {
Image(systemName: "heart.fill")
}
}.toolbarRole(.editor)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you run this on a Catalyst it crashes when trying to customize the toolbar. Am I doing something wrong?
Thank you
EDIT: here's the error of the crash which is super-weird! This is just a simple "hello-world" app...
*** Assertion failure in -[NSPasteboardItem setPropertyList:forType:], NSPasteboardItem.m:210
Related
I've been experimenting with TabView and tabViewStyle and I've run into a problem with my code I can't figure out.
In the code below, when the app opens up on my device I start on the HomeScreen() (as expected) but if I tap on Profile in the top bar, the tab navigation doesn't happen. The Profile text turns red (indicating that pageIndex has been updated), but for reasons I can't figure out, the TabView isn't updating accordingly.
BUT, if I open the app and tap on Settings in the top bar, the tab navigation happens as expected.
Swiping works as expected, no issues there.
Have I missed something obvious?
Steps to reproduce:
Copy code into xcode
Run on simulator / canvas / device
Tap Profile (don't swipe or tap anything else)
Profile will turn red, but the page won't be animated left to the Profile screen.
If you tap Settings or swipe any direction, tapping Profile will work as expected.
import SwiftUI
struct SwipeNavigation2: View {
#State var pageIndex = 1
var body: some View {
NavigationView {
TabView(selection: self.$pageIndex) {
// The screen to the "left" of the Home screen
ProfileScreen()
.tag(0)
// The screen we want the app to load on
HomeScreen()
.tag(1)
// The screen to the "right" of the Home screen
SettingsScreen()
.tag(2)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation(.spring()) {
pageIndex = 0
}
} label: {
Text("Profile")
.foregroundColor(pageIndex == 0 ? .red : .primary)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation(.spring()) {
pageIndex = 2
}
} label: {
Text("Settings")
.foregroundColor(pageIndex == 2 ? .red : .primary)
}
}
}
}
}
}
private struct ProfileScreen: View {
var body: some View {
Text("Profile screen")
}
}
private struct HomeScreen: View {
var body: some View {
Text("Home screen")
}
}
private struct SettingsScreen: View {
var body: some View {
Text("Settings screen")
}
}
Edit:
I've taken some of the suggestions and amended the code as such:
struct SwipeNavigation2: View {
#State var pageIndex = 0
var body: some View {
NavigationView {
TabView(selection: self.$pageIndex) {
ProfileScreen()
.tag(0)
HomeScreen()
.tag(1)
SettingsScreen()
.tag(2)
}
.onAppear {
pageIndex = 1
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation(.spring()) {
pageIndex = 0
}
} label: {
Text("Profile")
.foregroundColor(pageIndex == 0 ? .red : .primary)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation(.spring()) {
pageIndex = 2
}
} label: {
Text("Settings")
.foregroundColor(pageIndex == 2 ? .red : .primary)
}
}
}
}
}
}
Edit 1:
Here's a recording from my simulator (Xcode14.1), on an iPhone 14. You'll see once the recording starts, I tap on Profile (which turns it red), but the TabView isn't moving me to the correct page.
https://imgur.com/a/B9QiYDM
Edit 2:
It gets weirder. I've tested the following devices in XCode simulator:
iPhone 13 (doesn't work)
iPhone 13 Mini (doesn't work)
iPhone 14 (doesn't work)
iPhone 14 Pro (works)
iPhone 14 Pro Max (works)
Move the onAppear modifier on the tab with the index set at init (in your case the Profile View.
import SwiftUI
struct AdamView: View {
#State var pageIndex: Int = 0
var body: some View {
NavigationView {
TabView(selection: self.$pageIndex) {
Text("Profile")
.onAppear {
pageIndex = 1
}
.tag(0)
Text("Home")
.tag(1)
Text("Settings")
.tag(2)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation(.spring()) {
pageIndex = 0
}
} label: {
Text("Profile")
.foregroundColor(pageIndex == 0 ? .red : .primary)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation(.spring()) {
pageIndex = 2
}
} label: {
Text("Settings")
.foregroundColor(pageIndex == 2 ? .red : .primary)
}
}
}
}
}
}
Another solution is to use the task modifier instead. With the task, you can keep the onAppear on the TabView but I noticed we see the Profile View for a very short time before showing the Home tab.
Does anyone know why when I have two NavigationLinks in the same view, there is this bug that my view pops up correctly, but then immediately disappears. I've tried different solutions, but none of them seem to be working. Any help would be appreciated. I believe the error might be related to isActive, but I'm not sure.
So, basically, I have a button that is supposed to open up the MessageView through the NavigationLink and dismiss the ChatView and ConversationCell views.
import SwiftUI
struct ConversationsView: View {
#State var isShowingNewMessageView: Bool = false
#State var showChat: Bool = false
var body: some View {
ZStack(alignment: .bottomTrailing) {
NavigationLink(isActive: $showChat, destination: {ChatView()}) {
// EmptyView()
}
NavigationLink(destination: EmptyView(), label: {})
.navigationViewStyle(StackNavigationViewStyle())
ScrollView {
VStack {
ForEach(0..<20) { _ in
NavigationLink {
ChatView()
} label: {
ConversationCell()
}
}
}
.padding()
}
Button {
self.isShowingNewMessageView.toggle()
} label: {
Image(systemName: "envelope")
.resizable()
.scaledToFit()
.frame(width: 32, height: 32)
.padding()
}
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Circle())
.padding()
.sheet(isPresented: $isShowingNewMessageView, content: {
NewMessageView(startChat: $showChat, show: $isShowingNewMessageView)
})
}
}
}
struct ConversationsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ConversationsView()
}
}
}
I'm having a problem where when I close the app to send it to the background and then reopen it, it doesn't go to the last screen. Instead it seems to reset just like if the app were being opened from scratch.
I've included an example below. Run it on an iPad in landscape mode, select "Favorites". The details controller will show a red screen. Close the app to send it to the background, open any other app, then go back to the test app. You'll see that it reset itself to the green view. It should stay on the red view.
I've taken all of my code straight from the Fruta example project which doesn't have this behavior so I have no idea what's going on.
EDIT
I've made SideBar a standalone list like Asperi suggested, and I'm also now using SceneStorage as Apple recommends. Using SceneStorage solves the sidebar issue at first, but the core problem is still there when I'm multiple levels deep in a navigation stack.
In this updated example if you tap on Numbers in the sidebar, then select a row, leave the app, and come back after doing something else, the sidebar selection resets.
I have discovered that this is only a problem if your app supports multiple windows. If you uncheck that box none of this seems to be necessary.
The example below has the most recent code edits.
EDIT
I reached out to Apple Code Level Support and they re-debited my account telling me that my issue might be a bug and advised me to file a radar. I’m not entirely sure that it’s a framework bug though.
struct ContentView: View {
#if os(iOS)
#Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
var body: some View {
#if os(iOS)
if horizontalSizeClass == .compact {
AppTabNavigation()
} else {
AppSidebarNavigation()
}
#else
AppSidebarNavigation()
#endif
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// MARK: - AppTabNavigation
struct AppTabNavigation: View {
#State private var selection: Tab = .menu
enum Tab {
case menu
case favorites
case rewards
case recipes
}
var body: some View {
TabView(selection: $selection) {
NavigationView {
Color.purple
}
.tabItem {
Label("Menu", systemImage: "list.bullet")
.accessibility(label: Text("Menu"))
}
.tag(Tab.menu)
NavigationView {
Color.orange
}
.tabItem {
Label("Favorites", systemImage: "heart.fill")
.accessibility(label: Text("Favorites"))
}
.tag(Tab.favorites)
NavigationView {
Color.red
}
.tabItem {
Label("Numbers", systemImage: "book.closed.fill")
.accessibility(label: Text("Numbers"))
}
.tag(Tab.recipes)
}
}
}
enum NavigationItem: String {
case menu
case favorites
case recipes
}
struct SideBar: View{
#Binding var selection: String?
var body: some View {
List(selection: $selection) {
NavigationLink(destination: Color.green, tag: NavigationItem.menu.rawValue, selection: $selection) {
Label("Menu", systemImage: "list.bullet")
}
NavigationLink(destination: Color.red, tag: NavigationItem.favorites.rawValue, selection: $selection) {
Label("Favorites", systemImage: "heart")
}
NavigationLink(destination: ListView(), tag: NavigationItem.recipes.rawValue, selection: $selection) {
Label("Numbers", systemImage: "book.closed")
}
}
.listStyle(SidebarListStyle())
}
}
struct AppSidebarNavigation: View {
#SceneStorage("ContentView.selection") private var selection: String?
var body: some View {
NavigationView {
SideBar(selection: $selection)
Text("Select a category")
.foregroundColor(.secondary)
}
}
}
struct ListView: View{
#SceneStorage("ListView.selection") private var selection: String?
var body: some View{
List(0..<20){ num in
NavigationLink(destination: ListDetails(num: num), tag: num.description, selection: $selection) {
Text(num.description)
}
}
}
}
struct ListDetails: View{
let num: Int
var body: some View{
Text(num.description)
.font(.title)
}
}
Computable property sidebar reevaluated causing List rebuilding, use instead standalone view, like
enum NavigationItem {
case menu
case favorites
case recipes
}
struct AppSidebarNavigation: View {
#State private var selection: NavigationItem? = .menu
#State private var presentingRewards = false
var body: some View {
NavigationView {
SideBarView(selection: $selection)
Text("Select a category")
.foregroundColor(.secondary)
}
}
}
struct SideBarView: View {
#Binding var selection: NavigationItem?
var body: some View {
List(selection: $selection) {
NavigationLink(destination: Color.green, tag: NavigationItem.menu, selection: $selection) {
Label("Menu", systemImage: "list.bullet")
}
.tag(NavigationItem.menu)
NavigationLink(destination: Color.red, tag: NavigationItem.favorites, selection: $selection) {
Label("Favorites", systemImage: "heart")
}
.tag(NavigationItem.favorites)
NavigationLink(destination: Color.blue, tag: NavigationItem.recipes, selection: $selection) {
Label("Recipes", systemImage: "book.closed")
}
.tag(NavigationItem.recipes)
}
.listStyle(SidebarListStyle())
}
}
after I dismiss a sheet my buttons on the screen above do not work anymore. Only after pressing on a non-interacting surface the buttons work again. I use swift version 5 and the error occurs in the simulator and on the device.
#Edit
Code Snippets
AddView this will be displayed in a sheet
struct AddView: View {
#Environment(\.managedObjectContext) var moc
#Environment(\.presentationMode) var presentationMode
// some state
var body: some View {
NavigationView {
Form {
Section(header: Text("Name")) {
TextField("Task-Name (e.g. Eat the 🍰)", text: $title)
}
Section(header: Text("Settings")) {
DatePicker("Date", selection: $timestamp, displayedComponents: .date)
Toggle(isOn: $highPrio) {
Text("High Priority")
}
}
}
.navigationBarItems(trailing: Button("Add"){
// logic
do {
try self.moc.save()
} catch {
print(error.localizedDescription)
}
self.presentationMode.wrappedValue.dismiss()
}.alert(isPresented: $showAlert) {
Alert(title: Text("Name field is empty"), message: Text("Please enter a name"), dismissButton: .default(Text("Got it!")))
})
.navigationBarTitle("New Task")
}
}
}
struct AddView_Previews: PreviewProvider {
static var previews: some View {
AddView()
}
}
ContentView includes a FetchRequest with some functions and nothing more.
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Task.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Task.timestamp, ascending: true),
NSSortDescriptor(keyPath: \Task.status, ascending: false),
NSSortDescriptor(keyPath: \Task.highPriority, ascending: false),
]) var tasks: FetchedResults<Task>
#State private var showingAddSheet = false
#State private var showAlert = false
#State private var editMode = false
var body: some View {
NavigationView {
List {
ForEach(tasks.filter{return self.filterTasks(task: $0)}, id: \.self) { task in
HStack {
TaskRowView(
title: task.wrappedTitle,
status: task.wrappedStatus,
timestamp: task.wrappedTimestamp,
highPriority: task.highPriority,
showDetail: self.editMode
).onTapGesture {
self.toggleStatus(item: task)
}
}
}
.onDelete(perform: removeTask)
}
.navigationBarTitle(self.editMode ? "All Tasks" : "Today")
.navigationBarItems(leading: Button(self.editMode ? "Done" : "Edit") {self.editMode.toggle()}, trailing: Button("Add") {self.showingAddSheet.toggle()})
.sheet(isPresented: $showingAddSheet) {
AddView().environment(\.managedObjectContext, self.moc)
}
}.onAppear(perform: {
self.cleanupTasks()
}).alert(isPresented: $showAlert) {
Alert(title: Text("Unfished Task found"),
message: Text("Do you want to take over the old tasks or delete them?"),
primaryButton: .destructive(Text("Delete all")) {
self.removeOldTasks()
},
secondaryButton: .default(Text("Take over")) {
self.takeOldTasksOver()
}
)
}
}
// functions...
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return ContentView().environment(\.managedObjectContext, context)
}
}
#endif
Solution
This is a Bug that is related to the .large navigationBarItem. You can set that to .inline to go around it for now:
NavigationView {
,,,
.navigationBarTitle(Text(""), displayMode: .inline)
}
Related Thread: SwiftUI - Navigation bar button not clickable after sheet has been presented
The problem happens when there is a navigationView inside your "AddView" struct. From what I have tested, If you remove the navigationView and just use a button (for dismissal) somewhere else inside the AddView it works perfectly. as below:
var body: some View {
VStack{
HStack {
Spacer()
Button(action: {
// logic ..
self.presentationMode.wrappedValue.dismiss()
}){
Text("Add")
}.alert(isPresented: $showAlert) {
Alert(title: Text("Name field is empty"), message: Text("Please enter a name"), dismissButton: .default(Text("Got it!")))
}
.padding(24)
}
Form {
Section(header: Text("Name")) {
TextField("Task-Name (e.g. Eat the 🍰)", text: $title)
}
Section(header: Text("Settings")) {
DatePicker("Date", selection: $timestamp, displayedComponents: .date)
Toggle(isOn: $highPrio) {
Text("High Priority")
}
}
}
}
}
I have this problem in the simulator, but on a real device it works well. Consider updating of xcode, mac OS or iOS.
It's working on device with latest Xcode.
Glad to post my first question here!
I've been playing around with SwiftUI for a few weeks now and during a bigger project, I found the following bug.
If you have a TabView and a list inside it, if you try to change the tab while the scroll animation takes place, the app will crash with FATAL ERROR: "Thread 1: signal SIGABRT".
Console:
BugTest[11830:362796] precondition failure: attribute failed to set an initial value: 98
Have you ever encountered this? Is there any way I can solve this issue without changing my list into a ForEach?
Thank you in advance!
Code:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
list()
.tabItem {
Image(systemName: "doc")
.font(.system(size: 25))
}
Text("Testing the bug")
.tabItem {
Image(systemName: "list.dash")
.font(.system(size: 25))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct list: View {
var body: some View {
List(0..<50){_ in
Text("test")
}
}
}
According to this post, the error occurred because the items in the list were not conforming to the Identifiable protocol.
struct ContentView: View {
var body: some View {
TabView {
list()
.tabItem {
Image(systemName: "doc")
.font(.system(size: 25))
}
Text("Testing the bug")
.tabItem {
Image(systemName: "list.dash")
.font(.system(size: 25))
}
}
}
}
struct list: View {
var elements: [CustomInt] = []
init() {
for i in 0...1000{
elements.append(CustomInt(text:String(i)))
}
}
var body: some View {
List(elements){element in
Text(element.text)
}
}
}
struct CustomInt: Identifiable{
var id = UUID()
var text:String
}
This should work
struct ContentView: View {
var body: some View {
TabView {
VStack{
list()
.tabItem {
Image(systemName: "doc")
.font(.system(size: 25))
}
Text("Testing the bug")
.tabItem {
Image(systemName: "list.dash")
.font(.system(size: 25))
}
}
}
}}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}}
struct list: View {
var body: some View {
List(0..<50){_ in
Text("test")
}
}}