I have this code:
VStack {
if viewRouter.currentPage == "onboardingView" {
OnboardingView()
} else if viewRouter.currentPage == "homeView" {
TabView {
HomeView(mealsController: self.mealsController)
.tabItem {
Image(systemName: "house")
Text("Menu")
}
SettingsView(mealsController: self.mealsController)
.tabItem {
Image(systemName: "slider.horizontal.3")
Text("Settings")
}
}
.accentColor(Color(getThemeConstants.accentThemeColor))
}
}
on the bottom of that I have:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
...
}
.onAppear() {
...
}
inside .onReceive I have a bunch of code that I need to reuse on .onAppear, how can I reuse this kind of code?
Just extract common code in some private function and call it from both modifiers, like below
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
self.handlerFunction()
}
.onAppear() {
self.handlerFunction()
}
...
private func handlerFunction() {
// move common code here
}
Related
I have a TabView with three views (triangle, square and circle) nested inside a navigation view and link. TabView works fine. I'd like to have toolbar buttons for only a specific tabview; say circle. The toolbar modifier adds the buttons on all the tabviews.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink() {
TabView {
Text("triangle")
.tabItem {
Label("triangle", systemImage: "triangle")
}
Text("square")
.tabItem {
Label("square", systemImage: "square")
}
Text("circle")
.tabItem {
Label("circle", systemImage: "circle")
}
}
.navigationTitle("Tabs")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button("About") {
print("About tapped!")
}
Button("Help") {
print("Help tapped!")
}
}
}
} label: {
Text("Hello!")
}
.navigationTitle("Title")
}
}
}
How can I set this up to only show toolbar buttons on one tabview only?
I suppose a secondary option (way less preferred) may be to disable the buttons on tabviews where they are not needed (if possible).
It can be done with a selection state for TabView and making visibility of specific toolbar buttons depending on that state.
Here is a demo. Tested with Xcode 13.4 / iOS 15.5
struct ContentView: View {
#State private var selection = 0 // << here !!
var body: some View {
NavigationView {
NavigationLink() {
TabView(selection: $selection) { // << here !!
Text("triangle")
.tabItem {
Label("triangle", systemImage: "triangle")
}.tag(0)
Text("square")
.tabItem {
Label("square", systemImage: "square")
}.tag(1)
Text("circle")
.tabItem {
Label("circle", systemImage: "circle")
}.tag(2)
}
.navigationTitle("Tabs")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
if selection == 2 { // << here !!
Button("About") {
print("About tapped!")
}
Button("Help") {
print("Help tapped!")
}
}
}
}
} label: {
Text("Hello!")
}
.navigationTitle("Title")
}
}
}
Test code in GitHub
Goal: to have the tapped person in the ScrollView/HStack align it's centre with the other views using the explicit custom alignment, CentreScreenAlignment
Problem: currently nothing is happening when I tap on a given person.
Code:
extension HorizontalAlignment {
private enum CentreScreenAlignment: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[HorizontalAlignment.center]
}
}
static let centreScreenAlignment = HorizontalAlignment(CentreScreenAlignment.self)
}
struct ContentView: View {
#State private var centrePt: Int = 0
var body: some View {
GeometryReader { g in
VStack(alignment: .centreScreenAlignment) {
ZStack {
HStack {
Button("Cancel") {
// action
}
.padding(.leading)
Spacer()
Button("Done") {
// action
}
.padding(.trailing)
}
Button {
// action
} label: {
Image(systemName: "person.fill.badge.plus")
.coordinateSpace(name: "Add button")
.foregroundColor(.blue)
.alignmentGuide(.centreScreenAlignment) { d in
d[.centreScreenAlignment]
}
}
}
EmptyView()
.alignmentGuide(.centreScreenAlignment) { d in
d[.centreScreenAlignment]
}
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(1..<10) { index in
Group {
if index == self.centrePt {
Button("Patient #\(index)") {
//
}.transition(AnyTransition.identity)
.alignmentGuide(.centreScreenAlignment) { d in
d[.centreScreenAlignment]
}
} else {
Button("Person #\(index)") {
withAnimation {
centrePt = index
print(centrePt)
}
}
.transition(AnyTransition.identity)
}
}
}
}
}.padding()
Text("Hello")
}
}
}
}
Thanks very much.
I want to use a ForEach loop to simplify the following code:
.toolbar {
ToolbarItem() {
Button {
}
label: {
Image(systemName: "magnifyingglass")
}
}
ToolbarItem() {
Button {
}
label: {
Image(systemName: "plus")
}
}
}
But it's not working. My approach only creates the "magnifyingglass" button.
My approach:
let toolbar = ["magnifyingglass", "plus"]
.toolbar {
ToolbarItem() {
ForEach(toolbar.indices) { index in
Button {
}
label: {
Image(systemName: toolbar[index])
}
}
}
}
you could try this:
struct ContentView: View {
let toolbar = ["magnifyingglass", "plus"]
var body: some View {
NavigationView {
Text("testing")
.toolbar {
ToolbarItem() {
HStack { // <--- here
ForEach(toolbar.indices) { index in
Button { }
label: { Image(systemName: toolbar[index]) }
}
}
}
}
}
}
}
import SwiftUI
struct ContentView: View {
let toolbar = ["magnifyingglass", "plus"]
var body: some View {
NavigationView {
Text("Toolbar")
.navigationTitle("")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
ForEach(toolbar, id: \.self) { index in
Button {
} label: {
Image(systemName: ("\(index)"))
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I suggest (not tested) to put the for loop outside of ToolbarItem() :
let toolbar = ["magnifyingglass", "plus"]
.toolbar {
ForEach(toolbar.indices) { index in
ToolbarItem() {
Button {
}
label: {
Image(systemName: toolbar[index])
}
}
}
}
With .onDelete() attached to the ForEach, nothing happens - cannot swipe to delete nor does tapping Edit show anything. Not sure what I've done wrong. It compiles fine in simulator and on my iPhone (iOS 14.5) Here's my actual code.
NavigationView {
List {
ForEach(medicines) { medicine in
HStack {
VStack(alignment: .leading) {
Text("\(medicine.name!) \(medicine.strength) \(medicine.unit!)" as String)
.font(.headline)
Text("\(medicine.quantity) x \(medicine.form!) \(medicine.direction!)" as String)
.font(.subheadline)
}
}
.frame(height: 50)
}
.onDelete(perform: deleteMedicines)
.listStyle(PlainListStyle())
.navigationBarTitle("Medicines")
.navigationBarItems(leading: EditButton(), trailing: Button(action: {
self.showingAddScreen.toggle()
}) {
Image(systemName: "plus")
})
.sheet(isPresented: $showingAddScreen) {
AddMedicineView().environment(\.managedObjectContext, self.viewContext)
}
}
}
Then here's the deleteMedicines() function:
private func deleteMedicines(offsets: IndexSet) {
withAnimation {
offsets.map { medicines[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
I've seen a few different ways of doing this and I've tried a few. I had it working when I was first playing around with List. The deleteMedicines code is borrowed from Xcode template for Core Data.
Provided code is not testable, but try to keep only onDelete modifier on ForEach, everything else move below onto List (placement of modifiers are important and can be a reason of your issue), like
NavigationView {
List {
ForEach(medicines) { medicine in
// content here
}
.onDelete(perform: deleteMedicines) // << here !!
}
.listStyle(PlainListStyle())
.navigationBarTitle("Medicines")
.navigationBarItems(leading: EditButton(), trailing: Button(action: {
self.showingAddScreen.toggle()
}) {
Image(systemName: "plus")
})
.sheet(isPresented: $showingAddScreen) {
AddMedicineView().environment(\.managedObjectContext, self.viewContext)
}
I can't find the issue for this error. I don't think the compiler is giving a meaningful error message.
Why self.book.map {} does not work here?
var body: some View {
List {
ForEach(cards) { card in
if (!self.showMarkedOnly || card.marked) {
ZStack() {
CardView(card: card).frame(maxWidth: 400)
.contextMenu() {
if self.editMode {
Button(action: {}) { HStack { Image(systemName: "square.and.pencil"); Text("Edit") } }
Button(action: {}) { HStack { Image(systemName: "trash"); Text("Delete") } }
// self.book.map { Button(action: { self.onRemoveCard(card, fromBook: $0) }) { HStack { Image(systemName: "folder.badge.minus"); Text("Remove from current Book") } } } // ERROR happens here
if self.book != nil {
Button(action: { self.onRemoveCard(card, fromBook: self.book!) }) { HStack { Image(systemName: "folder.badge.minus"); Text("Remove from current Book") } }
} // <------- this works!!! but I prefer to not use "self.book!"
} else {} }
}
}
}
}
}
This is a case of nesting closures interfering with $0. Try this:
self.book.map { book in Button(action: { self.onRemoveCard(card, fromBook: book) }) { ... }