Model to search notes by date - swift

How to model notes, so I can search them ,after, by dates? like on the picture.
Firstly, I thought about dictionary with key of type Date; but it didn't work to me.
So I've tried the following String format:
"04-28-2020" - US calendar
"28-04-2020" - Other calendar
One of the issues - if the telephone changes date style(region dependent), keys remain in dictionary, but aren't accessed any more...
My intuition that it should be the dictionary, but what are the sustainable way to make such a model?
Can it be an array of notes [Note], without keys?
struct Note: Codable, Identifiable {
let id = UUID()
var date: Date
var title: String
var description: String
var dateString: String {
let df = DateFormatter()
df.dateStyle = .short
df.timeStyle = .medium
return df.string(from: date)
}
// method returns "key" to be used in dictionary
static func dateKey(for date: Date) -> String {
let df = DateFormatter()
df.dateStyle = .short
return df.string(from: date)
}
}
class NotesOrganizer: ObservableObject {
// #Published var notes = [Note]() - tried initially
#Published var notesDictionary = [String : [Note]]()
}
struct ContentView: View {
#State private var title = ""
#State private var description = ""
#ObservedObject var notesOrganizer = NotesOrganizer()
var body: some View {
NavigationView {
VStack {
Button("Save note") {
let key = Note.dateKey(for: Date())
notesOrganizer.notesDictionary[key]?.append(Note(date: Date(), title: title, description: description))
?? (notesOrganizer.notesDictionary[key] = [Note(date: Date(), title: title, description: description)])
print("Date Key: \(Note.dateKey(for: Date()))")
}
NavigationLink(destination: DetailView(notesOrganizer: notesOrganizer))
{
Text("Log history ->")
.foregroundColor(.purple)
.underline()
}
}
.navigationBarHidden(true)
}
}
init() {
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
UINavigationBar.appearance().shadowImage = UIImage()
}
}
DetailView()
extension Date {
var tomorrow: Date? {
return Calendar.current.date(byAdding: .day, value: 1, to: self)
}
var yesterday: Date? {
return Calendar.current.date(byAdding: .day, value: -1, to: self)
}
}
struct DetailView: View {
#ObservedObject var notesOrganizer: NotesOrganizer
#State private var dayLabel: String = Note.dateKey(for: Date())
#State private var chosenDate = Date()
var body: some View {
VStack {
HStack {
Button(action: {
chosenDate = chosenDate.yesterday!
dayLabel = Note.dateKey(for: chosenDate)
}, label: {
Image(systemName: "chevron.left")
})
Text(dayLabel)
Spacer()
Button(action: {
chosenDate = chosenDate.tomorrow!
dayLabel = Note.dateKey(for: chosenDate)
}, label: {
Image(systemName: "chevron.right")
})
}
.padding([.horizontal,.bottom])
List{
ForEach(notesOrganizer.notesDictionary[day] ?? []) { note in
HStack {
VStack(alignment:.leading) {
Text(note.title)
Text(note.description)
}.multilineTextAlignment(.leading)
Spacer()
Text(note.dateString)
}
}.onDelete(perform: removeItems)
}
.navigationBarTitle("", displayMode: .inline)
Spacer()
}

Related

Having trouble with "Return from initializer without initializing all stored properties" error

Having trouble resolving this error "Return from initializer without initializing all stored properties"
Here is the code that prompted the error,
import SwiftUI
struct TaskEditView: View
{
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#Environment(\.managedObjectContext) private var viewContext
#EnvironmentObject var dateHolder: DateHolder
#ObservedObject var notificationManager: NotificationManager
#State var selectedTaskItem: TaskItem?
#State var name: String
#State var desc: String
#State var dueDate: Date
#State var scheduleTime: Bool
init(passedTaskItem: TaskItem?, initialDate: Date)
{
if let taskItem = passedTaskItem
{
_selectedTaskItem = State(initialValue: taskItem)
_name = State(initialValue: taskItem.name ?? "")
_desc = State(initialValue: taskItem.desc ?? "")
_dueDate = State(initialValue: taskItem.dueDate ?? initialDate)
_scheduleTime = State(initialValue: taskItem.scheduleTime)
}
else
{
_name = State(initialValue: "")
_desc = State(initialValue: "")
_dueDate = State(initialValue: initialDate)
_scheduleTime = State(initialValue: false)
}
} //error appears right here
var body: some View
{
NavigationView{
Form
{
Section(header: Text("ToDo"))
{
TextField("Name", text: $name)
TextField("Description", text: $desc)
}
Section(header: Text("Deadline"))
{
Toggle("Schedule Time", isOn: $scheduleTime)
DatePicker("Date", selection: $dueDate, displayedComponents: displayedComps())
}
if selectedTaskItem?.isCompleted() ?? false
{
Section(header: Text("Completed"))
{
Text(selectedTaskItem?.completedDate?.formatted(date: .abbreviated, time: .shortened) ?? "")
}
}
Section()
{
Button("Save", action: saveAction)
.font(.headline)
.foregroundColor(.green)
.padding(.horizontal)
}
.navigationTitle("Create New ToDo 📝")
}
}
}
func displayedComps() -> DatePickerComponents
{
return scheduleTime ? [.hourAndMinute, .date] : [.date]
}
func saveAction()
{
withAnimation
{
if selectedTaskItem == nil
{
selectedTaskItem = TaskItem(context: viewContext)
}
selectedTaskItem?.created = Date()
selectedTaskItem?.name = name
selectedTaskItem?.desc = desc
selectedTaskItem?.dueDate = dueDate
selectedTaskItem?.scheduleTime = scheduleTime
dateHolder.saveContext(viewContext)
self.presentationMode.wrappedValue.dismiss()
}
let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: dueDate)
guard let hour = dateComponents.hour, let minute = dateComponents.minute else { return }
notificationManager.createLocalNotification(title: name, hour: hour, min: minute) { error in
if error == nil {}
}
}
}
struct TaskEditView_Previews: PreviewProvider {
static var previews: some View {
TaskEditView(passedTaskItem: TaskItem(), initialDate: Date())
}
}
I looked online and it said that I have to initialize all my proporties but im unsure how to do that for the
_selectedTaskItem
in the else statement.
Im very new to coding and much help would be appreciated
Thanks
Yes, you still need to initialize selectedTaskItem, even if its initial value is nil.
The good news is that passedTaskItem works in both cases, so you can move that initialization out of the if clause:
init(passedTaskItem: TaskItem?, initialDate: Date) {
_selectedTaskItem = State(initialValue: passedTaskItem)
if let taskItem = passedTaskItem {
_name = State(initialValue: taskItem.name ?? "")
// etc.

Swiftui searchable list with a datepicker?

I have a List which is searchable.
Is is possible to make the search with a datepicker UI?
NavigationView {
List {
ForEach (articles) { article in
ArticleRow(article: article)
}
}
Text("Content")
.navigationTitle("Articles")
}
.searchable(text: $searchText)
Your code is more or less generic. So you would need to adapt this solution to fit your code. But it should give you an idea on how to do this.
//struct used for this example
struct Article: Identifiable{
let id = UUID()
var date: Date
}
struct TestView: View{
//holds the unfiltered array
let articles: [Article]
#State private var selectedDate: Date = Date()
//filtered array
#State private var sorted: [Article] = []
var body: some View{
NavigationView {
List {
//iterate over the sorted array
ForEach (sorted) { article in
// ArticleRow(article: article)
Text(article.date, format: .dateTime)
}
}
Text("Content")
.navigationTitle("Articles")
}
//remove the searchable modifier
// .searchable(text: $searchText)
// react to changes of the selected Date
.onChange(of: selectedDate) { newValue in
//filter the array
// I filtered only for the day itself ignoring time
self.sorted = articles.filter{ Calendar.current.compare($0.date, to: selectedDate, toGranularity: .day) == .orderedSame}
}.onAppear{
//assign the initial array
sorted = articles
}
//DatePicker to select the date
DatePicker("Select Date", selection: $selectedDate)
}
}
Here is the code I implemented to search using the DatePicker. It uses the Calendar.current.compare function as implemented by #burnsi. I also added a reset button to reset the filter and get access to the complete list of books.
import SwiftUI
extension Date {
static func from(year: Int, month: Int, day: Int) -> Date {
let calendar = Calendar.current
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
return calendar.date(from: dateComponents) ?? Date()
}
}
struct Book: Identifiable {
let id = UUID()
let title: String
let datePublished: Date
}
struct ContentView: View {
#State private var selectedDate: Date = Date()
#State private var reset: Bool = false
#State private var books = [
Book(title: "Introduction to JavaScript", datePublished: Date.from(year: 2022, month: 09, day: 10)),
Book(title: "Mastering Swift", datePublished: Date.from(year: 2022, month: 09, day: 11)),
Book(title: "Beginning SQL", datePublished: Date.from(year: 2022, month: 09, day: 12)),
Book(title: "Professional Git", datePublished: Date.from(year: 2022, month: 09, day: 11)),
]
#State private var filteredBooks: [Book] = []
var body: some View {
VStack(alignment: .leading) {
DatePicker("Search by date", selection: $selectedDate, displayedComponents: .date)
Button("Reset") {
reset = true
}
Spacer()
if filteredBooks.isEmpty && !reset {
Text("No books found.")
} else {
List(reset ? books: filteredBooks) { book in
VStack(alignment: .leading) {
Text(book.title)
Text(book.datePublished, style: .date)
}
}
}
Spacer()
}.onChange(of: selectedDate) { value in
reset = false
// filter out the books
filteredBooks = books.filter { Calendar.current.compare($0.datePublished, to: selectedDate, toGranularity: .day) == .orderedSame }
}
.onAppear {
filteredBooks = books
}
.padding()
}
}

Linking ObservableObject to DatePicker in SwiftUI

I am new to Swift and all its frameworks. I have a JSON file with readings for each day of the year. I have made a Decodable struct for the reading and an ObservableObject class which stores the readings in an array. I have made the ObservableObject an #EnvironmentObject so it can be accessed in all views. Can I link the readings to the date picker so that selecting a date will take me to a detailed view?
import SwiftUI
struct CalendarView: View {
// this is where ObserveableObject is required
#EnvironmentObject var days: Days
#State private var date = Date()
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
// formatter.dateStyle = .long
formatter.dateFormat = "LLLL d"
return formatter
}()
var body: some View {
VStack {
Text("Select a date to read")
.font(.largeTitle)
DatePicker("Select a date", selection: $date, displayedComponents: .date)
.datePickerStyle(GraphicalDatePickerStyle())
.labelsHidden()
.frame(maxHeight: 400)
Text("\(date, formatter: dateFormatter)")
}
.navigationTitle("Datepicker")
}
}
struct CalendarView_Previews: PreviewProvider {
static var previews: some View {
CalendarView()
}
}
//Replace this with your Decodable object
class Day : ObservableObject{
let id: UUID = UUID()
var date: Date
init( date: Date) {
self.date = date
}
}
class CalendarViewModel: ObservableObject, Identifiable {
#Published var days: [Day] = []
#Published var selectedDate: Date = Date()
var filteredDay: Day?{
days.filter({
Calendar.current.isDate($0.date, equalTo: selectedDate, toGranularity: .day)
}).first
}
init() {
//Create sample Days you can remove this and populate your array with acual data
for n in 1...30{
days.append(Day(date: Date().addingTimeInterval(TimeInterval(60*60*24*n))))
days.append(Day(date: Date().addingTimeInterval(TimeInterval(-60*60*24*n))))
}
}
}
struct CalendarView: View {
#StateObject var vm: CalendarViewModel = CalendarViewModel()
#State var isVisible: Bool = false
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
// formatter.dateStyle = .long
formatter.dateFormat = "LLLL d"
return formatter
}()
var body: some View {
NavigationView{
ScrollView {
Text("Select a date to read")
.font(.largeTitle)
DatePicker("Select a date", selection: $vm.selectedDate, displayedComponents: .date)
.datePickerStyle(GraphicalDatePickerStyle())
.labelsHidden()
.frame(maxHeight: 400)
Text("\(vm.selectedDate, formatter: dateFormatter)")
if vm.filteredDay != nil{
NavigationLink(
destination: DayView(day: vm.filteredDay!),
isActive: $isVisible,
label: {
Text("View Day")
})
}else{
Text("No results for selected day")
}
}
.navigationTitle("Datepicker")
}
}
}
struct DayView:View {
//Observe the actual Day here for changes
#ObservedObject var day: Day
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
// formatter.dateStyle = .long
formatter.dateFormat = "LLLL d"
return formatter
}()
var body: some View {
VStack{
Text(dateFormatter.string(from: day.date))
}
}
}

