Slider reading Dictionary Keys SwiftUI - dictionary

How do I loop over my Dic keys on a slider? the slider behaivior is dividing the first key by the last key. what I want is only to loop throgh the keys value then somehow return the value's value...
struct ContentView: View {
#State private var currentValue = 0.0
#ObservedObject var scoreModel = ScoreModel()
var body: some View {
let endValue = Dictionary(uniqueKeysWithValues:
scoreModel.eventMDL.map { key, value in (key, value) })
return VStack {
Slider(value: $currentValue, in: endValue.keys.sorted()[1]...endValue.keys.sorted()[27])
.rotationEffect(Angle(degrees: -90))
Spacer()
Text("\(currentValue, specifier: "%.0f")")
}
.frame(height: 280)
}
}
The model:
#Published var eventMDL = [340.0: 100.0,
330.0: 97.0,
320.0: 94.0,
310.0: 92.0,
300.0: 90.0,
290.0: 88.0,
280.0: 86.0,
270.0: 84.0,
260.0: 82.0,
250.0: 80.0,
240.0: 78.0,
230.0: 76.0,
220.0: 74.0,
210.0: 72.0,
200.0: 70.0,
190.0: 68.0,
180.0: 65.0,
170.0: 64.0,
160.0: 63.0,
150.0: 62.0,
140.0: 60.0,
130.0: 50.0,
120.0: 40.0,
110.0: 30.0,
100.0: 20.0,
90.0: 10.0,
80.0: 0.0,
0.0 : 0.0]

First, I'd recommend creating an array with your keys and sort it. As you already use a #Published property I added updating this array in didSet:
class ScoreModel: ObservableObject {
#Published var eventMDL = [
340.0: 100.0,
...
0.0: 0.0,
] {
didSet {
eventMDLKeysSorted = eventMDL.keys.reversed().sorted()
}
}
lazy var eventMDLKeysSorted: [Double] = eventMDL.keys.reversed().sorted()
}
However, if the eventMDL dictionary is assigned only once, there's no need for it to be #Published:
class ScoreModel: ObservableObject {
let eventMDL = [
340.0: 100.0,
...
0.0: 0.0,
]
lazy var eventMDLKeysSorted: [Double] = eventMDL.keys.reversed().sorted()
}
Then keep track of current key instead of value:
#State private var currentKey = 0.0
Then create a range:
var firstKey: Double {
scoreModel.eventMDLKeysSorted[1]
}
var lastKey: Double {
scoreModel.eventMDLKeysSorted[scoreModel.eventMDLKeysSorted.count - 1]
}
and set it for the Slider adding a step of 10 (the difference between the subsequent keys):
Slider(value: $currentKey, in: firstKey...lastKey, step: 10)
Then we access the current value:
var currentValue: Double {
scoreModel.eventMDL[currentKey]!
// or provide a default value
// or make currentValue optional
}
Summing up this is how your ContentView might look like:
struct ContentView: View {
#ObservedObject var scoreModel = ScoreModel()
#State private var currentKey = 0.0
var firstKey: Double {
scoreModel.eventMDLKeysSorted[1]
}
var lastKey: Double {
scoreModel.eventMDLKeysSorted[scoreModel.eventMDLKeysSorted.count - 1]
}
var currentValue: Double {
return scoreModel.eventMDL[currentKey]!
}
var body: some View {
VStack {
Slider(value: $currentKey, in: firstKey...lastKey, step: 10)
.rotationEffect(Angle(degrees: -90))
Spacer()
Text("\(currentValue, specifier: "%.0f")")
}
.frame(height: 280)
}
}
Also you may want to set the Slider's start position:
init() {
_currentKey = .init(initialValue: scoreModel.eventMDLKeysSorted[1])
}

Related

Display data from firestore

