← Back to portfolio
💊 PillMemo

💊 PillMemo

PillMemo is an easy-to-use application that helps users remember to take their medicine 🏥

The application allows users to set reminders for different types of medicine and at different times of the day/week/month 📅

👨‍💻 Technology Used

  • SwiftUI
  • Core Data
  • CloudKit
  • User Notifications
  • UICalendarView

📌 What Changed In The Repo

The key change was introducing unique medicine identity and separate save paths for entries with reminder vs without reminder. This removed duplicate-reference collisions and fixed crashes when saving multiple pills for the same date.

🎥 PillMemo In Action

PillMemo demo video

🏆 Standout Success

Distinguish to save pill data with and without notifications.

Sounds trivial, but the real challenge was to create functionality to save pill data in Core Data and be read correctly by app, where each pill needed to be unique. Why? Because earlier, every next pill was saved, but remembered with the same reference, for the same date, where for the same date can be taken more than one pill. Without this change, app crashed every time when I wanted to save new pill with the same exact date.

func addMedicine(context: NSManagedObjectContext) async -> Bool {

    // MARK: Editing Data
    var medicine: Medicine!
    if let editMedicines = editMedicines {
        await context.perform {
            medicine = editMedicines

            // MARK: Remove All Pending Notifications
            UNUserNotificationCenter.current()
                .removePendingNotificationRequests(withIdentifiers: medicine.medicineNotificationIDs ?? [])
            medicine.medicineTitle = self.title
            medicine.medicineColor = self.medicineColor
            medicine.medicineWeekdays = self.weekDays
            medicine.medicineType = self.medicineType
            medicine.medicineTakePartOne = self.takeMedicinePartOne
            medicine.medicineTakePartTwo = self.takeMedicinePartTwo
            medicine.medicineIsReminderOn = self.isReminderOn
            medicine.medicineReminderText = self.title
            medicine.medicineNotificationDate = self.reminderDate
            medicine.medicineNotificationIDs = []
            medicine.medicineQuantity = self.quantityMedicine
            medicine.medicineCalendarIcon = self.calendarIcon
        }

        // Check if reminder is turn on here
        if isReminderOn {

            // MARK: Notifications Are Going To Be Scheduled
            if let ids = try? await scheduleNotification() {
                await context.perform {
                    medicine.medicineNotificationIDs = ids
                    try? context.save()
                    TelemetryManager.send("Edited medicine with reminder", with: ["Name": self.title])
                }
                return true
            }
        } else {

            // MARK: Add Data To Be Saved Without Reminder
            await context.perform {
                try? context.save()
                TelemetryManager.send("Edited medicine without reminder", with: ["Name": self.title])
            }
            return true
        }
    } else {
        await context.perform {

            // MARK: Fetch Existing Event Records
            let fetchRequest: NSFetchRequest<Medicine> = Medicine.fetchRequest()

            do {
                let existingMedicines = try self.moc.fetch(fetchRequest)

                // Determine the count of existing medicine
                let medicineCount = existingMedicines.count
                print(medicineCount)

                medicine = Medicine(context: context)

                // Add a number to be as a uniqal reference for each medicine
                medicine.medicineNumber = Int32(medicineCount + 1)

                medicine.medicineTitle = self.title
                medicine.medicineColor = self.medicineColor
                medicine.medicineWeekdays = self.weekDays
                medicine.medicineType = self.medicineType
                medicine.medicineTakePartOne = self.takeMedicinePartOne
                medicine.medicineTakePartTwo = self.takeMedicinePartTwo
                medicine.medicineIsReminderOn = self.isReminderOn
                medicine.medicineReminderText = self.title
                medicine.medicineNotificationDate = self.reminderDate
                medicine.medicineNotificationIDs = []
                medicine.medicineQuantity = self.quantityMedicine
                medicine.medicineCalendarIcon = self.calendarIcon
            } catch {
                print("Failed to insert event: \\(error.localizedDescription)")
            }
        }

        // Check if reminder is turn on here
        if isReminderOn {

            // MARK: Notifications Are Going To Be Scheduled
            if let ids = try? await scheduleNotification() {
                await context.perform {
                    medicine.medicineNotificationIDs = ids
                    try? context.save()
                    self.medicines.append(medicine)
                    self.changedMedicine = medicine
                    TelemetryManager.send("Added medicine with reminder", with: ["Name": self.title])
                }
                return true
            }
        } else {

            // MARK: Add Data To Be Saved Without Reminder
            await context.perform {
                try? context.save()
                self.medicines.append(medicine)
                self.changedMedicine = medicine
                TelemetryManager.send("Added medicine without reminder", with: ["Name": self.title])
            }
            return true
        }
    }
    return false
}
PillMemo app screenshot
PillMemo UI preview

🧩 Entireness

Nevertheless, while creating this simple medicine reminder app, I have also included the following:

  • In-App Purchases
  • TipKit
  • WishKit
  • Concurrency
  • Error handling & alerts
  • Onboarding
  • PDF Generator
  • Unit Tests
  • UI Tests
  • CI/CD (Xcode Cloud)
  • Project organization (VOODO Architecture)
  • Privacy Manifest
  • Published in App Store (04/2024)

ℹ️ License

The PillMemo is the property of Marcin Jędrzejak. Unauthorized copying, distribution, or modification of this application is strictly prohibited.