SwiftUI searchable modifier cancel button dismisses parent view's sheet presentation - ios

I have a swiftui list with a .searchable modifier, contained within a navigation view child view, within a sheet presentation. When the Cancel button is used, not only is the search field dismissed (unfocused, keyboard dismissed etc), but the navigation view dismisses (ie moves back a page) and then the containing sheet view also dismisses.
This list acts as a custom picker view that allows the selection of multiple HKWorkoutActivityTypes. I use it in multiple locations and the bug only sometimes presents itself, despite being implemented in almost identical views. If I swap the .sheet for a .fullScreenCover, then the presentation of the bug swaps between those previously unaffected and those that were.
The parent view (implemented as a form item, in .sheet):
struct MultipleWorkoutActivityTypePickerFormItem: View, Equatable {
static func == (lhs: MultipleWorkoutActivityTypePickerFormItem, rhs: MultipleWorkoutActivityTypePickerFormItem) -> Bool {
return lhs.workoutActivityTypes == rhs.workoutActivityTypes
}
#Binding var workoutActivityTypes: [HKWorkoutActivityType]
var workoutActivityTypeSelectionDescription: String {
var string = ""
if workoutActivityTypes.count == 1 {
string = workoutActivityTypes.first?.commonName ?? ""
} else if workoutActivityTypes.count == 2 {
string = "\(workoutActivityTypes[0].commonName) & \(workoutActivityTypes[1].commonName)"
} else if workoutActivityTypes.count > 2 {
string = "\(workoutActivityTypes.first?.commonName ?? "") & \(workoutActivityTypes.count - 1) others"
} else {
string = "Any Workout"
}
return string
}
var body: some View {
NavigationLink {
MultipleWorkoutActivityTypePickerView(selectedWorkoutActivityTypes: $workoutActivityTypes)
.equatable()
} label: {
HStack {
Text("Workout Types:")
Spacer()
Text(workoutActivityTypeSelectionDescription)
.foregroundColor(.secondary)
}
}
}
}
And the child view:
struct MultipleWorkoutActivityTypePickerView: View, Equatable {
static func == (lhs: MultipleWorkoutActivityTypePickerView, rhs: MultipleWorkoutActivityTypePickerView) -> Bool {
return (lhs.favouriteWorkoutActivityTypes == rhs.favouriteWorkoutActivityTypes && lhs.selectedWorkoutActivityTypes == rhs.selectedWorkoutActivityTypes)
}
#State var searchString: String = ""
#Environment(\.dismissSearch) var dismissSearch
#Environment(\.isSearching) var isSearching
//MARK: - FUNCTIONS
#Binding var selectedWorkoutActivityTypes: [HKWorkoutActivityType]
#State var favouriteWorkoutActivityTypes: [FavouriteWorkoutActivityType] = []
#State var searchResults: [HKWorkoutActivityType] = HKWorkoutActivityType.allCases
let viewContext = PersistenceController.shared.container.viewContext
func loadFavouriteWorkoutActivityTypes() {
let request: NSFetchRequest<FavouriteWorkoutActivityType> = FavouriteWorkoutActivityType.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \FavouriteWorkoutActivityType.index, ascending: true)]
do {
try favouriteWorkoutActivityTypes = viewContext.fetch(request)
} catch {
print(error.localizedDescription)
}
print("FavouriteWorkoutActivityType - Load")
}
func updateSearchResults() {
if searchString.isEmpty {
searchResults = HKWorkoutActivityType.allCases
} else {
searchResults = HKWorkoutActivityType.allCases.filter { $0.commonName.contains(searchString) }
}
}
func createFavouriteWorkoutActivityType(workoutActivityType: HKWorkoutActivityType) {
let newFavouriteWorkoutActivityType = FavouriteWorkoutActivityType(context: viewContext)
newFavouriteWorkoutActivityType.workoutActivityTypeRawValue = Int16(workoutActivityType.rawValue)
newFavouriteWorkoutActivityType.index = Int16(favouriteWorkoutActivityTypes.count)
print(newFavouriteWorkoutActivityType)
do {
try viewContext.save()
} catch {
print(error.localizedDescription)
}
loadFavouriteWorkoutActivityTypes()
}
func updateFavouriteWorkoutActivityTypeIndex(favouriteWorkoutActivityType: FavouriteWorkoutActivityType) {
//reoder
}
func deleteFavouriteWorkoutActivityType(favouriteWorkoutActivityType: FavouriteWorkoutActivityType) {
viewContext.delete(favouriteWorkoutActivityType)
do {
try viewContext.save()
} catch {
print(error.localizedDescription)
}
loadFavouriteWorkoutActivityTypes()
}
func deleteFavouriteWorkoutActivityTypeFor(workoutActivityType: HKWorkoutActivityType) {
for favouriteWorkoutActivityType in favouriteWorkoutActivityTypes {
if favouriteWorkoutActivityType.workoutActivityTypeRawValue == workoutActivityType.rawValue {
viewContext.delete(favouriteWorkoutActivityType)
}
}
do {
try viewContext.save()
} catch {
print(error.localizedDescription)
}
loadFavouriteWorkoutActivityTypes()
}
func deleteItems(offsets: IndexSet) {
offsets.map { favouriteWorkoutActivityTypes[$0] }.forEach(viewContext.delete)
renumberItems()
do {
try viewContext.save()
} catch {
print(error.localizedDescription)
}
loadFavouriteWorkoutActivityTypes()
}
func renumberItems() {
if favouriteWorkoutActivityTypes.count > 1 {
for item in 1...favouriteWorkoutActivityTypes.count {
favouriteWorkoutActivityTypes[item - 1].index = Int16(item)
}
} else if favouriteWorkoutActivityTypes.count == 1 {
favouriteWorkoutActivityTypes[0].index = Int16(1)
}
// print("ChartsViewModel - Renumber")
do {
try viewContext.save()
} catch {
print(error.localizedDescription)
}
loadFavouriteWorkoutActivityTypes()
}
func moveItems(from source: IndexSet, to destination: Int) {
// Make an array of items from fetched results
var revisedItems: [FavouriteWorkoutActivityType] = favouriteWorkoutActivityTypes.map{ $0 }
// change the order of the items in the array
revisedItems.move(fromOffsets: source, toOffset: destination)
// update the userOrder attribute in revisedItems to
// persist the new order. This is done in reverse order
// to minimize changes to the indices.
for reverseIndex in stride( from: revisedItems.count - 1,
through: 0,
by: -1 )
{
revisedItems[reverseIndex].index =
Int16(reverseIndex)
}
// print("ChartsViewModel - Move")
do {
try viewContext.save()
} catch {
print(error.localizedDescription)
}
loadFavouriteWorkoutActivityTypes()
}
var body: some View {
List {
if searchString == "" {
Section {
ForEach(favouriteWorkoutActivityTypes) { favouriteWorkoutActivityType in
if let workoutActivityType = HKWorkoutActivityType(rawValue: UInt(favouriteWorkoutActivityType.workoutActivityTypeRawValue)) {
HStack {
HStack {
Label {
Text(workoutActivityType.commonName)
} icon: {
Image(systemName: (selectedWorkoutActivityTypes.contains(workoutActivityType) ? "checkmark.circle" : "circle"))
}
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
if !selectedWorkoutActivityTypes.contains(where: {$0 == workoutActivityType}) {
selectedWorkoutActivityTypes.append(workoutActivityType)
print("does not contain, adding:", workoutActivityType.commonName)
} else {
selectedWorkoutActivityTypes = selectedWorkoutActivityTypes.filter({$0 != workoutActivityType})
print("does contain, removing:", workoutActivityType.commonName)
}
}
Button {
withAnimation {
deleteFavouriteWorkoutActivityType(favouriteWorkoutActivityType: favouriteWorkoutActivityType)
}
} label: {
Image(systemName: "heart.fill")
}
.buttonStyle(BorderlessButtonStyle())
}
}
}
.onDelete(perform: deleteItems(offsets:))
.onMove(perform: moveItems(from:to:))
if favouriteWorkoutActivityTypes.count < 1 {
Text("Save your favourite workout types by hitting the hearts below.")
.foregroundColor(.secondary)
}
} header: {
Text("Favourites:")
}
}
Section {
ForEach(searchResults, id: \.self) { workoutActivityType in
HStack {
HStack {
Label {
Text(workoutActivityType.commonName)
} icon: {
Image(systemName: (selectedWorkoutActivityTypes.contains(workoutActivityType) ? "checkmark.circle" : "circle"))
}
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
if !selectedWorkoutActivityTypes.contains(where: {$0 == workoutActivityType}) {
selectedWorkoutActivityTypes.append(workoutActivityType)
print("does not contain, adding:", workoutActivityType.commonName)
} else {
selectedWorkoutActivityTypes = selectedWorkoutActivityTypes.filter({$0 != workoutActivityType})
print("does contain, removing:", workoutActivityType.commonName)
}
}
if favouriteWorkoutActivityTypes.contains(where: { FavouriteWorkoutActivityType in
workoutActivityType.rawValue == UInt(FavouriteWorkoutActivityType.workoutActivityTypeRawValue)
}) {
Button {
withAnimation {
deleteFavouriteWorkoutActivityTypeFor(workoutActivityType: workoutActivityType)
}
} label: {
Image(systemName: "heart.fill")
}
.buttonStyle(BorderlessButtonStyle())
} else {
Button {
withAnimation {
createFavouriteWorkoutActivityType(workoutActivityType: workoutActivityType)
}
} label: {
Image(systemName: "heart")
}
.buttonStyle(BorderlessButtonStyle())
}
}
}
} header: {
if searchString == "" {
Text("All:")
} else {
Text("Results:")
}
}
}
.searchable(text: $searchString.animation(), prompt: "Search")
.onChange(of: searchString, perform: { _ in
withAnimation {
updateSearchResults()
}
})
.onAppear {
loadFavouriteWorkoutActivityTypes()
}
.navigationBarTitle(Text("Type"), displayMode: .inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
}
}
}