I am trying to display the data I get back from the firestore in my swiftUI app but am having some trouble.
Here is where I get the data
import Foundation
import Firebase
class RecipesViewModel: ObservableObject {
#Published var userRecipes = [RecipesData]()
func getData() {
// Get a reference to the database
let db = Firestore.firestore()
// Read the documents at a specific path
db.collection("Recipes").getDocuments { snapshot, error in
// Check for errors
if error == nil {
// No errors
if let snapshot = snapshot {
// Update the list property in the main thread
DispatchQueue.main.async {
self.userRecipes = snapshot.documents.map { d in
return RecipesData(id: d["id"] as? String ?? "", name: d["name"] as? String ?? "", cuisine: d["cuisine"] as? String ?? "", difficulty: d["difficulty"] as? String ?? "", dishType: d["dishType"] as? String ?? "", prepMins: d["prepMins"] as? Int ?? 0, prepHours: d["prephours"] as? Int ?? 0, cookMins: d["cookMins"] as? Int ?? 0, cookHours: d["cookHours"] as? Int ?? 0, restMins: d["restMins"] as? Int ?? 0, restHours: d["restHours"] as? Int ?? 0, likes: d["lkies"] as? Int ?? 0)
}
}
}
}
else {
}
}
}
}
and here is where the data gets stored
import SwiftUI
import FirebaseFirestoreSwift
struct RecipesData: Identifiable{
var id: String
var name: String
var cuisine: String
var difficulty: String
var dishType: String
var prepMins: Int
var prepHours: Int
var cookMins: Int
var cookHours: Int
var restMins: Int
var restHours: Int
var likes: Int
}
I can get the Data and if I choose to display the name of each recipe in a list I can do that.
But what I want to do is to have my data too like this when being displayed
import SwiftUI
struct AllRecipesView: View {
#ObservedObject var model = RecipesViewModel()
private var gridCollum = [GridItem(.flexible(),spacing: 0), GridItem(.flexible(),spacing: 0)]
var body: some View {
VStack{
LazyVGrid(columns: gridCollum, spacing: 0){
ForEach(model.userRecipes) {item in
overlayView()
}
}
}
}
init(){
model.getData()
}
}
struct AllRecipesView_Previews: PreviewProvider {
static var previews: some View {
AllRecipesView()
}
}
struct overlayView:View{
#ObservedObject var model = RecipesViewModel()
var body: some View{
ForEach(model.userRecipes) {item in
VStack{
VStack{
Spacer()
HStack{
HStack{
Image(systemName: "star")
.foregroundColor(.white)
.font(.system(size: 20))
Text(item.likes)
.foregroundColor(.white)
.font(.system(size: 15))
}
.padding(.trailing)
Text(item.prepMins)
.foregroundColor(.white)
.font(.system(size: 15))
.padding(.horizontal)
}
.padding(.bottom)
}
.frame(width:180,height:130)
.background(Color.red)
.cornerRadius(8)
.shadow(color: .black, radius: 3, x: 2, y: 2)
.padding(.bottom)
Text("Salmon and Rice")
Text("Some User")
}
}
}
init(){
model.getData()
}
}
But I keep getting error saying No exact matches in call to initializer on the lines
Text(item.likes)
and
Text(prepMins)
How can I fix my errors please
try something like this example code, using #StateObject var model = RecipesViewModel() and passing it using #EnvironmentObject.
To fix the errors you get, remenber Text() needs a string, such as, Text(String(item.likes)) or Text("\(item.likes)").
struct AllRecipesView: View {
#StateObject var model = RecipesViewModel() // <-- here
private var gridCollum = [GridItem(.flexible(),spacing: 0), GridItem(.flexible(),spacing: 0)]
var body: some View {
VStack{
LazyVGrid(columns: gridCollum, spacing: 0){
ForEach(model.userRecipes) {item in
OverlayView()
}
}
}
.environmentObject(model) // <-- here
.onAppear {
model.getData() // <-- here
}
}
}
struct OverlayView:View{
#EnvironmentObject var model: RecipesViewModel // <-- here
var body: some View{
ForEach(model.userRecipes) {item in
VStack{
VStack{
Spacer()
HStack{
HStack{
Image(systemName: "star")
.foregroundColor(.white)
.font(.system(size: 20))
Text(String(item.likes)) // <-- here
.foregroundColor(.white)
.font(.system(size: 15))
}
.padding(.trailing)
Text(String(item.prepMins)) // <-- here
.foregroundColor(.white)
.font(.system(size: 15))
.padding(.horizontal)
}
.padding(.bottom)
}
.frame(width:180,height:130)
.background(Color.red)
.cornerRadius(8)
.shadow(color: .black, radius: 3, x: 2, y: 2)
.padding(.bottom)
Text("Salmon and Rice")
Text("Some User")
}
}
}
}

Show multiple users' location stored in a database on a map with SwiftUI and Firebase

