How to scale individual image within a forEach loop? - swift

I have a ListView that has 20 items and uses the following Image within the ForEach loop:
Image(systemName: "heart").foregroundColor(.red).onTapGesture {
selected.toggle()
favLists.append(country.id)
favLists = favLists.removingDuplicates()
}
.scaleEffect(self.selected ? 1.5 : 1)
The issue is that selected is a single variable, so that is toggling the state for ALL my items in the list. How can I declare dynamic state dependent on the number of index items?

try this approach, ...to scale individual image within a ForEach loop, works for me:
struct ContentView: View {
#State var selected = "heart"
var body: some View {
VStack (spacing: 55) {
ForEach(["globe", "house", "person", "heart"], id: \.self) { name in
Image(systemName: name).id(name)
.onTapGesture {
selected = name
// ....other code
}
.foregroundColor(selected == name ? .red : .blue)
.scaleEffect(selected == name ? 2.5 : 1)
}
}
}
}

Related

Multiple controls on same line in SwiftUI macOS form

I am making a UI to change the 3D coordinates of an object, and I thought it would make sense to put all three on the same line with a label beforehand, sort of like System Preferences does for number separators :
However, doing so messes up the alignment of the whole form, and I'm not sure how to resolve this (except by adding VStacks and HStacks everywhere, which I really hope is not the best available solution) :
Here is the code driving the view :
struct ObjectSettingsView: View {
#State var object : Object
var body : some View {
Form {
TextField("Name:", text: $object.name, prompt : Text("New Object"))
Toggle(
"Visible",
isOn: $object.visible
)
Divider()
HStack {
Text("Coordinates:")
NumberView(label : "X:", number : object.coordinates.x)
NumberView(label : "Y:", number : object.coordinates.y)
NumberView(label : "Z:", number : object.coordinates.z)
}
}
}
}
struct NumberView : View{
var label : String
#State var number : Int32
var body : some View {
HStack {
TextField(
self.label,
value: self.$number,
formatter: NumberFormatter()
)
Stepper("", value: self.$number, in: 1...8)
.labelsHidden()
}
}
}
( I know this really should be using a ViewModel, I'm just trying to figure out how forms work right now )
I add #Binding and LazyVGrid to your Code.
Maybe this helps:
struct ObjectData {
var name: String = "New Object"
var visible: Bool = true
var coordinates_x: Int32 = 0
var coordinates_y: Int32 = 0
var coordinates_z: Int32 = 0
}
struct ContentView: View {
#State var data = ObjectData()
let columns = [
GridItem(alignment: .trailing),
GridItem(alignment: .leading),
]
var form: some View {
LazyVGrid(columns: columns) {
Text("Name:")
TextField("", text: $data.name)
Text("blind").opacity(0)
Toggle("Visible:", isOn: $data.visible)
Text("Coordinates:")
HStack {
NumberView(label : "X:", number : $data.coordinates_x)
NumberView(label : "Y:", number : $data.coordinates_y)
NumberView(label : "Z:", number : $data.coordinates_z)
}
}
}
var body : some View {
VStack() {
form
Text(" --- Check --- ")
Text(String(describing: data))
}
.frame(width: 400.0)
}
}
struct NumberView : View{
var label : String
#Binding var number : Int32
var body : some View {
HStack {
TextField(
self.label,
value: self.$number,
formatter: NumberFormatter()
)
Stepper("", value: self.$number, in: 1...8)
.labelsHidden()
}
}
}
Separating things into two Forms almost does the trick, although labels are still not exactly aligned as in system Preferences :
struct ObjectSettingsView: View {
#State var object : Object
var body : some View {
VStack {
Form {
TextField("Name:", text: $object.name, prompt : Text("New Object"))
Toggle("Visible", isOn: $object.visible)
}
Divider()
Form {
HStack {
Text("Coordinates:")
NumberView(label : "X:", number : object.coordinates.x)
NumberView(label : "Y:", number : object.coordinates.y)
NumberView(label : "Z:", number : object.coordinates.z)
}
}
}
.padding()
}
}
// static let left = GridItem.Size.flexible(minimum: 10, maximum: .infinity)
static let left = GridItem.Size.flexible(minimum: 40, maximum: 100)
static let right = GridItem.Size.flexible(minimum: 40, maximum: 200)
let columns = [
GridItem(left, alignment: .trailing),
GridItem(right, alignment: .leading),
]
The problem is that the size structs are set to .infinity
I changed the column settings like above with fixed max values.
To get all infos see Apple Docs about GridItem.

How to write an array of values to Firestore with SwiftUI

Currently I have a screen that fetches a list of "items" from an "items" Collection. The user is supposed to be able to select multiple items, and add them to a document within the "Movies" collection when a button is pressed. I can't seem to figure out how to write the function to write the list of these items to the Movie document.
Code examples below
This is the loop that fetches the items (working so far)
let movie: Movie
#State var selections: [String] = []
#ObservedObject private var viewModel = ItemsViewModel()
#State private var isSelected = false
#State var movieID = ""
var body: some View {
VStack {
ForEach(viewModel.items.indices, id: \.self) { i in
Button(action: { viewModel.items[i].isSelected.toggle() }) {
HStack {
if viewModel.items[i].isSelected {
Text(viewModel.items[i].name)
.foregroundColor(.white)
.font(.system(size: 16, weight: .bold))
Spacer()
Image(systemName: "checkmark")
.foregroundColor(ColourManager.pinkColour)
.font(.system(size: 16))
} else {
Text(viewModel.items[i].name)
.foregroundColor(Color(.systemGray))
.font(.system(size: 16))
Spacer()
}
}
.padding(.vertical, 10)
.padding(.horizontal)
}
Divider()
}
}
.onAppear() {
self.viewModel.fetchData()
}
Button calling function
Button(action: { self.addItems() }) {
HStack {
Text("Add Selected Items")
}
Function to write to Firestore
func addItems() {
let db = Firestore.firestore()
db.collection("movies").document(self.movieID).setData(["item": [self.selections]])
}
What I would like is the Firestore db to look like:
{
movieID: 12345,
movieName: "Goodfellas",
items: [
item: "item 1",
item: "item 2"
]
}
So far I have the movieID and movieName working, just can't figure out how to record the selections and write them as items. Any help would be greatly appreciated!
The answer depends on what you'd like the type of items field to be. The information you want to store could be stored either as an array of strings or as an object literal. Using JSON:
// Array of String
items: ["item1", "item2"]
// Object Lieral
items: {
"key1": "item1",
"key2": "item2"
}
From the code posted the variable selections is already an array of strings, so the first option would be as simple as:
db.collection("movies").document(self.movieID).setData(["item": self.selections])
As for the second one, keep in mind that the keys of object literals must be unique so you'd need to derive these keys for each item somehow, construct the object and add it as the value of the document items key. For further reference on adding data in Swift check the documentation

Swift add a button that deletes row from list that uses a foreach loop

I have a list that has a foreach loop and in every row I want to make a delete button that deletes the row.
I have an array of structs
struct Lists{
var title : String
var id = UUID()
}
and the array
#State var list = [Lists]()
and this is the list
List{
ForEach(list, id : \.id ){ item in
NavigationLink(destination:DetailView(word:item.title)){
HStack {
Text(item.title)
Spacer()
Button(action: {
self.list.remove(at: 0)
}){
Text("×")
}
}
}
}
}
I have set the remove(at:0) zero because I don't know how to get the index in the for loop.
The list:
I usually think is easier to use a count number instead.. like this:
List {
ForEach(0..<list.count, id : \.self ){ index in
NavigationLink(destination:DetailView(word: list[index].title)){
HStack {
Text(list[index].title)
Spacer()
Button(action: {
self.list.remove(at: index)
}){
Text("×")
}
}
}
}
}

SwiftUI core data, grouped list fetch result

using core data im storing some airport and for every airport i'm storing different note
I have created the entity Airport and the entity Briefing
Airport have 1 attribute called icaoAPT and Briefing have 4 attribute category, descript, icaoAPT, noteID
On my detailsView I show the list all the noted related to that airport, I managed to have a dynamic fetch via another view called FilterList
import SwiftUI
import CoreData
struct FilterLIst: View {
var fetchRequest: FetchRequest<Briefing>
#Environment(\.managedObjectContext) var dbContext
init(filter: String) {
fetchRequest = FetchRequest<Briefing>(entity: Briefing.entity(), sortDescriptors: [], predicate: NSPredicate(format: "airportRel.icaoAPT == %#", filter))
}
func update(_ result : FetchedResults<Briefing>) ->[[Briefing]]{
return Dictionary(grouping: result) { (sequence : Briefing) in
sequence.category
}.values.map{$0}
}
var body: some View {
List{
ForEach(update(self.fetchRequest.wrappedValue), id: \.self) { (section : Briefing) in
Section(header: Text(section.category!)) {
ForEach(section, id: \.self) { note in
Text("hello")
/// Xcode error Cannot convert value of type 'Text' to closure result type '_'
}
}
}
}
}
}
on this view I'm try to display all the section divided by category using the func update...
but Xcode give me this error , I can't understand why..Cannot convert value of type 'Text' to closure result type '_'
fore reference I list below my detailsView
import SwiftUI
struct DeatailsView: View {
#Environment(\.managedObjectContext) var dbContext
#Environment(\.presentationMode) var presentation
#State var airport : Airport
#State var note = ""
#State var noteTitle = ["SAFTY NOTE", "TAXI NOTE", "CPNOTE"]
#State var notaTitleSelected : Int = 0
#State var notaID = ""
var body: some View {
Form{
Section(header: Text("ADD NOTE Section")) {
TextField("notaID", text: self.$notaID)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
TextField("add Note descrip", text: self.$note)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Picker(selection: $notaTitleSelected, label: Text("Class of Note")) {
ForEach(0 ..< noteTitle.count) {
Text(self.noteTitle[$0])
}
}
HStack{
Spacer()
Button(action: {
let nota = Briefing(context: self.dbContext)
nota.airportRel = self.airport
nota.icaoAPT = self.airport.icaoAPT
nota.descript = self.note
nota.category = self.noteTitle[self.notaTitleSelected]
nota.noteID = self.notaID
do {
try self.dbContext.save()
debugPrint("salvato notazione")
} catch {
print("errore nel salva")
}
}) {
Text("Salva NOTA")
}
Spacer()
}
}
Section(header: Text("View Note")) {
FilterLIst(filter: airport.icaoAPT ?? "NA")
}
}
}
}
thanks for the help
This is because you try to iterate over a single Briefing object and a ForEach loop expects a collection:
List {
ForEach(update(self.fetchRequest.wrappedValue), id: \.self) { (section: Briefing) in
Section(header: Text(section.category!)) {
ForEach(section, id: \.self) { note in // <- section is a single object
Text("hello")
/// Xcode error Cannot convert value of type 'Text' to closure result type '_'
}
}
}
}
I'd recommend you to extract the second ForEach to another method for clarity. This way you can also be sure you're passing the argument of right type ([Briefing]):
func categoryView(section: [Briefing]) -> some View {
ForEach(section, id: \.self) { briefing in
Text("hello")
}
}
Note that the result of your update method is of type [[Briefing]], which means the parameter in the ForEach is section: [Briefing] (and not Briefing):
var body: some View {
let data: [[Briefing]] = update(self.fetchRequest.wrappedValue)
return List {
ForEach(data, id: \.self) { (section: [Briefing]) in
Section(header: Text("")) { // <- can't be `section.category!`
self.categoryView(section: section)
}
}
}
}
This also means you can't write section.category! in the header as the section is an array.
You may need to access a Briefing object to get a category:
Text(section[0].category!)
(if you're sure the first element exists).
For clarity I specified types explicitly. It's also a good way to be sure you always use the right type.
let data: [[Briefing]] = update(self.fetchRequest.wrappedValue)
However, Swift can infer types automatically. In the example below, the data will be of type [[Briefing]]:
let data = update(self.fetchRequest.wrappedValue)

Add Button by id for SwiftUI inside ForEach using shorthand argument name $0

How to add Button inside the ForEach ? Works with Text but no luck with Button. Will like to make use of shorthand argument name $0
import SwiftUI
struct ContentView: View {
let emojiMoves = ["🗿", "🧻", "✂️"]
var body: some View {
HStack {
ForEach(emojiMoves, id: \.self) {
Text($0)
}
}
}
}
Something like this could work but I like the simplicity of $0
ForEach(0 ..< self.emojiMoves.count, id: \.self ) { number in
Button(action: {
self.emojiMoves[(number)]
})
}
You can try
ForEach(self.emojiMoves, id: \.self ) {
Button($0){
}
}