SwiftUI Change icon by number - ios

I want the Images to change according to the text, but it works over .degisim String and int value in the ready-made code block I found. That's why it doesn't work for String.
import SwiftUI
struct altin: View {
#State var users: [Altin] = []
var body: some View {
ZStack{
List(users) { altin in
NavigationLink {
detayView(altinKur: altin)
} label: {
HStack{
Image(systemName: getImage(percent: Int(altin.degisim)))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 9, height: 9)
.foregroundColor(
(altin.degisim).contains("-") ?
Color.down :
Color.up)
.padding(.trailing, 5)
Text(altin.degisim)
.font(.system(size: 16))
.fontWeight(.medium)
.foregroundColor(
(altin.degisim).contains("-") ?
Color.down :
Color.up)
}
}
}
}
.listStyle(PlainListStyle())
.padding(0)
.onAppear {
apiCall().getUsers { (users) in
self.users = users
}
}
}
}
struct altin_Previews: PreviewProvider {
static var previews: some View {
altin()
}
}
func getImage(percent: Int) -> String{
if percent > 0 {
return "chevron.up"
}else{
return "chevron.down"
}
}
.foregroundcolor works but not within Image.
The code is more complex, I simplified it for solution.
Model:
struct Altin: Codable, Identifiable {
let id = UUID()
let name: String
let alis: String
let satis: String
let degisim: String
}

Related

Swiftui items get duplicated in all views when added to a single custom view

