How to display the selected menu item in SwiftUI? - ios

I want to selected menu to get displayed and saved when the user comes back later. my current code just displays the selected item, but not getting saved when I close the sheet and comes back.
#State var selectedAge: Int = .zero
var body: some View {
Menu {
ForEach(myViewModel.MyModel.selectAge.indices, id: \.self) { indice in
Button(action: {
selectedAge = indice
}) {
if selectedAge == indice {
Text("\(myViewModel.MyModel.selectAge[indice])")
}
else {
Text("")
}
}
}
} label: {
Text("\(myViewModel.MyModel.selectAge[selectedAge])")
}
}
This code from my Model
var selectedAge: [String] = ["12", "15", "18", "21", "24"]
Please guide me to solve this issue.

I used this code to solve my issue. thank you for other developers who had taken their time to help me out!.
Menu{
ForEach(myViewModel.myModel.selectAge, id: \.self){ index in
Button(action : {
myViewModel.myModel.selectAge = "\(index)"
}) {
if myViewModel.myModel.selectedAge == index{
Label("\(index)", systemImage: "checkmark")
}else {
Text("\(index)")
}
}
}
} label: {
Text("\(myViewModel.myModel.selectedAge)")
}
and this is insert in my model
var selectedAge = "12"
var selectedAge: [String] = ["12", "15", "18", "21", "24"]

Try this:
// selectedAge1 array list replace with your data model list...
import Combine
class MenuAgeSettings: ObservableObject {
#Published var selectedAge: String {
didSet {
UserDefaults.standard.set(selectedAge, forKey: "selectedAge")
}
}
init() {
self.selectedAge = UserDefaults.standard.object(forKey: "selectedAge") as? String ?? ""
}
}
struct MenuView: View {
var selectedAge1: [String] = ["12", "15", "18", "21","24"]
#ObservedObject var menuSettings = MenuAgeSettings()
var body: some View {
Menu{
ForEach(self.selectedAge1.indices, id: \.self){ indice in
let text = self.selectedAge1[indice]
Button(action : {
menuSettings.selectedAge = text
}) {
if menuSettings.selectedAge == text {
Label(text, systemImage: "checkmark")
} else {
Text(text)
}
}
}
} label: {
if menuSettings.selectedAge.isEmpty {
Text(selectedAge1[.zero])
} else {
Text(menuSettings.selectedAge)
}
}
}
}

Related

How to change the selection of the second picker after changing the first picker?