Using Picker with Core Data

I have seen a number of responses about similar issues on here, but the picker for $category in the code below doesn't seem to work. When I select the picker, I see the list of categoryNames, but when I choose one, it doesn't get populated into $category.
I have 2 CoreData entities:
Expenses
expenseAccount:String
expenseCategory:String
expenseCost:Double
expenseDate:Date
expenseId:UUID
expenseIsMonthly:Bool
expenseName:String
Categories
categoryName:String
import SwiftUI
import CoreData
struct ExpenseDetail: View {
#FetchRequest(
entity: Categories.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Categories.categoryName, ascending: true)
]
)
private var result: FetchedResults<Categories>
var logToEdit: Expenses?
#Environment(\.managedObjectContext) var context
#State var name: String = ""
#State var amount: String = ""
#State var category: String = ""
#State var date: Date = Date()
#State var account: String = ""
#State var isMonthly: Bool = false
var currencyFormatter: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .currency
return f
}()
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
Form{
TextField("Expense Name", text: $name)
Section{
HStack{
TextField("$\(amount)", text: $amount)
.keyboardType(.decimalPad)
.textFieldStyle(PlainTextFieldStyle())
.disableAutocorrection(true).multilineTextAlignment(.leading)
}
DatePicker(selection: $date, displayedComponents: .date) {
Text("Date")
}.onAppear{self.hideKeyboard()}
Picker(selection: $category, label: Text("Category")) {
ForEach(result) { (log: Categories) in
Text(log.categoryName ?? "No Category").tag(log.categoryName)
}
}
Picker(selection: $account, label: Text("Account")) {
ForEach(result) { (log: Categories) in
self.Print("\(log.categoryName ?? "")")
Button(action: {
// TODO: Implement Edit
}) {
Text(log.categoryName!.capitalized).tag(self.category)
}
}
}
Toggle(isOn: $isMonthly) {
Text("Monthly Expense")
}.toggleStyle(CheckboxToggleStyle())
}
Section{
Button(action: {
onSaveTapped()
}) {
HStack {
Spacer()
Text("Save")
Spacer()
}
}
}
Section{
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Spacer()
Text("Cancel").foregroundColor(.red)
Spacer()
}
}
}
}.navigationBarTitle("Add Expense")
}
}
private func onSaveTapped() {
let expenseLog: Expenses
if let logToEdit = self.logToEdit {
expenseLog = logToEdit
} else {
expenseLog = Expenses(context: self.context)
expenseLog.expenseId = UUID()
}
expenseLog.expenseName = self.name
expenseLog.expenseCategory = self.category
print("\(expenseLog.expenseName ?? "") category Picker: \(self.category)")
print("\(expenseLog.expenseName ?? "") ExpenseCategory: \(expenseLog.expenseCategory!)")
expenseLog.expenseCost = Double(self.amount) ?? 0
print("\(expenseLog.expenseName ?? "") Amount: \(self.amount)")
print("\(expenseLog.expenseName ?? "")ExpenseCost: \(expenseLog.expenseCost)")
expenseLog.expenseDate = self.date
expenseLog.expenseAccount = self.account
expenseLog.expenseIsMonthly = self.isMonthly
do {
try context.save()
} catch let error as NSError {
print(error.localizedDescription)
}
self.presentationMode.wrappedValue.dismiss()
}
}
The type of the selection value is String while Category.categoryName is String?. Notice the added ?, this means it is an Optional.
It is the default type for CoreData, but you can remove the Optional value inside the model:
I would ask myself does a Category without a name make sense before doing this change. If it does, you will probably have to use another identifier for the selection.