I'm struggling with the following issue: I'm trying to build a very simple app that lets you add items in a dedicated view that can be collapsed. I managed to write a simple function that lets me add multiple of these custom collapsable views. It's my first app so I wanted to follow the MVVM protocol. I think I got confused along the way because now every item I add gets automatically added to all the custom collapsable views I made. Is there any way to fix this? I thought using the UUID would solve this issue.. I'm guessing that I have to customise the "saveButtonPressed" function, but I don't know how to tell it to only add the item to the view where I pressed the "plus" button..
Here are the Models for the individual items and the collapsable view:
struct ItemModel: Identifiable, Equatable {
let id: String
let title: String
init(id: String = UUID().uuidString, title: String) {
self.id = id
self.title = title
}
func updateCompletion() -> ItemModel {
return ItemModel(id: id, title: title)
}
}
--
import Foundation
struct CollapsableItem: Equatable, Identifiable, Hashable {
let id: String
var title: String
init(id: String = UUID().uuidString, title: String) {
self.id = id
self.title = title
}
func updateCompletion() -> CollapsableItem {
return CollapsableItem(id: id, title: title)
}
}
These are my two ViewModels:
class ListViewModel: ObservableObject {
#Published var items: [ItemModel] = []
init() {
getItems()
}
func getItems() {
let newItems = [
ItemModel(title: "List Item1"),
ItemModel(title: "List Item2"),
ItemModel(title: "List Item3"),
]
items.append(contentsOf: newItems)
}
func addItem(title: String) {
let newItem = ItemModel(title: title)
items.append(newItem)
}
func updateItem(item: ItemModel) {
if let index = items.firstIndex(where: { $0.id == item.id}) {
items[index] = item.updateCompletion()
}
}
}
--
class CollapsedViewModel: ObservableObject {
#Published var collapsableItems: [CollapsableItem] = []
#Published var id = UUID().uuidString
init() {
getCollapsableItems()
}
func getCollapsableItems() {
let newCollapsableItems = [
CollapsableItem(title: "Wake up")
]
collapsableItems.append(contentsOf: newCollapsableItems)
}
func addCollapsableItem(title: String) {
let newCollapsableItem = CollapsableItem(title: title)
collapsableItems.append(newCollapsableItem)
}
func updateCollapsableItem(collapsableItem: CollapsableItem) {
if let index = collapsableItems.firstIndex(where: { $0.id ==
collapsableItem.id}) {
collapsableItems[index] =
collapsableItem.updateCompletion()
}
}
}
The item view:
struct ListRowView: View {
#EnvironmentObject var listViewModel: ListViewModel
let item: ItemModel
var body: some View {
HStack() {
Text(item.title)
.font(.body)
.fontWeight(.bold)
.foregroundColor(.white)
.multilineTextAlignment(.center)
.lineLimit(1)
.frame(width: 232, height: 16)
}
.padding( )
.frame(width: 396, height: 56)
.background(.gray)
.cornerRadius(12.0)
}
}
The collapsable view:
struct CollapsedView2<Content: View>: View {
#State var collapsableItem: CollapsableItem
#EnvironmentObject var collapsedViewModel: CollapsedViewModel
#State private var collapsed: Bool = true
#EnvironmentObject var listViewModel: ListViewModel
#State var label: () -> Text
#State var content: () -> Content
#State private var show = true
var body: some View {
ZStack{
VStack {
HStack{
Button(
action: { self.collapsed.toggle() },
label: {
HStack() {
Text("Hello")
.font(.title2.bold())
Spacer()
Image(systemName: self.collapsed ? "chevron.down" :
"chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
Button(action: saveButtonPressed, label: {
Image(systemName: "plus")
.font(.title2)
.foregroundColor(.white)
})
}
VStack {
self.content()
}
ForEach(listViewModel.items) { item in ListRowView (item: item)
}
.lineLimit(1)
.fixedSize(horizontal: true, vertical: true)
.frame(minWidth: 396, maxWidth: 396, minHeight: 0, maxHeight: collapsed ?
0 : .none)
.animation(.easeInOut(duration: 1.0), value: show)
.clipped()
.transition(.slide)
}
}
}
func saveButtonPressed() {
listViewModel.addItem(title: "Hello")
}
}
and finally the main view:
struct ListView: View {
#EnvironmentObject var listViewModel: ListViewModel
#EnvironmentObject var collapsedViewModel: CollapsedViewModel
var body: some View {
ZStack{
ScrollView{
VStack{
HStack{
Text("MyFirstApp")
.font(.largeTitle.bold())
Button(action: newCollapsablePressed, label: {
Image(systemName: "plus")
.font(.title2)
.foregroundColor(.white)
})
}
.padding()
.padding(.leading)
ForEach(collapsedViewModel.collapsableItems) { collapsableItem in
CollapsedView2 (collapsableItem: collapsableItem,
label: { Text("") .font(.title2.bold()) },
content: {
HStack {
Text("")
Spacer() }
.frame(maxWidth: .infinity)
})
}
.padding()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.statusBar(hidden: false)
.navigationBarHidden(true)
}
}
func newCollapsablePressed() {
collapsedViewModel.addCollapsableItem(title: "hello2")
}
}
Would love to understand how I could fix this!
There is the anwser for your comment about add item in each CollapsedView2.
Because ListViewModel is not ObservableObject (ListViewModel is difference from each CollapsableItem). You should use "#State var items: [ItemModel]".
struct CollapsedView2<Content: View>: View {
#State var collapsableItem: CollapsableItem
// #State var listViewModel = ListViewModel()
#State var collapsed: Bool = true
#State var label: () -> Text
#State var content: () -> Content
#State private var show = true
#State var items: [ItemModel] = []
#State var count = 1
var body: some View {
VStack {
HStack{
Text("Hello")
.font(.title2.bold())
Spacer()
Button( action: { self.collapsed.toggle() },
label: {
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
)
.buttonStyle(PlainButtonStyle())
Button(action: saveButtonPressed, label: {
Image(systemName: "plus")
.font(.title2)
// .foregroundColor(.white)
})
}
VStack {
self.content()
}
ForEach(items) { item in
ListRowView (item: item)
}
.lineLimit(1)
.fixedSize(horizontal: true, vertical: true)
.frame(minHeight: 0, maxHeight: collapsed ? 0 : .none)
.animation(.easeInOut(duration: 1.0), value: show)
.clipped()
.transition(.slide)
}
}
func saveButtonPressed() {
addItem(title: "Hello \(count)")
count += 1
}
func addItem(title: String) {
let newItem = ItemModel(title: title)
items.append(newItem)
}
func updateItem(item: ItemModel) {
if let index = items.firstIndex(where: { $0.id == item.id}) {
items[index] = item.updateCompletion()
}
}
}
There is the anwser. Ask me if you have some questions
struct ListView: View {
#StateObject var collapsedViewModel = CollapsedViewModel()
var body: some View {
ScrollView{
VStack{
HStack{
Text("MyFirstApp")
.font(.largeTitle.bold())
Button(action: newCollapsablePressed, label: {
Image(systemName: "plus")
.font(.title2)
// .foregroundColor(.white)
})
}
ForEach(collapsedViewModel.collapsableItems) { collapsableItem in
CollapsedView2 (collapsableItem: collapsableItem,
label: { Text("") .font(.title2.bold()) },
content: {
HStack {
Text("")
Spacer()
}
})
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.statusBar(hidden: false)
.navigationBarHidden(true)
}
func newCollapsablePressed() {
collapsedViewModel.addCollapsableItem(title: "hello2")
}
}
struct CollapsedView2<Content: View>: View {
#State var collapsableItem: CollapsableItem
#State var listViewModel = ListViewModel()
#State var collapsed: Bool = true
#State var label: () -> Text
#State var content: () -> Content
#State private var show = true
var body: some View {
VStack {
HStack{
Button( action: { self.collapsed.toggle() },
label: {
HStack() {
Text("Hello")
.font(.title2.bold())
Spacer()
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
Button(action: saveButtonPressed, label: {
Image(systemName: "plus")
.font(.title2)
.foregroundColor(.white)
})
}
VStack {
self.content()
}
ForEach(listViewModel.items) { item in
ListRowView (item: item)
}
.lineLimit(1)
.fixedSize(horizontal: true, vertical: true)
.frame(minHeight: 0, maxHeight: collapsed ? 0 : .none)
.animation(.easeInOut(duration: 1.0), value: show)
.clipped()
.transition(.slide)
}
}
func saveButtonPressed() {
listViewModel.addItem(title: "Hello")
}
}
struct ListRowView: View {
let item: ItemModel
var body: some View {
HStack() {
Text(item.title)
.font(.body)
.fontWeight(.bold)
.foregroundColor(.white)
.multilineTextAlignment(.center)
.lineLimit(1)
.frame(width: 232, height: 16)
}
.padding( )
.frame(width: 396, height: 56)
.background(.gray)
.cornerRadius(12.0)
}
}
class ListViewModel {
var items: [ItemModel] = []
init() {
getItems()
}
func getItems() {
let newItems = [
ItemModel(title: "List Item1"),
ItemModel(title: "List Item2"),
ItemModel(title: "List Item3"),
]
items.append(contentsOf: newItems)
}
func addItem(title: String) {
let newItem = ItemModel(title: title)
items.append(newItem)
}
func updateItem(item: ItemModel) {
if let index = items.firstIndex(where: { $0.id == item.id}) {
items[index] = item.updateCompletion()
}
}
}
class CollapsedViewModel: ObservableObject {
#Published var collapsableItems: [CollapsableItem] = []
#Published var id = UUID().uuidString
init() {
getCollapsableItems()
}
func getCollapsableItems() {
let newCollapsableItems = [
CollapsableItem(title: "Wake up")
]
collapsableItems.append(contentsOf: newCollapsableItems)
}
func addCollapsableItem(title: String) {
let newCollapsableItem = CollapsableItem(title: title)
collapsableItems.append(newCollapsableItem)
}
func updateCollapsableItem(collapsableItem: CollapsableItem) {
if let index = collapsableItems.firstIndex(where: { $0.id ==
collapsableItem.id}) {
collapsableItems[index] =
collapsableItem.updateCompletion()
}
}
}
struct CollapsableItem: Equatable, Identifiable, Hashable {
let id: String
var title: String
init(id: String = UUID().uuidString, title: String) {
self.id = id
self.title = title
}
func updateCompletion() -> CollapsableItem {
return CollapsableItem(id: id, title: title)
}
}
struct ItemModel: Identifiable, Equatable {
let id: String
let title: String
init(id: String = UUID().uuidString, title: String) {
self.id = id
self.title = title
}
func updateCompletion() -> ItemModel {
return ItemModel(id: id, title: title)
}
}

Trouble implementing UserDefaults

I have previous coding experience but I'm extremely new to both Swift and iOS. I'm developing an app for personal use to coalesce the functions of multiple different apps I use to help manage ADHD into one place. The main view of the app provides a way to track daily expenditures. I'm trying to use UserDefaults to store the information entered so it will still be there when I reopen the app. The app runs fine, and as near as I can figure out I've written the data handling right, but it simply doesn't work. I've been beating my head against the wall at this for the past few days including on this website, so any help would be greatly appreciated. Here's the code for the main view of the app:
import SwiftUIFontIcon
import SwiftUI
//import UIKit
struct ContentView: View {
#State public var purchases = [Purchases]()
#State public var prices = [Price]()
#State public var isActive = false
#State public var goTo: String = ""
#State public var purchase: String = ""
#State public var price: String = ""
// #State public var isActive: Bool = false
init(){
if let data = UserDefaults.standard.data(forKey: "Purchases"){
if let decoded = try? JSONDecoder().decode([Purchases].self, from: data){
self.purchases = decoded
}
return
}
self.purchases = []
if let data2 = UserDefaults.standard.data(forKey: "Bread"){
if let decoded2 = try? JSONDecoder().decode([Price].self, from: data2){
self.prices = decoded2
}
return
}
self.prices = []
}
func addItem(){
saveStuff()
self.purchases.append(Purchases(name: purchase))
saveStuff()
purchase = ""
}
func addPrice(){
saveBread()
self.prices.append(Price(name: price))
saveBread()
price = ""
}
func deleteItem(at offsets: IndexSet){
purchases.remove(atOffsets: offsets)
}
func deletePrice(at offsets: IndexSet){
prices.remove(atOffsets: offsets)
}
func saveStuff(){
if let encodedData = try? JSONEncoder().encode(purchases){
UserDefaults.standard.set(encodedData, forKey: "Purchases")
}
// return
}
func saveBread(){
if let encodedData = try? JSONEncoder().encode(prices){
UserDefaults.standard.set(encodedData, forKey: "Bread")
}
// return
}
func clearList(){
self.prices.removeAll()
self.purchases.removeAll()
}
var body: some View {
NavigationView{
ZStack {
Color.gray
.edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
// Spacer()
// HStack {
// // Spacer()
// NavigationLink(
// destination: ToDoList(rootIsActive: self.$isActive),
// isActive: self.$isActive
//
// ){
// FontIcon.text(.ionicon(code: .ios_list_box), fontsize: 48, color: .black)
// }
// Spacer()
// NavigationLink(destination: ReminderView()){
// FontIcon.text(.ionicon(code: .ios_warning), fontsize: 48, color: .black)
// }
// // Spacer()
// }
Spacer()
VStack{
Spacer()
HStack {
Spacer()
HStack {
Spacer()
TextField("Add an Item", text: $purchase)
.padding(12)
.border(Color.black)
Spacer()
Spacer()
Spacer()
TextField("Add a Price", text: $price)
.padding(12)
.border(Color.black)
// Spacer()
FontIcon.button(.ionicon(code: .ios_add_circle), action: {
addItem()
addPrice()
saveStuff()
saveBread()
}, padding: 12, fontsize: 45, color: .black)
}
.opacity(1)
.padding(12)
// .border(Color.black)
// Spacer()
}
Spacer()
VStack {
HStack {
Spacer()
List{
ForEach(purchases){ purchase in
Text(purchase.name)
.cornerRadius(16)
.padding(10)
}
.onDelete(perform: deleteItem)
// .listStyle(GroupedListStyle())
}
Spacer()
List{
ForEach(prices){ price in
Text(price.name)
.cornerRadius(16)
.padding(10)
}
.onDelete(perform: deletePrice)
// .onAppear{UITableView.appearance().separatorColor = .clear}
}
Spacer()
}
Spacer()
HStack {
// Spacer()
// FontIcon.button(.ionicon(code: .ios_save), action: {
// saveStuff()
// saveBread()
// }, padding: 12, fontsize: 78, color: .green)
Spacer()
FontIcon.button(.ionicon(code: .ios_trash),action:{
clearList()
}, padding: 12, fontsize: 78, color: .red)
Spacer()
NavigationLink(destination: ViewLists()){
FontIcon.text(.ionicon(code: .ios_filing), fontsize: 58, color: .black)
}
Spacer()
NavigationLink(destination: ViewTotals()){
FontIcon.text(.ionicon(code: .ios_add), fontsize: 58, color: .black)
}
Spacer()
NavigationLink(destination: IncomeView()){
FontIcon.text(.ionicon(code: .ios_musical_note), fontsize: 58, color: .black)
}
Spacer()
}
// Spacer()
}
Spacer()
}
Spacer()
}
.navigationBarTitle("ADD ToolKit", displayMode: .large)
Spacer()
// .navigationBarTitle("Ledger", displayMode: .large)
// Spacer()
}
.toolbar{
ToolbarItem(placement: .primaryAction){
// Spacer()
// Spacer()
HStack {
Spacer()
NavigationLink(destination: ReminderView()){
FontIcon.text(.ionicon(code: .ios_warning), fontsize: 48, color: .black)
// Spacer()
}
}
}
ToolbarItem(placement: .navigationBarLeading){
HStack {
Spacer()
NavigationLink(
destination: ToDoList(rootIsActive: self.$isActive),
isActive: self.$isActive
){
FontIcon.text(.ionicon(code: .ios_list_box), fontsize: 48, color: .black)
}
}
}
}
//
}
.background(
NavigationLink(destination: Text(self.goTo), isActive: $isActive){
EmptyView()
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and the code for the model where I define all the array structs:
import Foundation
import SwiftUI
struct Purchases: Identifiable, Codable{
let id: String
let name: String
init(id: String = UUID().uuidString, name: String){
self.id = id
self.name = name
}
}
struct Price: Identifiable, Codable{
let id: String
let name: String
init(id: String = UUID().uuidString, name: String){
self.id = id
self.name = name
}
}
struct ToDo: Identifiable, Codable{
let id: String
let name: String
init(id: String = UUID().uuidString, name: String){
self.id = id
self.name = name
}
}
struct Reminder: Identifiable, Codable{
let id: String
let name: String
init(id: String = UUID().uuidString, name: String){
self.id = id
self.name = name
}
}
struct Income: Identifiable, Codable{
let id: String
let name: String
init(id: String = UUID().uuidString, name: String){
self.id = id
self.name = name
}
}
Thanks in advance
Add the below code to a .swift file in your project
//Allows all Codable Arrays to be saved using AppStorage
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode([Element].self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}
Then use #AppStorage vs #State for the arrays you want saved
#AppStorage("Purchases") var purchases: [Purchases] = []
#AppStorage("Bread") var breadPrices: [Price] = []
You can write to them as you would to a regular array
You don't need saveStuff or saveBread
Below is a simplified version of your code. I couldn't reproduce yours to test.
import SwiftUI
struct CodableUserDefaultView: View {
#AppStorage("Purchases") var purchases: [Purchases] = []
//#State public var purchases = [Purchases]()
#AppStorage("Bread") var breadPrices: [Price] = []
//#State public var breadPrices = [Price]()
#State public var isActive = false
#State public var goTo: String = ""
#State public var price: String = ""
func addItem(purchaes: Purchases){
self.purchases.append(purchaes)
}
func addPrice(price: Price){
self.breadPrices.append(price)
}
func deleteItem(at offsets: IndexSet){
purchases.remove(atOffsets: offsets)
}
func deletePrice(at offsets: IndexSet){
breadPrices.remove(atOffsets: offsets)
}
func clearList(){
self.breadPrices.removeAll()
self.purchases.removeAll()
}
var body: some View {
List{
Section(content: {
ForEach(breadPrices){ price in
HStack{
Text(price.name)
Spacer()
Button("purchase", action: {
addItem(purchaes: Purchases(name: price.name))
})
}
}.onDelete(perform: deletePrice)
VStack{
Text("Bread")
TextField("Bread Price", text: $price, onCommit: {
addPrice(price: Price(name: price))
})
}
}, header: {
Text("Bread")
})
Section(content: {
ForEach(purchases){ purchase in
HStack{
Text(purchase.name)
}
}.onDelete(perform: deleteItem)
}, header: {
Text("Purchases")
})
}
}
}
struct CodableUserDefaultView_Previews: PreviewProvider {
static var previews: some View {
CodableUserDefaultView()
}
}
But as mentioned in the comments this really isn't a good use for UserDefaults. It is meant for smaller stuff.
The defaults system allows an app to customize its behavior to match a user’s preferences. For example, you can allow users to specify their preferred units of measurement or media playback speed. Apps store these preferences by assigning values to a set of parameters in a user’s defaults database.
https://developer.apple.com/documentation/foundation/userdefaults
You might want to look into Core Data if you are staying iOS only or another Database system like Firebase, AWS, Azure, etc.

SwiftUI - How to implement "hide when scrolling" action to Search Bar (Xcode 12.1 - iOS 14)

I tried looking for an answer to this problem, but all of the solutions I found either were with UIKit, or needed me to add something to the AppDelegate/SceneDelegate. Since I'm using SwiftUI and Xcode 12.1 (that doesn't have AppDelegate and SceneDelegate anymore), I'm here asking this question:
How can I make this SearchBar disappear when user scrolls down (and leave it hidden until he/she scrolls down)?
Right now it works fine (even though I don't really like the animation of the cancel Button), and it look like this.
I have Search.swift like this:
import SwiftUI
struct Search: View {
#Binding var text: String
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search ...", text: $text)
.padding(7)
.padding(.horizontal, 25)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
)
.padding(.horizontal, 10)
.onTapGesture {
self.isEditing = true
}
if isEditing {
Button(action: {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
self.isEditing = false
self.text = ""
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
.animation(.default)
}
}
}
}
Then in my Content View I have this:
import SwiftUI
struct ContentView: View {
#ObservedObject var dm : DataManager
#State var searchText = ""
#State private var isEditing = false
var body: some View {
NavigationView {
VStack {
Search(text: $searchText)
.padding(.horizontal, 10)
Form {
ForEach(dm.storageValues.filter({ searchText.isEmpty ? true : $0.valueName.contains(searchText) })) { data in
Text(data.valueName)
}
}
.navigationBarTitle("Example")
}
}
}
}
I don't know if they can be useful, but the DataManager is like this:
import SwiftUI
import Combine
class DataManager : Equatable, Identifiable, ObservableObject {
static let shared = DataManager()
#Published var storageValues : [ValueModel] = []
typealias StorageValues = [ValueModel]
//The rest of the code
}
With the ValueModel that is like this:
import SwiftUI
import Combine
class ValueModel : Codable, Identifiable, Equatable, ObservableObject, Comparable {
var id = UUID()
var valueName : String
var notes : String?
var expires : Date?
init(valueName: String, notes: String?, expires: Date?) {
self.valueName = valueName
self.notes = notes
self.expires = expires
}
}
For the SearchBar I followed this guide
Thanks to everyone who will help me!

SwiftUI: How to select multi items(image) with ForEach?

I'm working on my project with the feature of select multiple blocks of thumbnails. Only selected thumbnail(s)/image will be highlighted.
For the ChildView, The binding activeBlock should be turned true/false if a use taps on the image.
However, when I select a thumbnail, all thumbnails will be highlighted.I have come up with some ideas like
#State var selectedBlocks:[Bool]
// which should contain wether or not a certain block is selected.
But I don't know how to implement it.
Here are my codes:
ChildView
#Binding var activeBlock:Bool
var thumbnail: String
var body: some View {
VStack {
ZStack {
Image(thumbnail)
.resizable()
.frame(width: 80, height: 80)
.background(Color.black)
.cornerRadius(10)
if activeBlock {
RoundedRectangle(cornerRadius: 10)
.stroke(style: StrokeStyle(lineWidth: 2))
.frame(width: 80, height: 80)
.foregroundColor(Color("orange"))
}
}
}
BlockBView
struct VideoData: Identifiable{
var id = UUID()
var thumbnails: String
}
struct BlockView: View {
var videos:[VideoData] = [
VideoData(thumbnails: "test"), VideoData(thumbnails: "test2"), VideoData(thumbnails: "test1")
]
#State var activeBlock = false
var body: some View {
ScrollView(.horizontal){
HStack {
ForEach(0..<videos.count) { _ in
Button(action: {
self.activeBlock.toggle()
}, label: {
ChildView(activeBlock: $activeBlock, thumbnail: "test")
})
}
}
}
}
Thank you for your help!
Here is a demo of possible approach - we initialize array of Bool by videos count and pass activated flag by index into child view.
Tested with Xcode 12.1 / iOS 14.1 (with some replicated code)
struct BlockView: View {
var videos:[VideoData] = [
VideoData(thumbnails: "flag-1"), VideoData(thumbnails: "flag-2"), VideoData(thumbnails: "flag-3")
]
#State private var activeBlocks: [Bool] // << declare
init() {
// initialize state with needed count of bools
self._activeBlocks = State(initialValue: Array(repeating: false, count: videos.count))
}
var body: some View {
ScrollView(.horizontal){
HStack {
ForEach(videos.indices, id: \.self) { i in
Button(action: {
self.activeBlocks[i].toggle() // << here !!
}, label: {
ChildView(activeBlock: activeBlocks[i], // << here !!
thumbnail: videos[i].thumbnails)
})
}
}
}
}
}
struct ChildView: View {
var activeBlock:Bool // << value, no binding needed
var thumbnail: String
var body: some View {
VStack {
ZStack {
Image(thumbnail)
.resizable()
.frame(width: 80, height: 80)
.background(Color.black)
.cornerRadius(10)
if activeBlock {
RoundedRectangle(cornerRadius: 10)
.stroke(style: StrokeStyle(lineWidth: 2))
.frame(width: 80, height: 80)
.foregroundColor(Color.orange)
}
}
}
}
}
Final result
Build your element and it's model first. I'm using MVVM,
class RowModel : ObservableObject, Identifiable {
#Published var isSelected = false
#Published var thumnailIcon: String
#Published var name: String
var id : String
var cancellables = Set<AnyCancellable>()
init(id: String, name: String, icon: String) {
self.id = id
self.name = name
self.thumnailIcon = icon
}
}
//Equivalent to your BlockView
struct Row : View {
#ObservedObject var model: RowModel
var body: some View {
GroupBox(label:
Label(model.name, systemImage: model.thumnailIcon)
.foregroundColor(model.isSelected ? Color.orange : .gray)
) {
HStack {
Capsule()
.fill(model.isSelected ? Color.orange : .gray)
.onTapGesture {
model.isSelected = !model.isSelected
}
//Two way binding
Toggle("", isOn: $model.isSelected)
}
}.animation(.spring())
}
}
Prepare data and handle action in your parent view
struct ContentView: View {
private let layout = [GridItem(.flexible()),GridItem(.flexible())]
#ObservedObject var model = ContentModel()
var body: some View {
VStack {
ScrollView {
LazyVGrid(columns: layout) {
ForEach(model.rowModels) { model in
Row(model: model)
}
}
}
if model.selected.count > 0 {
HStack {
Text(model.selected.joined(separator: ", "))
Spacer()
Button(action: {
model.clearSelection()
}, label: {
Text("Clear")
})
}
}
}
.padding()
.onAppear(perform: prepare)
}
func prepare() {
model.prepare()
}
}
class ContentModel: ObservableObject {
#Published var rowModels = [RowModel]()
//I'm handling by ID for futher use
//But you can convert to your Array of Boolean
#Published var selected = Set<String>()
func prepare() {
for i in 0..<20 {
let row = RowModel(id: "\(i)", name: "Block \(i)", icon: "heart.fill")
row.$isSelected
.removeDuplicates()
.receive(on: RunLoop.main)
.sink(receiveValue: { [weak self] selected in
guard let `self` = self else { return }
print(selected)
if selected {
self.selected.insert(row.name)
}else{
self.selected.remove(row.name)
}
}).store(in: &row.cancellables)
rowModels.append(row)
}
}
func clearSelection() {
for r in rowModels {
r.isSelected = false
}
}
}
Don't forget to import Combine framework.

Update view bugs when data in firebase changes

This is when I launch the app and it looks okThis is when I change some data from firebase, as you can see some data is not arranged in the right way and some rectangles are doubleI'm currently working on this project where I need do read data from Firebase and update my views from the changes. Every time my data changes on firebase my views bugs in strange ways.
This is my Student class
struct Student : Identifiable {
var id : String
var name : String
var surname : String
var isAbsent : Bool
var isBathroom : Bool
}
Here where I get my data from firebase and publish to get all other views the data
class getStudents: ObservableObject {
#Published var datas = [Student]()
init() {
let db = Firestore.firestore()
let classRef = db.collection("5SA")
classRef.order(by: "surname").addSnapshotListener {(snap,error) in
if error != nil{
print((error?.localizedDescription)!)
return
}
for i in snap!.documentChanges{
let id = i.document.documentID
let name = i.document.get("name")as! String
let surname = i.document.get("surname") as! String
let isAbsent = i.document.get("absent") as! Bool
let isBathroom = i.document.get("bathroom") as! Bool
self.datas.append(Student(id: id, name: name, surname: surname, isAbsent: isAbsent, isBathroom: isBathroom))
if i.type == .modified{
let isAbsent = i.document.get("absent") as! Bool
let isBathroom = i.document.get("bathroom") as! Bool
for j in 0..<self.datas.count{
if self.datas[j].id == id{
self.datas[j].isAbsent = isAbsent
self.datas[j].isBathroom = isBathroom
}
}
}
}
}
}
}
This are my views. Apart from the landscape on the iPad simulator the bugs every time.
What I'm trying to achieve is creating like a class app. Where there are students absent, in the toilet or present. I want to to show every student with a rectangle with his name on it and to change color of the rectangles every time the bools on my fire base changes(isAbsent,isBathroom). But when I try to change value on firebase the rectangles multiplies and do strange thinks.
struct ContentView: View {
#ObservedObject var students = getStudents()
var body: some View {
NavigationView{
VStack{
ScrollView{
Text("Menu")
Menu().background(Color.white)
Text("Future verifiche")
HStack{
ScrollView(.horizontal, showsIndicators: false){
RoundedRectangle(cornerRadius: 20).frame(width:300,height: 300).foregroundColor(.white).overlay(Text("V. Matematica"))
}
}
Text("Oggi")
HStack{
Spacer()
Text("Assenti ")
Spacer()
Text("In bagno")
Spacer()
Text("Presenti")
Spacer()
}
HStack{
Spacer()
ScrollView{
ForEach(students.datas,id: \.id){student in
VStack(spacing: 15){
if student.isAbsent{
RoundedRectangle(cornerRadius: 20).frame(width:200,height: 100).foregroundColor(self.getColor(isAbsent: student.isAbsent, isBathroom: student.isBathroom)).overlay(Text(student.name))
}
}
}
}
Spacer()
ScrollView{
ForEach(students.datas,id: \.id){student in
VStack(spacing: 15){
if student.isBathroom{
RoundedRectangle(cornerRadius: 20).frame(width:200,height: 100).foregroundColor(self.getColor(isAbsent: student.isAbsent, isBathroom: student.isBathroom)).overlay(Text(student.name))
}
}
}
}
Spacer()
ScrollView{
ForEach(students.datas,id: \.id){student in
VStack(spacing: 15){
if !student.isAbsent && !student.isBathroom{
RoundedRectangle(cornerRadius: 20).frame(width:200,height: 100).foregroundColor(self.getColor(isAbsent: student.isAbsent, isBathroom: student.isBathroom)).overlay(Text(student.name))
}
}
}
}
Spacer()
}
}
}.navigationBarTitle("Classe 5SA",displayMode: .inline)
}.navigationViewStyle(StackNavigationViewStyle())
}
func getColor(isAbsent: Bool, isBathroom : Bool) -> Color {
if isAbsent{
return Color.red
}
if isBathroom{
return Color.blue
}
return Color.green
}
}
struct Menu : View {
var body : some View{
HStack{
NavigationLink(destination: Studenti()){
RoundedRectangle(cornerRadius: 30).frame(width: 150,height: 150).foregroundColor(.red).opacity(0.5).overlay(Text("Studenti").foregroundColor(.black))
}
NavigationLink(destination: Text("Overview")){
RoundedRectangle(cornerRadius: 30).frame(width: 150,height: 150).foregroundColor(.green).opacity(0.5).overlay(Text("Overview").foregroundColor(.black))
}
NavigationLink(destination: Text("Orario")){
RoundedRectangle(cornerRadius: 30).frame(width: 150,height: 150).foregroundColor(.blue).opacity(0.5).overlay(Text("Orario").foregroundColor(.black))
}
NavigationLink(destination: Text("Calendario")){
RoundedRectangle(cornerRadius: 30).frame(width: 150,height: 150).foregroundColor(.orange).opacity(0.5).overlay(Text("Calendario").foregroundColor(.black))
}
}
}
}
struct Studenti : View {
#ObservedObject var students = getStudents()
var body : some View {
ForEach(students.datas,id: \.id){student in
VStack{
Rectangle().frame(width:300,height: 150).foregroundColor(self.getColor(isAbsent: student.isAbsent, isBathroom: student.isBathroom)).overlay(Text(student.name))
}
}
}
func getColor(isAbsent: Bool, isBathroom : Bool) -> Color {
if isAbsent{
return Color.red
}
if isBathroom{
return Color.blue
}
return Color.green
}
}

Resources