Related

UI won't change in real time w/ CoreData

I have a SwiftUI + CoreData simple Todo app, and everything works properly, but my updateTodo function which is supposed to handle the click on a todo and turn in from done to undone and vice versa, isn't working properly.
When I click on a todo nothing happens in the UI, but when I go a screen back and come back to the todos screen, I can see the UI change, also it does persist so when I close and open the app the change is being reflected in the app.
So my problem is that the 'isDone' property is not being toggled in the UI in real-time, and only when the view reappears it actually shows the change that has been made.
ViewModel (CoreData) :
class TodoViewModel:ObservableObject {
let container: NSPersistentContainer
#Published var categories = [CategoryTodo]()
#Published var todos = [Todo]()
init() {
container = NSPersistentContainer(name: "UniversityProject")
container.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Error: \(error.localizedDescription)")
}
}
}
//MARK: - Todos
func getTodos() {
let request = NSFetchRequest<Todo>(entityName: "Todo")
let sort = NSSortDescriptor(key: #keyPath(Todo.dateCreated), ascending: false)
request.sortDescriptors = [sort]
do {
try todos = container.viewContext.fetch(request)
} catch {
print("Error getting data. \(error)")
}
}
func addTodo(todo text: String, category categoryName:String) {
let newTodo = Todo(context: container.viewContext)
newTodo.todo = text
newTodo.category = categoryName
newTodo.id = UUID().uuidString
newTodo.isDone = false
newTodo.dateCreated = Date()
saveTodo()
}
func saveTodo() {
do {
try container.viewContext.save()
getTodos()
} catch let error {
print("Error: \(error)")
}
}
func deleteTodo(indexSet: IndexSet) {
let todoIndex = indexSet[indexSet.startIndex]
let object = todos[todoIndex]
container.viewContext.delete(object)
saveTodo()
}
func updateTodo(item: Todo) {
item.setValue(!item.isDone, forKey: "isDone")
saveTodo()
}
}
TodosView:
struct TodosView: View {
#EnvironmentObject var viewModel: TodoViewModel
let categoryName:String
var body: some View {
List {
ForEach(viewModel.todos.filter{$0.category == categoryName}) { item in
TodoItem(item: item)
.onTapGesture {
withAnimation(.linear) {
viewModel.updateTodo(item: item)
}
}
}
.onDelete(perform: viewModel.deleteTodo)
}.onAppear { viewModel.getTodos() }
.navigationBarTitle(categoryName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
HStack {
EditButton()
NavigationLink(destination: AddTodoView(category: categoryName)) {
Image(systemName: "plus.circle")
.resizable()
.foregroundColor(.blue)
.frame(width: 25, height: 25)
}
}
}
}
}
}
without all relevent code, I can only guess and suggest you try something like these:
func updateTodo(item: Todo) {
if let ndx = todos.firstIndex(where: {$0.id == item.id}) {
todos[ndx].isDone = !item.isDone
saveTodo()
}
}
or
func updateTodo(item: Todo) {
objectWillChange.send()
item.setValue(!item.isDone, forKey: "isDone")
saveTodo()
}