How to properly group CoreData records by category in SwiftUI?

I am using CoreData. I have:
1 Entity: Todo
3 attributes: category (String), date (Date), title (String).
Module: Current Product Module
Codegen: Class Definition
I would like to build a simple ToDo app that looks like this:
To do items
Category
1. 3/26/20 To do item 1
2. 3/26/20 To do item 2
Category
1. 3/26/20 To do item 3
2. 3/27/20 To do item 4
I know there are similar questions, but I didn't find an answer on how to set everything using CoreData and SwiftUI.
I have most of the code done. Including adding items, saving to CoreData, deleting items.
ContentView.swift
Here I display the To do list. I have added the comments to problematic parts.
import SwiftUI
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#State private var date = Date()
#FetchRequest(
entity: Todo.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Todo.date, ascending: true)
]
) var todos: FetchedResults<Todo>
#State private var show_modal: Bool = false
// let dictionary = Dictionary(grouping: Todo) { $0.category }
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}
var body: some View {
NavigationView {
List {
// Here I should sort by category
ForEach(self.todos, id: \.title) { todo in
// Here I should write category name instead of static text
Section(header: Text("Category")) {
ForEach(Array(self.todos.enumerated()), id: \.element) {(i, todo) in
NavigationLink(destination: TodoDetailsView(todo: todo)) {
HStack {
Text("\(i+1). ")
Text("\(todo.date ?? Date(), formatter: self.dateFormatter)")
Text(todo.title ?? "")
}
}
}
}
}
}
.navigationBarTitle(Text("To do items"))
.navigationBarItems(
trailing:
Button(action: {
self.show_modal = true
}) {
Text("Add")
}.sheet(isPresented: self.$show_modal) {
TodoAddView().environment(\.managedObjectContext, self.moc)
}
)
.listStyle(GroupedListStyle())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return ContentView().environment(\.managedObjectContext, context)
}
}
TodoAddView.swift
Here I add new items and save them to CoreData. This works OK.
import SwiftUI
struct TodoAddView: View {
#Environment(\.presentationMode) var presentationMode
#Environment(\.managedObjectContext) var moc
static let dateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
#State private var showDatePicker = false
#State private var title = ""
#State private var category = ""
#State private var date : Date = Date()
var body: some View {
NavigationView {
VStack {
HStack {
Button(action: {
self.showDatePicker.toggle()
}) {
Text("\(date, formatter: Self.dateFormat)")
}
Spacer()
}
if self.showDatePicker {
DatePicker(
selection: $date,
displayedComponents: .date,
label: { Text("Date") }
)
.labelsHidden()
}
TextField("title", text: $title)
TextField("category", text: $category)
Spacer()
}
.padding()
.navigationBarTitle(Text("Add to do item"))
.navigationBarItems(
leading:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Cancel")
},
trailing:
Button(action: {
let todo = Todo(context: self.moc)
todo.date = self.date
todo.title = self.title
todo.category = self.category
do {
try self.moc.save()
}catch{
print(error)
}
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Done")
}
)
}
}
}
struct TodoAddView_Previews: PreviewProvider {
static var previews: some View {
TodoAddView()
}
}
TodoDetailsView.swift
Here I display details of each item and can delete an item from there. This works good too.
import SwiftUI
struct TodoDetailsView: View {
#Environment(\.managedObjectContext) var moc
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var todo: Todo
static let dateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
#State private var showDatePicker = false
#State private var newDate : Date = Date()
#State private var newTitle = ""
#State private var newCategory = ""
var body: some View {
ScrollView {
VStack {
HStack {
Button(action: {
self.showDatePicker.toggle()
}) {
Text("\(newDate, formatter: Self.dateFormat)")
}
Spacer()
}
if self.showDatePicker {
DatePicker(
selection: $newDate,
displayedComponents: .date,
label: { Text("Date") }
)
.labelsHidden()
}
TextField("title", text: $newTitle, onCommit: {
self.todo.title = self.newTitle
try? self.moc.save()
}
)
TextField("category", text: $newCategory, onCommit: {
self.todo.category = self.newCategory
try? self.moc.save()
}
)
Spacer()
}
.padding()
.navigationBarTitle(Text("Details"))
.navigationBarItems(
trailing:
Button(action: {
self.moc.delete(self.todo)
do {
try self.moc.save()
self.presentationMode.wrappedValue.dismiss()
}catch{
print(error)
}
}) {
Text("Delete")
.foregroundColor(.red)
}
)
}
.onAppear {
self.newDate = self.todo.date ?? Date()
self.newTitle = self.todo.title ?? ""
self.newCategory = self.todo.category ?? ""
}
.onDisappear {
self.todo.date = self.newDate
self.todo.title = self.newTitle
self.todo.category = self.newCategory
try? self.moc.save()
}
}
}
struct TodoDetailsView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let todo = Todo.init(context: context)
todo.date = Date()
todo.title = "to do item"
return TodoDetailsView(todo: todo).environment(\.managedObjectContext, context)
}
}
The thing I don't know is how to properly group the items by, for example, category.
How to do it using Core Data? I assume I need to change Codegen of my Entity and add an extension to group the records. For example something like this:
let dictionary = Dictionary(grouping: Todo) { $0.category }
But should it be Manual/None or Category/Extension? And where and which code to use? I know how to create a Subclass.
And later I could probably reference to this dictionary in ForEach in my ContentView. But don't know how exactly.
If my assumptions are wrong, please correct me. Thanks in advance.