I'm learning SwiftUI and tried to implement the MVVM architecture. The idea is simple, I tried to add photo to a list which it changes based on the weekday selected. The photos are locally saved.
However When I used the MVVM architecture, it complicated the whole situation. Specially if you want to save each schedule of the weekday separately. Because each weekday is a separate array. I'm sure there is a much easier way to do the code below. But I didn't figure it out.
The model :
import Foundation
struct Activity: Identifiable, Codable {
var id = UUID()
var image: String
var name: String
}
Viewmodel :
import Foundation
import UIKit
class Activities: ObservableObject {
//MARK:- PROPERTIES:
var indoorActivities = [Activity]()
var outdoorActivities = [Activity]()
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path
//Use it as example in RowView preview
var exampleAct: [Activity] {
return [indoorActivities[0], indoorActivities[1]]
}
@Published var sundayActivities = [Activity]() {
didSet {
print("Change hapeend to sundayAcitivty")
let encoder = JSONEncoder()
if let data = try? encoder.encode(sundayActivities) {
UserDefaults.standard.set(data, forKey: "sunday")
}
}
}
@Published var mondayActivities = [Activity]() {
didSet {
let encoder = JSONEncoder()
if let data = try? encoder.encode(mondayActivities) {
UserDefaults.standard.set(data, forKey: "monday")
}
}
}
@Published var tuesdayActivities = [Activity]() {
didSet {
let encoder = JSONEncoder()
if let data = try? encoder.encode(tuesdayActivities) {
UserDefaults.standard.set(data, forKey: "tuesday")
}
}
}
@Published var wednesdayActivities = [Activity]() {
didSet {
let encoder = JSONEncoder()
if let data = try? encoder.encode(wednesdayActivities) {
UserDefaults.standard.set(data, forKey: "wednesday")
}
}
}
@Published var thursdayActivities = [Activity]() {
didSet {
let encoder = JSONEncoder()
if let data = try? encoder.encode(thursdayActivities) {
UserDefaults.standard.set(data, forKey: "thursday")
}
}
}
@Published var fridayActivities = [Activity]() {
didSet {
let encoder = JSONEncoder()
if let data = try? encoder.encode(fridayActivities) {
UserDefaults.standard.set(data, forKey: "friday")
}
}
}
@Published var saturdayActivities = [Activity]() {
didSet {
let encoder = JSONEncoder()
if let data = try? encoder.encode(saturdayActivities) {
UserDefaults.standard.set(data, forKey: "saturday")
}
}
}
//MARK:- INIT:
init() {
if let data = UserDefaults.standard.data(forKey: "sunday") {
let decoder = JSONDecoder()
if let sunday = try? decoder.decode([Activity].self, from: data) {
self.sundayActivities = sunday
}
} else {
self.sundayActivities = []
}
if let data = UserDefaults.standard.data(forKey: "monday") {
let decoder = JSONDecoder()
if let monday = try? decoder.decode([Activity].self, from: data) {
self.mondayActivities = monday
}
} else {
self.mondayActivities = []
}
if let data = UserDefaults.standard.data(forKey: "tuesday") {
let decoder = JSONDecoder()
if let monday = try? decoder.decode([Activity].self, from: data) {
self.tuesdayActivities = monday
}
} else {
self.tuesdayActivities = []
}
if let data = UserDefaults.standard.data(forKey: "wednesday") {
let decoder = JSONDecoder()
if let monday = try? decoder.decode([Activity].self, from: data) {
self.wednesdayActivities = monday
}
} else {
self.wednesdayActivities = []
}
if let data = UserDefaults.standard.data(forKey: "thursday") {
let decoder = JSONDecoder()
if let monday = try? decoder.decode([Activity].self, from: data) {
self.thursdayActivities = monday
}
} else {
self.thursdayActivities = []
}
if let data = UserDefaults.standard.data(forKey: "friday") {
let decoder = JSONDecoder()
if let monday = try? decoder.decode([Activity].self, from: data) {
self.fridayActivities = monday
}
} else {
self.fridayActivities = []
}
if let data = UserDefaults.standard.data(forKey: "saturday") {
let decoder = JSONDecoder()
if let monday = try? decoder.decode([Activity].self, from: data) {
self.saturdayActivities = monday
}
} else {
self.saturdayActivities = []
}
if let urls = Bundle.main.urls(forResourcesWithExtension: "jpg", subdirectory: "/activities/indoorActivities") {
for url in urls {
let path = "//activities/indoorActivities" + "/\(url.lastPathComponent)"
let name = url.deletingPathExtension().lastPathComponent
let activity = Activity(image: path, name: name)
indoorActivities.append(activity)
}
}
//Get the documnet directory
// attach the image path to the document direcotroy
// assign it to image
if let urls = Bundle.main.urls(forResourcesWithExtension: "jpg", subdirectory: "/activities/outdoorActivities") {
for url in urls {
let path = "//activities/outdoorActivities" + "/\(url.lastPathComponent)"
let name = url.deletingPathExtension().lastPathComponent
let activity = Activity(image: path, name: name)
outdoorActivities.append(activity)
}
}
}
// Get UIImage from a url path
class func getImage(image: String) -> UIImage {
let bundle = Bundle.main.bundlePath
if let uiImage = UIImage(contentsOfFile: bundle + image) {
return uiImage
}
return UIImage(systemName: "circle.fill")!
}
}
Views :
DailyLifeView :
import SwiftUI
struct DailyLifeView: View {
@EnvironmentObject var activities: Activities
@State private var weekDay = "Sun"
@State private var showActivites = false
@State private var showDeleteAllButton = false
var weekDays = ["Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"]
var body: some View {
NavigationView {
ZStack {
VStack (spacing: 20){
Text("Choose the daily activities for your child")
.font(.headline)
.fontWeight(.bold)
.foregroundColor(.secondary)
Picker("Weeks", selection: $weekDay) {
ForEach(weekDays, id: \.self) {
Text($0)
}
}
.pickerStyle(SegmentedPickerStyle())
if showDeleteAllButton {
Button(action: {
withAnimation {
deleteAll()
}
}, label: {
HStack {
Text("Delete All")
Image(systemName: "trash")
}
.font(.headline)
.foregroundColor(.white)
})
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))
.background(Color.red)
.cornerRadius(10)
.transition(.scale)
}
List {
switch weekDay {
case "Sun":
RowView(activities: $activities.sundayActivities)
case "Mon":
RowView(activities: $activities.mondayActivities)
case "Tue":
RowView(activities: $activities.tuesdayActivities)
case "Wed":
RowView(activities: $activities.wednesdayActivities)
case "Thur":
RowView(activities: $activities.thursdayActivities)
case "Fri":
RowView(activities: $activities.fridayActivities)
case "Sat":
RowView(activities: $activities.saturdayActivities)
default:
RowView(activities: $activities.sundayActivities)
}
}
.listStyle(InsetListStyle())
Spacer()
}
.padding()
VStack{
Spacer()
HStack{
Spacer()
Button(action: {showActivites.toggle()}, label: {
Text("+")
.font(.system(.largeTitle))
.frame(width: 77, height: 70)
.foregroundColor(Color.white)
.padding(.bottom, 7)
})
.background(Color(hex: "64B5F6"))
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 3,
x: 3,
y: 3)
.sheet(isPresented: $showActivites, content: {
ActivitiesList(weekDay: self.weekDay)
})
}
}
}
.navigationViewStyle(DefaultNavigationViewStyle())
.navigationBarTitle("Daily Life")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
trailing: Button(action: {
withAnimation {
showDeleteAllButton.toggle()
}
}, label: {
Text(showDeleteAllButton ? "Done" : "Edit")
})
)
}
}
func deleteAll() {
switch weekDay {
case "Sun":
activities.sundayActivities.removeAll()
case "Mon":
activities.mondayActivities.removeAll()
case "Tue":
activities.tuesdayActivities.removeAll()
case "Wed":
activities.wednesdayActivities.removeAll()
case "Thur":
activities.thursdayActivities.removeAll()
case "Fri":
activities.fridayActivities.removeAll()
case "Sat":
activities.saturdayActivities.removeAll()
default:
activities.sundayActivities.removeAll()
}
}
}
ActivitiesList :
import SwiftUI
struct ActivitiesList: View {
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var activities: Activities
var weekDays = ["Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"]
var weekDay: String = "Sun"
let columns = [
GridItem(.adaptive(minimum: 100), spacing: 20)
]
var body: some View {
ScrollView {
LazyVGrid(
columns: columns,
spacing: 30
// pinnedViews: [.sectionHeaders]
) {
Section(
header: Text("INDOOR ACTIVITIES")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
) {
ForEach(activities.indoorActivities) { activity in
Button(action: {
self.selectWeekDay(activity: activity)
print(activity.name)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Image(uiImage: Activities.getImage(image: activity.image))
.resizable()
.scaledToFit()
.cornerRadius(10)
//
})
}
}
Section(
header: Text("OUTDOOR ACTIVITIES")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
) {
ForEach(activities.outdoorActivities) { activity in
Button(action: {
self.activities.sundayActivities.append(activity)
print(activity.name)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Image(uiImage: Activities.getImage(image: activity.image))
.resizable()
.scaledToFit()
.cornerRadius(10)
})
}
}
}
.padding()
}
.background(Color(hex: "90CAF9"))
}
//MARK: - FUNCTIONS:
func selectWeekDay(activity: Activity) {
switch weekDay {
case "Sun":
self.activities.sundayActivities.append(activity)
case "Mon":
self.activities.mondayActivities.append(activity)
case "Tue":
self.activities.tuesdayActivities.append(activity)
case "Wed":
self.activities.wednesdayActivities.append(activity)
case "Thur":
self.activities.thursdayActivities.append(activity)
case "Fri":
self.activities.fridayActivities.append(activity)
case "Sat":
self.activities.saturdayActivities.append(activity)
default:
self.activities.sundayActivities.append(activity)
}
}
}
RowView:
import SwiftUI
import AVFoundation
struct RowView: View {
@Binding var activities: [Activity]
var body: some View {
ForEach(activities) { activity in
Button(action: {
let Synth = AVSpeechSynthesizer()
let utterance = AVSpeechUtterance(string: activity.name)
Synth.speak(utterance)
}, label: {
HStack {
Image(uiImage: Activities.getImage(image: activity.image))
.resizable()
.scaledToFit()
.cornerRadius(10)
Spacer()
Text(activity.name)
.font(.title3)
.fontWeight(.bold)
.foregroundColor(.white)
}
.padding()
.background(Color(hex: "90CAF9"))
.frame(height: 100)
.cornerRadius(20)
.shadow(
color: Color.black.opacity(0.3), radius: 3, x: 3, y: 3)
})
}
.onDelete(perform: removeItems)
}
func getImage(image: String) -> UIImage {
if let uiImage = UIImage(contentsOfFile: image) {
return uiImage
}
return UIImage(systemName: "circle.fill")!
}
func removeItems(at offsets: IndexSet) {
activities.remove(atOffsets: offsets)
}
}
Sorry for the long code attached, but I couldn't explain the issue unless you see the full code of the view.