How would I get persistent data working in my reminder app

I have a reminder app that I am trying to implement persistent data in but whenever I close the app no data is saved. I know how to make it work with a normal MVC but I would like to get it working with the view model that I have.
I think I know what needs to change to fix the problem but I am not sure how to get to the solution. I am pretty sure that in the ReminderApp file under the NavigationView where it says HomeViewModel(reminds: store.reminds) I think that the store.reminds part needs to be binded to with a $ at the beginning but when I try doing that it doesn't work and instead says that HomeViewModel reminds property expects Reminder instead of Binding.
ReminderStore loads and saves the reminders to a file with the reminders and HomeViewModel contains the reminders array and appends a reminder to the array when a user adds a new reminder.
If anyone knows how to get this working that would be great since I have been stuck on this. My minimal reproducable example code is below.
RemindersApp
import SwiftUI
#main
struct RemindersApp: App {
#StateObject private var store = ReminderStore()
var body: some Scene {
WindowGroup {
NavigationView {
HomeView(homeVM: HomeViewModel(reminds: store.reminds)) {
ReminderStore.save(reminds: store.reminds) { result in
if case .failure(let error) = result {
fatalError(error.localizedDescription)
}
}
}
.navigationBarHidden(true)
}
.onAppear {
ReminderStore.load { result in
switch result {
case .failure(let error):
fatalError(error.localizedDescription)
case .success(let reminds):
store.reminds = reminds
}
}
}
}
}
}
HomeView
import SwiftUI
struct HomeView: View {
#StateObject var homeVM: HomeViewModel
#Environment(\.scenePhase) private var scenePhase
#State var addView = false
let saveAction: ()->Void
var body: some View {
VStack {
List {
ForEach($homeVM.reminds) { $remind in
Text(remind.title)
}
}
}
.safeAreaInset(edge: .top) {
HStack {
Text("Reminders")
.font(.title)
.padding()
Spacer()
Button(action: {
addView.toggle()
}) {
Image(systemName: "plus")
.padding()
.font(.title2)
}
.sheet(isPresented: $addView) {
NavigationView {
VStack {
Form {
TextField("Title", text: $homeVM.newRemindData.title)
}
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Dismiss") {
homeVM.newRemindData = Reminder.Data()
addView.toggle()
}
}
ToolbarItem(placement: .principal) {
Text("New Reminder")
.font(.title3)
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
homeVM.addRemindData(remindData: homeVM.newRemindData)
addView.toggle()
}
}
}
}
}
.onChange(of: scenePhase) { phase in
if phase == .inactive { saveAction() }
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
HomeView(homeVM: HomeViewModel(reminds: Reminder.sampleReminders), saveAction: {})
}
}
ReminderStore
import Foundation
import SwiftUI
class ReminderStore: ObservableObject {
#Published var reminds: [Reminder] = []
private static func fileURL() throws -> URL {
try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
.appendingPathComponent("reminds.data")
}
static func load(completion: #escaping (Result<[Reminder], Error>) -> Void) {
DispatchQueue.global(qos: .background).async {
do {
let fileURL = try fileURL()
guard let file = try? FileHandle(forReadingFrom: fileURL) else {
DispatchQueue.main.async {
completion(.success([]))
}
return
}
let reminds = try JSONDecoder().decode([Reminder].self, from: file.availableData)
DispatchQueue.main.async {
completion(.success(reminds))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
static func save(reminds: [Reminder], completion: #escaping (Result<Int, Error>) -> Void) {
do {
let data = try JSONEncoder().encode(reminds)
let outfile = try fileURL()
try data.write(to: outfile)
DispatchQueue.main.async {
completion(.success(reminds.count))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
HomeViewModel
import Foundation
class HomeViewModel: ObservableObject {
#Published var reminds: [Reminder]
#Published var newRemindData = Reminder.Data()
init(reminds: [Reminder]) {
self.reminds = reminds
}
func addRemindData(remindData: Reminder.Data) {
let newRemind = Reminder(data: remindData)
reminds.append(newRemind)
newRemindData = Reminder.Data()
}
}
Reminder
import Foundation
struct Reminder: Identifiable, Codable {
var title: String
let id: UUID
init(title: String, id: UUID = UUID()) {
self.title = title
self.id = id
}
}
extension Reminder {
struct Data {
var title: String = ""
var id: UUID = UUID()
}
var data: Data {
Data(title: title)
}
mutating func update(from data: Data) {
title = data.title
}
init(data: Data) {
title = data.title
id = UUID()
}
}
extension Reminder {
static var sampleReminders = [
Reminder(title: "Reminder1"),
Reminder(title: "Reminder2"),
Reminder(title: "Reminder3")
]
}
The reason you are struggeling here is because you try to have multiple Source of truth.
documentation on dataflow in SwiftUI
You should move the code from HomeViewModel to your ReminderStore and change the static functions to instance functions. This would keep your logic in one place.
You can pass your ReminderStore to your HomeView as an #EnvironmentObject
This would simplify your code to:
class ReminderStore: ObservableObject {
#Published var reminds: [Reminder] = []
#Published var newRemindData = Reminder.Data()
private func fileURL() throws -> URL {
try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
.appendingPathComponent("reminds.data")
}
func load() {
DispatchQueue.global(qos: .background).async {
do {
let fileURL = try self.fileURL()
guard let file = try? FileHandle(forReadingFrom: fileURL) else {
return
}
let reminds = try JSONDecoder().decode([Reminder].self, from: file.availableData)
DispatchQueue.main.async {
self.reminds = reminds
}
} catch {
DispatchQueue.main.async {
fatalError(error.localizedDescription)
}
}
}
}
func save() {
do {
let data = try JSONEncoder().encode(reminds)
let outfile = try fileURL()
try data.write(to: outfile)
} catch {
fatalError(error.localizedDescription)
}
}
func addRemindData() {
let newRemind = Reminder(data: newRemindData)
reminds.append(newRemind)
newRemindData = Reminder.Data()
}
}
struct RemindersApp: App {
#StateObject private var store = ReminderStore()
var body: some Scene {
WindowGroup {
NavigationView {
HomeView() {
store.save()
}
.navigationBarHidden(true)
.environmentObject(store)
}
.onAppear {
store.load()
}
}
}
}
struct HomeView: View {
#Environment(\.scenePhase) private var scenePhase
#EnvironmentObject private var store: ReminderStore
#State var addView = false
let saveAction: ()->Void
var body: some View {
VStack {
List {
ForEach(store.reminds) { remind in
Text(remind.title)
}
}
}
.safeAreaInset(edge: .top) {
HStack {
Text("Reminders")
.font(.title)
.padding()
Spacer()
Button(action: {
addView.toggle()
}) {
Image(systemName: "plus")
.padding()
.font(.title2)
}
.sheet(isPresented: $addView) {
NavigationView {
VStack {
Form {
TextField("Title", text: $store.newRemindData.title)
}
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Dismiss") {
store.newRemindData = Reminder.Data()
addView.toggle()
}
}
ToolbarItem(placement: .principal) {
Text("New Reminder")
.font(.title3)
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
store.addRemindData()
addView.toggle()
}
}
}
}
}
.onChange(of: scenePhase) { phase in
if phase == .inactive { saveAction() }
}
}
}
}
}
An issue I would recommend solving:
Naming a type after something that´s allready taken by Swift is a bad idea. You should rename your Data struct to something different.
ReminderStore.save isn't invoking in time.
By the time it invokes it doesn't have/get the reminder data.
That's the first thing I would make sure gets done. You may end up running into other issues afterward, but I would personally focus on that first.

How to pass method handler back SwiftUI

I'm new at Swift and currently, I'm implementing UI for verification code but I have no idea to do I have found something that similar to my requirement on StackOverflow I just copy and pass into my project, and then as you can see in VerificationView_Previews we need to pass the method handler back i don't know how to pass it help, please
//
// VerificationView.swift
// UpdateHistory
//
// Created by Admin on 4/21/21.
//
import SwiftUI
public struct VerificationView: View {
var maxDigits: Int = 6
var label = "Enter One Time Password"
#State var pin: String = ""
#State var showPin = true
var handler: (String, (Bool) -> Void) -> Void
public var body: some View {
VStack {
Text(label).font(.title)
ZStack {
pinDots
backgroundField
}
}
}
private var pinDots: some View {
HStack {
Spacer()
ForEach(0..<maxDigits) { index in
Image(systemName: self.getImageName(at: index))
.font(.system(size: 60, weight: .thin, design: .default))
Spacer()
}
}
}
private func getImageName(at index: Int) -> String {
if index >= self.pin.count {
return "square"
}
if self.showPin {
return self.pin.digits[index].numberString + ".square"
}
return "square"
}
private var backgroundField: some View {
let boundPin = Binding<String>(get: { self.pin }, set: { newValue in
self.pin = newValue
self.submitPin()
})
return TextField("", text: boundPin, onCommit: submitPin)
.accentColor(.clear)
.foregroundColor(.clear)
.keyboardType(.numberPad)
}
private var showPinButton: some View {
Button(action: {
self.showPin.toggle()
}, label: {
self.showPin ?
Image(systemName: "eye.slash.fill").foregroundColor(.primary) :
Image(systemName: "eye.fill").foregroundColor(.primary)
})
}
private func submitPin() {
if pin.count == maxDigits {
handler(pin) { isSuccess in
if isSuccess {
print("pin matched, go to next page, no action to perfrom here")
} else {
pin = ""
print("this has to called after showing toast why is the failure")
}
}
}
}
}
extension String {
var digits: [Int] {
var result = [Int]()
for char in self {
if let number = Int(String(char)) {
result.append(number)
}
}
return result
}
}
extension Int {
var numberString: String {
guard self < 10 else { return "0" }
return String(self)
}
}
struct VerificationView_Previews: PreviewProvider {
static var previews: some View {
VerificationView() // need to pass method handler
}
}
For viewing purpose you can just simply use like this. No need second param for preview
struct VerificationView_Previews: PreviewProvider {
static var previews: some View {
VerificationView { (pin, _) in
print(pin)
}
}
}
You can also use like this
struct VerificationView_Previews: PreviewProvider {
var successClosure: (Bool) -> Void
static var previews: some View {
VerificationView { (pin, successClosure) in
}
}
}

error preventing me from building my project that is ambiguous

I am getting this error in my SwiftUI file. I have tried compiling in Swift 5.3, and 5.4 beta, but I keep getting the same error. This file contains custom views that come from an in-house framework created by me. This framework works fine on other projects, so I have eliminated this being the issue. I have many if statements creating the views based on user selection. The code worked before, but it randomly started stating this error.
import SwiftUI
struct Cheatsheet: View {
var Letters = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","Number 1","Number 2","Number 3","Number 4","Number 5","Number 6","Number 7","Number 8","Number 9","Number 0","Number Sign"]
#State private var selection = 0
var body: some View {
VStack {
//Logic
if selection == 0 {
NavigationView {
NavigationLink(destination: LetterA()) { LetterA()
}
}
}
if selection == 1 {
NavigationView {
NavigationLink(destination: LetterB()) { LetterB()
}
}
}
if selection == 2 {
NavigationView {
NavigationLink(destination: LetterC()) { LetterC()
}
}
}
if selection == 3 {
NavigationView {
NavigationLink(destination: LetterD()) { LetterD()
}
}
}
if selection == 4 {
NavigationView {
NavigationLink(destination: LetterE()) { LetterE()
}
}
}
if selection == 5 {
NavigationView {
NavigationLink(destination: LetterF()) { LetterF()
}
}
}
if selection == 6 {
NavigationView {
NavigationLink(destination: LetterG()) { LetterG()
}
}
}
if selection == 7 {
NavigationView {
NavigationLink(destination: LetterH()) { LetterH()
}
}
}
if selection == 8 {
NavigationView {
NavigationLink(destination: LetterI()) { LetterI()
}
}
}
if selection == 9 {
NavigationView {
NavigationLink(destination: LetterJ()) { LetterJ()
}
}
}
if selection == 10 {
NavigationView {
NavigationLink(destination: LetterK()) { LetterK()
}
}
}
if selection == 11 {
NavigationView {
NavigationLink(destination: LetterL()) { LetterL()
}
}
}
if selection == 12 {
NavigationView {
NavigationLink(destination: LetterM()) { LetterM()
}
}
}
if selection == 13 {
NavigationView {
NavigationLink(destination: LetterN()) { LetterN()
}
}
}
if selection == 14 {
NavigationView {
NavigationLink(destination: LetterO()) { LetterO()
}
}
}
if selection == 15 {
NavigationView {
NavigationLink(destination: LetterP()) { LetterP()
}
}
}
if selection == 16 {
NavigationView {
NavigationLink(destination: LetterQ()) { LetterQ()
}
}
}
if selection == 17 {
NavigationView {
NavigationLink(destination: LetterR()) { LetterR()
}
}
}
if selection == 18 {
NavigationView {
NavigationLink(destination: LetterS()) { LetterS()
}
}
}
if selection == 19 {
NavigationView {
NavigationLink(destination: LetterT()) { LetterT()
}
}
}
if selection == 20 {
NavigationView {
NavigationLink(destination: LetterU()) { LetterU()
}
}
}
if selection == 21 {
NavigationView {
NavigationLink(destination: LetterV()) { LetterV()
}
}
}
if selection == 22 {
NavigationView {
NavigationLink(destination: LetterW()) { LetterW()
}
}
}
if selection == 23 {
NavigationView {
NavigationLink(destination: LetterX()) { LetterX()
}
}
}
if selection == 24 {
NavigationView {
NavigationLink(destination: LetterY()) { LetterY()
}
}
}
if selection == 25 {
NavigationView {
NavigationLink(destination: LetterZ()) { LetterZ()
}
}
}
//END -> Logic
Picker(selection: $selection, label: Text("")) {
ForEach(0 ..< Letters.count) {
Text(Letters[$0])
}
}
}
}
}
struct Cheatsheet_Previews: PreviewProvider {
static var previews: some View {
Cheatsheet()
}
}
It is possible to have not more than 10 views in one container, so group them by 10th, like
Group {
if selection == 0 {
NavigationView {
NavigationLink(destination: LetterA()) { LetterA()
}
}
}
if selection == 1 {
NavigationView {
NavigationLink(destination: LetterB()) { LetterB()
}
}
}
...
if selection == 9 {
NavigationView {
NavigationLink(destination: LetterJ()) { LetterJ()
}
}
}
}
Group {
if selection == 10 {
NavigationView {
NavigationLink(destination: LetterK()) { LetterK()
}
}
}
...
}
or, better, rethink the design to use ForEach view builder.
Problem is here
Picker(selection: $selection, label: Text("")) {
ForEach(0 ..< Letters.count) {
Text(Letters[$0])
}
}
you need self with Letters so it should be
Picker(selection: $selection, label: Text("")) {
ForEach(0 ..< Letters.count) {
Text(self.Letters[$0])
}
}
And second problem is the amount of views added in 1 group, you can't have more than 10 views per 1 group. You would be better off just making a function that takes care of the switching. and its cleaner.
Here is an example
struct Test: View {
var Letters = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","Number 1","Number 2","Number 3","Number 4","Number 5","Number 6","Number 7","Number 8","Number 9","Number 0","Number Sign"]
#State private var selection = 0
var body: some View {
VStack {
// Logic
getLetterView(selection: self.selection)
//END -> Logic
Picker(selection: $selection, label: Text("")) {
ForEach(0 ..< Letters.count) {
Text(self.Letters[$0])
}
}
}
}
}
func getLetterView(selection: Int) -> AnyView? {
var destination: AnyView? = nil
if selection == 0 {
destination = AnyView(LetterA())
} else if(selection == 1) {
destination = AnyView(LetterB())
} else if(selection == 2) {
destination = AnyView(LetterC())
} else if(selection == 3) {
destination = AnyView(LetterD())
} else if(selection == 4) {
destination = AnyView(LetterE())
} else if(selection == 5) {
destination = AnyView(LetterF())
} else if(selection == 6) {
destination = AnyView(LetterG())
} else if(selection == 7) {
destination = AnyView(LetterH())
} else if(selection == 8) {
destination = AnyView(LetterI())
} else if(selection == 9) {
destination = AnyView(LetterJ())
}
// Rest of your if conditions
return AnyView(NavigationView {
NavigationLink(destination: destination) {
destination
}
})
}

First declared closure in SwiftUI is called but not others

I have 4 closure in button action but only the first one is triggered
Button(action: { self.showScanner = true }) {
ButtonContent(buttonTextContent: "IN")
}
.sheet(isPresented: self.$showScanner) {
QRCodeRepresentable(isPresented: self.$showScanner, didCodeFound: { code in
debugPrint("IN - \(code)")
})
}
Button(action: { self.showScanner = true }) {
CloakroomButtonContent(buttonTextContent: "OUT")
}
.sheet(isPresented: self.$showScanner) {
QRCodeRepresentable(isPresented: self.$showScanner, didCodeFound: { code in
debugPrint("OUT - \(code)")
})
}
Button(action: { self.showScanner = true }) {
ButtonContent(buttonTextContent: "OWNER")
}
.sheet(isPresented: self.$showScanner) {
QRCodeRepresentable(isPresented: self.$showScanner, didCodeFound: { code in
debugPrint("Owner - \(code)")
})
}
struct QRCodeRepresentable: UIViewControllerRepresentable {
#Binding var isPresented: Bool
var didCodeFound: (String) -> ()
}
The issue is that if i tap on other button like "out" and "owner" then the closure of the "in" button is triggered.
I always have "debugPrint("IN - (code)")" called.
Any ideas, what's wrong in my code?
Thanks.
If you have multiple sheets, please use item, not isPresented
extension String: Identifiable{
public var id : String {get{self}}
}
#State var activeString : String?
var body: some View {
{
ForEach(["IN", "Out", "Owner"], id: \.self ){ str in
Button(action: { self.activeString = str} ) {
// ButtonContent(buttonTextContent: "IN")
Text(str)
}}.sheet(item: self.$activeString) {
Text($0)
// QRCodeRepresentable(isPresented: self.$showScanner, didCodeFound: { code in
// debugPrint("IN - \(code)")
// })
}
}
I think you need to remove that boolean variable showScanner or make it type String or other (int) type. For maintain all 3 state you want in functionality
Try below code.
#State var showScanner = ""
Button(action: { self.showScanner = "IN" }) {
ButtonContent(buttonTextContent: "IN")
}
.sheet(isPresented: self.$showScanner == "IN") {
QRCodeRepresentable(isPresented: self.$showScanner, didCodeFound: { code in
debugPrint("IN - \(code)")
})
}
Button(action: { self.showScanner = "OUT" }) {
CloakroomButtonContent(buttonTextContent: "OUT")
}
.sheet(isPresented: self.$showScanner == "OUT") {
QRCodeRepresentable(isPresented: self.$showScanner, didCodeFound: { code in
debugPrint("OUT - \(code)")
})
}
Button(action: { self.showScanner = "OWNER" }) {
ButtonContent(buttonTextContent: "OWNER")
}
.sheet(isPresented: self.$showScanner == "OWNER") {
QRCodeRepresentable(isPresented: self.$showScanner, didCodeFound: { code in
debugPrint("Owner - \(code)")
})
}

Resources