Here I'm trying to create the first little app that helps to convert a value from one unit of measurement to another.
The first conversion picker helps me to select data for the second picker(from which measure I'm going to converse) and the third picker(to which measure I'm going converse).
But when I'm changing the first picker - it doesn't change the #State value of the second and third pickers. I tried different approaches but the result is the same. Could you please help me?
struct ContentView: View {
#State private var userInput = 0.0
#State var selectionOfTypeOfConversion: Conversions = .temperature
#State var selectionFromConversion: String = TemperatureConversions.celsius.rawValue
#State var selectionToConversion: String = TemperatureConversions.celsius.rawValue
#FocusState private var inputIsFocused: Bool
var output: Double {
//doing all counting
}
var body: some View {
VStack {
NavigationView {
Form {
Section {
Picker("Type of conversion", selection: $selectionOfTypeOfConversion) {
ForEach(Conversions.allCases, id: \.self) {
Text($0.rawValue)
}
}.onSubmit {
checkConvention()
}
TextField("", value: $userInput, format: .number)
.keyboardType(.decimalPad)
.focused($inputIsFocused)
Picker("From conversion", selection: $selectionFromConversion) {
switch selectionOfTypeOfConversion {
case Conversions.temperature:
ForEach(TemperatureConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
case Conversions.length:
ForEach(LengthConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
case Conversions.volume:
ForEach(VolumeConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
}
}
} header: {
Text("From")
}
Section {
Picker("To conversion", selection: $selectionToConversion) {
switch selectionOfTypeOfConversion {
case Conversions.temperature:
ForEach(TemperatureConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
case Conversions.length:
ForEach(LengthConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
case Conversions.volume:
ForEach(VolumeConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
}
}
Text(output, format: .number)
} header: {
Text("To")
}
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
inputIsFocused = false
}
}
}
}
}
}
private func checkConvention() {
if selectionOfTypeOfConversion == Conversions.temperature {
selectionFromConversion = TemperatureConversions.celsius.rawValue
selectionToConversion = TemperatureConversions.celsius.rawValue
} else if selectionOfTypeOfConversion == Conversions.length{
selectionFromConversion = LengthConversions.meters.rawValue
selectionToConversion = LengthConversions.meters.rawValue
} else if selectionOfTypeOfConversion == Conversions.volume{
selectionFromConversion = VolumeConversions.milliliters.rawValue
selectionToConversion = VolumeConversions.milliliters.rawValue
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
enum Conversions: String, CaseIterable {
case temperature = "temperature"
case length = "length"
case volume = "volume"
}
enum TemperatureConversions: String, CaseIterable {
var id: Self { self }
case celsius = "celsius"
case fahrenheit = "fahrenheit"
case kelvin = "kelvin"
}
enum LengthConversions: String, CaseIterable {
var id: Self { self }
case meters = "meters"
case feet = "feet"
case miles = "miles"
}
enum VolumeConversions: String, CaseIterable {
var id: Self { self }
case milliliters = "ml"
case pints = "pints"
case gallons = "gallons"
}
Expecting: change the formula which counts input into output.

Update Details on a List SwiftUI

I'm fairly new to SwiftUI and I'm trying to update the details on a list and then save it.
I am able to get the details to update, but every time I try saving I'm not able to do it.
I have marked the area where I need help. Thanks in advance.
// This is the Array to store the items:
struct ExpenseItem : Identifiable, Codable {
var id = UUID()
let name: String
let amount: Int
}
// This is the UserDefault Array
class Expenses: ObservableObject {
#Published var items = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(encoded, forKey: "Items")
}
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Items") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
items = decodedItems
return
}
}
items = []
}
}
// View to add details :
struct AddView: View {
#State private var name = ""
#State private var amount = 0
#StateObject var expenses: Expenses
#Environment(\.dismiss) var dismiss
var body: some View {
Form {
TextField("Name", text: $name)
Text("\(amount)")
Button("Tap Me") {
amount += 1
}
}
.navigationTitle("Add New Count")
.toolbar {
if name != "" {
Button("Save") {
let item = ExpenseItem(name: name, amount: amount)
expenses.items.append(item)
dismiss()
}
}
}
}
}
// This is the file to update the details:
struct UpdateDhikr: View {
#EnvironmentObject var expenses : Expenses
#State var name : String
#State var amount : Int
var body: some View {
Form {
TextField("Name", text: $name)
Text("\(amount)")
Button("Tap Me") {
amount += 1
}
}
.navigationTitle("Update Count")
.toolbar {
if name != "" {
Button("Save") {
// This is where I'm having problems.
}
}
}
}
}

Encoding to JSON format is not encoding the toggled boolean value in Swift

I am making an app that has information about different woods, herbs and spices, and a few other things. I am including the ability to save their favorite item to a favorites list, so I have a heart button that the user can press to add it to the favorites. Pressing the button toggles the isFavorite property of the item and then leaving the page calls a method that encodes the data to save it to the user's device. The problem that I am running into is that it is not encoding the updated value of the isFavorite property. It is still encoding the value as false, so the favorites list is not persisting after closing and reopening the app.
Here is my Wood.swift code, this file sets up the structure for Wood items. I also included the test data that I was using to make sure that it displayed properly in the Wood extension:
import Foundation
struct Wood: Identifiable, Codable {
var id = UUID()
var mainInformation: WoodMainInformation
var preparation: [Preparation]
var isFavorite = false
init(mainInformation: WoodMainInformation, preparation: [Preparation]) {
self.mainInformation = mainInformation
self.preparation = preparation
}
}
struct WoodMainInformation: Codable {
var category: WoodCategory
var description: String
var medicinalUses: [String]
var magicalUses: [String]
var growZone: [String]
var lightLevel: String
var moistureLevel: String
var isPerennial: Bool
var isEdible: Bool
}
enum WoodCategory: String, CaseIterable, Codable {
case oak = "Oak"
case pine = "Pine"
case cedar = "Cedar"
case ash = "Ash"
case rowan = "Rowan"
case willow = "Willow"
case birch = "Birch"
}
enum Preparation: String, Codable {
case talisman = "Talisman"
case satchet = "Satchet"
case tincture = "Tincture"
case salve = "Salve"
case tea = "Tea"
case ointment = "Ointment"
case incense = "Incense"
}
extension Wood {
static let woodTypes: [Wood] = [
Wood(mainInformation: WoodMainInformation(category: .oak,
description: "A type of wood",
medicinalUses: ["Healthy", "Killer"],
magicalUses: ["Spells", "Other Witchy Stuff"],
growZone: ["6A", "5B"],
lightLevel: "Full Sun",
moistureLevel: "Once a day",
isPerennial: false,
isEdible: true),
preparation: [Preparation.incense, Preparation.satchet]),
Wood(mainInformation: WoodMainInformation(category: .pine,
description: "Another type of wood",
medicinalUses: ["Healthy"],
magicalUses: ["Spells"],
growZone: ["11G", "14F"],
lightLevel: "Full Moon",
moistureLevel: "Twice an hour",
isPerennial: true,
isEdible: true),
preparation: [Preparation.incense, Preparation.satchet])
]
}
Here is my WoodData.swift file, this file contains methods that allow the app to display the correct wood in the list of woods, as well as encode, and decode the woods:
import Foundation
class WoodData: ObservableObject {
#Published var woods = Wood.woodTypes
var favoriteWoods: [Wood] {
woods.filter { $0.isFavorite }
}
func woods(for category: WoodCategory) -> [Wood] {
var filteredWoods = [Wood]()
for wood in woods {
if wood.mainInformation.category == category {
filteredWoods.append(wood)
}
}
return filteredWoods
}
func woods(for category: [WoodCategory]) -> [Wood] {
var filteredWoods = [Wood]()
filteredWoods = woods
return filteredWoods
}
func index(of wood: Wood) -> Int? {
for i in woods.indices {
if woods[i].id == wood.id {
return i
}
}
return nil
}
private var dataFileURL: URL {
do {
let documentsDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
return documentsDirectory.appendingPathComponent("evergreenData")
}
catch {
fatalError("An error occurred while getting the url: \(error)")
}
}
func saveWoods() {
if let encodedData = try? JSONEncoder().encode(woods) {
do {
try encodedData.write(to: dataFileURL)
let string = String(data: encodedData, encoding: .utf8)
print(string)
}
catch {
fatalError("An error occurred while saving woods: \(error)")
}
}
}
func loadWoods() {
guard let data = try? Data(contentsOf: dataFileURL) else { return }
do {
let savedWoods = try JSONDecoder().decode([Wood].self, from: data)
woods = savedWoods
}
catch {
fatalError("An error occurred while loading woods: \(error)")
}
}
}
Finally, this is my WoodsDetailView.swift file, this file displays the information for the wood that was selected, as well as calls the method that encodes the wood data:
import SwiftUI
struct WoodsDetailView: View {
#Binding var wood: Wood
#State private var woodsData = WoodData()
var body: some View {
VStack {
List {
Section(header: Text("Description")) {
Text(wood.mainInformation.description)
}
Section(header: Text("Preparation Techniques")) {
ForEach(wood.preparation, id: \.self) { technique in
Text(technique.rawValue)
}
}
Section(header: Text("Edible?")) {
if wood.mainInformation.isEdible {
Text("Edible")
}
else {
Text("Not Edible")
}
}
Section(header: Text("Medicinal Uses")) {
ForEach(wood.mainInformation.medicinalUses.indices, id: \.self) { index in
let medicinalUse = wood.mainInformation.medicinalUses[index]
Text(medicinalUse)
}
}
Section(header: Text("Magical Uses")) {
ForEach(wood.mainInformation.magicalUses.indices, id: \.self) { index in
let magicalUse = wood.mainInformation.magicalUses[index]
Text(magicalUse)
}
}
Section(header: Text("Grow Zone")) {
ForEach(wood.mainInformation.growZone.indices, id: \.self) { index in
let zone = wood.mainInformation.growZone[index]
Text(zone)
}
}
Section(header: Text("Grow It Yourself")) {
Text("Water: \(wood.mainInformation.moistureLevel)")
Text("Needs: \(wood.mainInformation.lightLevel)")
if wood.mainInformation.isPerennial {
Text("Perennial")
}
else {
Text("Annual")
}
}
}
}
.navigationTitle(wood.mainInformation.category.rawValue)
.onDisappear {
woodsData.saveWoods()
}
.toolbar {
ToolbarItem {
HStack {
Button(action: {
wood.isFavorite.toggle()
}) {
Image(systemName: wood.isFavorite ? "heart.fill" : "heart")
}
}
}
}
}
}
struct WoodsDetailView_Previews: PreviewProvider {
#State static var wood = Wood.woodTypes[0]
static var previews: some View {
WoodsDetailView(wood: $wood)
}
}
This is my MainTabView.swift file:
import SwiftUI
struct MainTabView: View {
#StateObject var woodData = WoodData()
var body: some View {
TabView {
NavigationView {
List {
WoodsListView(viewStyle: .allCategories(WoodCategory.allCases))
}
}
.tabItem { Label("Main", systemImage: "list.dash")}
NavigationView {
List {
WoodsListView(viewStyle: .favorites)
}
.navigationTitle("Favorites")
}.tabItem { Label("Favorites", systemImage: "heart.fill")}
}
.environmentObject(woodData)
.onAppear {
woodData.loadWoods()
}
.preferredColorScheme(.dark)
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
This is my WoodListView.swift file:
import SwiftUI
struct WoodsListView: View {
#EnvironmentObject private var woodData: WoodData
let viewStyle: ViewStyle
var body: some View {
ForEach(woods) { wood in
NavigationLink(wood.mainInformation.category.rawValue, destination: WoodsDetailView(wood: binding(for: wood)))
}
}
}
extension WoodsListView {
enum ViewStyle {
case favorites
case singleCategory(WoodCategory)
case allCategories([WoodCategory])
}
private var woods: [Wood] {
switch viewStyle {
case let .singleCategory(category):
return woodData.woods(for: category)
case let .allCategories(category):
return woodData.woods(for: category)
case .favorites:
return woodData.favoriteWoods
}
}
func binding(for wood: Wood) -> Binding<Wood> {
guard let index = woodData.index(of: wood) else {
fatalError("Wood not found")
}
return $woodData.woods[index]
}
}
struct WoodsListView_Previews: PreviewProvider {
static var previews: some View {
WoodsListView(viewStyle: .singleCategory(.ash))
.environmentObject(WoodData())
}
}
Any assistance into why it is not encoding the toggled isFavorite property will be greatly appreciated.
Your problem is that structs are value types in Swift. Essentially this means that the instance of Wood that you have in WoodsDetailView is not the same instance that is in your array in your model (WoodData); It is a copy (Technically, the copy is made as soon as you modify the isFavourite property).
In SwiftUI it is important to maintain separation of responsibilities between the view and the model.
Changing the favourite status of a Wood is something the view should ask the model to do.
This is where you have a second issue; In your detail view you are creating a separate instance of your model; You need to refer to a single instance.
You have a good start; you have put your model instance in the environment where views can access it.
First, change the detail view to remove the binding, refer to the model from the environment and ask the model to do the work:
struct WoodsDetailView: View {
var wood: Wood
#EnvironmentObject private var woodsData: WoodData
var body: some View {
VStack {
List {
Section(header: Text("Description")) {
Text(wood.mainInformation.description)
}
Section(header: Text("Preparation Techniques")) {
ForEach(wood.preparation, id: \.self) { technique in
Text(technique.rawValue)
}
}
Section(header: Text("Edible?")) {
if wood.mainInformation.isEdible {
Text("Edible")
}
else {
Text("Not Edible")
}
}
Section(header: Text("Medicinal Uses")) {
ForEach(wood.mainInformation.medicinalUses, id: \.self) { medicinalUse in
Text(medicinalUse)
}
}
Section(header: Text("Magical Uses")) {
ForEach(wood.mainInformation.magicalUses, id: \.self) { magicalUse in
Text(magicalUse)
}
}
Section(header: Text("Grow Zone")) {
ForEach(wood.mainInformation.growZone, id: \.self) { zone in
Text(zone)
}
}
Section(header: Text("Grow It Yourself")) {
Text("Water: \(wood.mainInformation.moistureLevel)")
Text("Needs: \(wood.mainInformation.lightLevel)")
if wood.mainInformation.isPerennial {
Text("Perennial")
}
else {
Text("Annual")
}
}
}
}
.navigationTitle(wood.mainInformation.category.rawValue)
.onDisappear {
woodsData.saveWoods()
}
.toolbar {
ToolbarItem {
HStack {
Button(action: {
self.woodsData.toggleFavorite(for: wood)
}) {
Image(systemName: wood.isFavorite ? "heart.fill" : "heart")
}
}
}
}
}
}
struct WoodsDetailView_Previews: PreviewProvider {
static var wood = Wood.woodTypes[0]
static var previews: some View {
WoodsDetailView(wood: wood)
}
}
I also got rid of the unnecessary use of indices when listing the properties.
Now, add a toggleFavorite function to your WoodData object:
func toggleFavorite(for wood: Wood) {
guard let index = self.woods.firstIndex(where:{ $0.id == wood.id }) else {
return
}
self.woods[index].isFavorite.toggle()
}
You can also remove the index(of wood:Wood) function (which was really just duplicating Array's firstIndex(where:) function) and the binding(for wood:Wood) function.
Now, not only does your code do what you want, but you have hidden the mechanics of toggling a favorite from the view; It simply asks for the favorite status to be toggled and doesn't need to know what this actually involves.

How to append more than one Number into Binding var

I'm new to swift and I'm trying to develop a Velocity Calculator.
Here is my Code:
struct VelocityCalc: View {
#State var velocityNumbers1 : [String] = []
var body: some View {
VStack {
VStack {
Text("Headline")
TextField("e.g., 1, 3, 5, 8,...", text: $velocityNumbers1)
Button {
print("Button works")
} label: {
Text("Tap me")
}
}
}
}
What I want to develop is that the User can type in for example: 12, 14, 12, 10, ...
This Numbers needs to be sorted and so on.
Maybe someone can help me with this Issue or give me some advisory for that.
Big thanks for your help :)
I have seen answers, however what I have found out that when you enter the numbers the way you showed us on your question ex: 2, 1, 5, 9 with Space or WhiteSpace it won't work as expected so here it is a solution to overcome this problem:
#State var velocityNumbers = ""
func reorderTheArray(velocity: String) -> [String] {
let orderVelocity = velocity.components(separatedBy: ",").compactMap{
Int($0.trimmingCharacters(in: .whitespaces))
}
return orderVelocity.sorted().compactMap {
String($0)
}
}
var body: some View {
VStack {
Text("Headline")
TextField("example", text: self.$velocityNumbers)
Button(action: {
self.velocityNumbers = reorderTheArray(velocity: self.velocityNumbers).joined(separator: ",")
print(self.velocityNumbers)
}) {
Text("Reorder")
}
}
}
Now when you click the Reorder button, everything will be reordered on your textfield directly.
Try something like this,
Get the numbers as a string
Split them using separator(')
Convert them into Int and sort
struct ContentView: View {
#State var velocityNumber : String = ""
var body: some View {
VStack {
VStack {
Text("Headline")
TextField("e.g., 1, 3, 5, 8,...", text: $velocityNumber)
Button {
let allNumbers = velocityNumber.split(separator: ",").compactMap {
Int($0)
}
print(allNumbers.sorted())
} label: {
Text("Tap me")
}
}
}
}
}
I would see it like this:
First i would take all numbers as a string, then split the string using the separator ",", then convert all strings to an int array and sort
struct VelocityCalc: View {
#State var velocityNumbers1 : String
var body: some View {
VStack {
VStack {
Text("Headline")
TextField("e.g., 1, 3, 5, 8,...", text: $velocityNumbers1)
Button {
let velocityNumbersArray = velocityNumbers1
.components(separatedBy: ",")
.map { Int($0)! }.sorted()
print(velocityNumbersArray)
} label: {
Text("Tap me")
}
}
}
}
}
I think it makes more sense to enter one value at a time and use a separate field to display the entered values
#State var velocityNumbers : [Int] = []
#State var velocity: String = ""
var body: some View {
VStack {
VStack {
Text("Headline")
TextField("velocity", text: $velocity)
Button {
if let value = Int(velocity) {
velocityNumbers.append(value)
velocityNumbers.sort()
}
velocity = ""
} label: {
Text("Add")
}
.keyboardShortcut(.defaultAction)
Divider()
Text(velocityNumbers.map(String.init).joined(separator: ", "))
}
}
}

How to change color of buttons created dynamically inside forEach in SwiftUI

I want to change the colour of different buttons with different color inside swiftUI forEach statement. The colour of other buttons shouldn't be changed when changing a button color. How can I achieve this? My code looks like this:
import SwiftUI
struct ColorModel: Identifiable {
let value: Color
let id = UUID()
}
let colors = [
ColorModel(value: Color.orange),
ColorModel(value: Color.green),
ColorModel(value: Color.blue),
ColorModel(value: Color.red),
ColorModel(value: Color.yellow),
ColorModel(value: Color.gray),
ColorModel(value: Color.pink),
]
let totalButtons: Int = 10
struct ContentView: View {
func updateSelectedButtons(value: Int) {
if self.selectedButtons.contains(value) {
if let index = self.selectedButtons.firstIndex(of: value) {
self.selectedButtons.remove(at: index)
}
} else {
if self.selectedButtons.count < 7 {
self.selectedButtons.append(value)
}
}
}
#State private var selectedButtons: [Int] = [Int]()
#State private var colorIndex: Int = 0
var body: some View {
ForEach(0 ..< totalButtons) { index in
Button(action: {
self.updateSelectedButtons(value: index)
self.colorIndex += 1
}) {
Text(" ")
}
.background(self.selectedButtons.contains(index) ? colors[self.colorIndex].value : Color.white)
}
}
}
You can try the following:
struct ContentView: View {
#State private var selectedButtons = [Int]()
var body: some View {
ForEach(0..<totalButtons) { index in
Button(action: {
self.updateSelectButton(value: index) // <- on tap update selection
}) {
Text("Button \(index)")
}
.background(self.selectedButtons.contains(index) ? colors[index].value : Color.white) // <- if index is selected set color
}
}
func updateSelectButton(value: Int) {
guard value < colors.count else { // <- make sure you don't go outside the `colors` range
return
}
if let index = self.selectedButtons.firstIndex(of: value) {
self.selectedButtons.remove(at: index)
} else {
self.selectedButtons.append(value)
}
}
}

Resources