As said in the title, I would like to show the location of my users on a map in my app. I would like something like Snapshat map :
Snapshat Map Picture
I used SwiftUI and Firebase.
Here is what I have done so far :
import SwiftUI
import Firebase
import CoreLocation
import MapKit
struct mapTimelineView: View {
#StateObject private var locationViewModel = LocationViewModel.shared
#State private var showNewPostView = false
#ObservedObject var viewModel = TimelineViewModel()
#ObservedObject var authViewModel = AuthViewModel()
#ObservedObject var obs = observer()
var body: some View {
ZStack (alignment: .bottomTrailing) {
// Map(coordinateRegion: $locationViewModel.region, showsUserLocation: true)
// .accentColor(Color("accentColor"))
// .edgesIgnoringSafeArea(.all)
mapView(geopoints: self.obs.data["data"] as! [String : GeoPoint]) <--- /!\ the problem occurs here /!\
Button {
showNewPostView.toggle()
} label: {
Image(systemName: "plus")
.resizable()
.renderingMode(.template)
.frame(width: 30, height: 30)
.font(.system(size: 30, weight: .bold, design: .default))
.padding()
}
.background(Color("accentColor"))
.foregroundColor(Color("backgroundColor"))
.clipShape(Circle())
.padding()
.shadow(radius: 20)
.fullScreenCover(isPresented: $showNewPostView) {
uploadPostView()
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("home")
.background(Color("backgroundColor"))
}
}
struct mapTimelineView_Previews: PreviewProvider {
static var previews: some View {
mapTimelineView()
}
}
struct mapView: UIViewRepresentable {
#ObservedObject var authViewModel = AuthViewModel()
var geopoints : [String: GeoPoint]
func makeCoordinator() -> Coordinator {
return mapView.Coordinator(parent1: self)
}
let map = MKMapView()
let manager = CLLocationManager()
func makeUIView(context: Context) -> MKMapView {
manager.delegate = context.coordinator
manager.startUpdatingLocation()
map.showsUserLocation = true
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 48.856614, longitude: 2.3522219), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
map.region = region
return map
}
func updateUIView(_ uiView: MKMapView, context: Context) {
for i in geopoints {
let point = MKPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude: i.value.latitude, longitude: i.value.longitude)
point.title = i.key
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotation(point)
}
}
class Coordinator: NSObject, CLLocationManagerDelegate {
#ObservedObject var authViewModel = AuthViewModel()
var parent: mapView
init(parent1: mapView) {
parent = parent1
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let uid = self.authViewModel.userSession?.uid else { return }
let last = locations.last
Firestore.firestore().collection("locations").document("coordinate").setData(["updates" : [uid : GeoPoint(latitude: (last?.coordinate.latitude)!, longitude: (last?.coordinate.longitude)!)]],merge: true) { (err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
print("success")
}
}
}
}
class observer : ObservableObject{
#Published var data = [String : Any]()
init() {
let db = Firestore.firestore()
db.collection("locations").document("coordinate").addSnapshotListener { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
let updates = snap?.get("updates") as! [String : GeoPoint]
self.data["data"] = updates
}
}
}
It shows a map and the user location.
But my problem is that my app crash because there is "nil" on this line :
mapView(geopoints: self.obs.data["data"] as! [String : GeoPoint])
I got this error message :
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
And, I don't understand this issue because I do have data in my database :
So, if anyone has a solution, I would like to know it.

swiftUI tabView pagetabviewstyle - button to next is not working

import SwiftUI
GuideImageView
currentPage mean 1VStack which is in text and images from array from guidelists
struct GuideImageView: View {
#State var currentPage: Int = 0
var body: some View {
VStack{
TabView(selection: $currentPage){
ForEach(guidelists){i in
VStack{
Text(i.explain)
Image(i.image)
.resizable()
}
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) //page처럼 구현 + ...을 안보이게함
Button("Next") {
if currentPage == 3 {
currentPage = 0
//return
}else{
currentPage += 1
}
}
}
}
}
Struct GuideList
struct GuideList: Identifiable, Hashable{//가이드리스트 구조체, 이미지와 설명넣기
let id = UUID() //UUID = 고유식별자
let image: String
let explain: String
}
let guidelists
let guidelists = [
GuideList(image: "image1",explain: "explain1." ),
GuideList(image: "image2",explain: "explain2." ),
GuideList(image: "image3",explain: "explain3." )
]
GuideImageView_Previews
struct ImageView_Previews: PreviewProvider {
static var previews: some View {
GuideImageView()
}
}
I want to make button to go to next page
button seems doesn't work
The reason for this not working is the type mismatch in your models id and the selection var.
Detail:
TabView(selection: $currentPage){
ForEach(guidelists){i in
these two lines tell the compiler that the id for every element is of type UUID (because GuideList is identifieable and id is of type UUID. Thats fine for itself, but TabView has a selection var of type Int (currentPage is an Int) so it is not working. So changing one of both types to equal the other will solve the problem.
easy example:
Change your code to:
struct GuideList: Identifiable, Hashable{//가이드리스트 구조체, 이미지와 설명넣기
let id: Int
let image: String
let explain: String
}
let guidelists = [
GuideList(id: 0, image: "image1",explain: "explain1."),
GuideList(id: 1, image: "image2",explain: "explain2." ),
GuideList(id: 2, image: "image3",explain: "explain3." )
]

Is it possible to create a LazyVGrid without the Lazy modifier?

I'm loading data in from my Firebase backend, the "lazy" part makes my app look glitchy/frozen-like when scrolling down, it lags heavily...
Is it possible to create a VGrid "without the lazy functionality"??
(iOS 14)
If not, any suggestions other than ditching the Grid look altogether?
let layout = [
GridItem(.flexible()),
GridItem(.flexible()),
]
#ObservedObject var homeModel = Home_ViewModel()
NavigationView(content: {
ScrollView() {
LazyVGrid(columns: layout, spacing: 10) {
ForEach(homeModel.projectList) { item in
ProjectItemWidget(
projectID: item.id,
projectTitle: item.projectTitle,
projectAuthorProfileImage: item.authorProfileImageUrl,
projectAuthor: item.projectAuthor)
}
}
.padding(.trailing, 7.5)
}
}
I was struggling thinking in a solution where a could create a grid layout without using LazyVGrid and came up with the following:
extension Array {
func getElementAt(index: Int) -> Element? {
return (index < self.endIndex) ? self[index] : nil
}
}
struct CustomGridLayout<Element, GridCell>: View where GridCell: View {
private var array: [Element]
private var numberOfColumns: Int
private var gridCell: (_ element: Element) -> GridCell
init(_ array: [Element], numberOfColumns: Int, #ViewBuilder gridCell: #escaping (_ element: Element) -> GridCell) {
self.array = array
self.numberOfColumns = numberOfColumns
self.gridCell = gridCell
}
var body: some View {
Grid {
ForEach(Array(stride(from: 0, to: self.array.count, by: self.numberOfColumns)), id: \.self) { index in
GridRow {
ForEach(0..<self.numberOfColumns, id: \.self) { j in
if let element = self.array.getElementAt(index: index + j) {
self.gridCell(element)
}
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
An example using in a View:
struct ContentView: View {
private var array: [Int] = Array(1...7)
var body: some View {
CustomGridLayout(array, numberOfColumns: 3) { element in
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.orange)
.overlay(alignment: .center) {
Text("\(element)")
}
}
.padding(.horizontal)
}
}
You can see the result in the following link: https://i.stack.imgur.com/1o7ip.png

swiftUI how to disabled a button for 2sec?

i have a bug, if i click on button before the animation before the card flip back. i think for me the best it would be to disable the button for 2 sec, but i made some research and didnt find anything!
struct CardBack: View {
var body: some View {
Image("back_card")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
}
}
struct ContentView: View {
#State var flipped = false
#State private var cardsFront = ["bigCard1", "bigCard2", "bigCard3", "bigCard4", "bigCard5" ]
#State private var cardBack = "back_card"
#State private var disablled = true
var body: some View {
VStack {
Spacer()
ZStack {
Image(flipped ? self.cardsFront.randomElement()! : self.cardBack)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
.rotation3DEffect(Angle(degrees: flipped ? 180 : 0 ), axis: (x: 0, y: 1, z: 0))
}
Spacer()
HStack {
Button(action: {
withAnimation(.spring()) {
self.flipped.toggle()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation(.spring()) {
self.flipped.toggle()
}
}
}) {
Image("circle")
.renderingMode(.original)
}
Button(action: {
}) {
Image("plus")
.renderingMode(.original)
}
iOS 13, Swift 5
You can set the button as disabled initially and then enable it using the same sort of logic I used here.
import SwiftUI
struct ContentView: View {
#State var changeColor = false
var body: some View {
TextView(changeColor: $changeColor)
}
}
struct TextView: View {
#Binding var changeColor: Bool
var body: some View {
Text("Hello World")
.foregroundColor(changeColor ? Color.black: Color.red)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.changeColor.toggle()
}
}
}
}
You are almost there, you just need to use the .appear tag in your code to do this.

Resources