ios-tracker/HabitTracker/Models/Habit.swift
rene 22b8f5d806 Initiales HabitTracker-Projekt: SwiftUI + SwiftData Gewohnheiten-Tracker
Natives iOS-App-Gerüst (Xcode 26, synchronisierte Ordner, iOS 18+).

Features:
- Gewohnheiten anlegen (Name, SF-Symbol, Farbe), heute abhaken, Streaks, Löschen
- Detailansicht mit Monatskalender (Tage nachtragbar) und Statistiken
- Tägliche Erinnerungen via lokale Notifications
- Home-Screen-Widget (klein/mittel) mit App-Group-Datenaustausch
2026-05-29 21:12:45 +02:00

84 lines
2.4 KiB
Swift

import Foundation
import SwiftData
@Model
final class Habit {
var uuid: UUID = UUID()
var name: String
var symbolName: String
var colorHex: String
var createdAt: Date
/// Time of day for the daily reminder; nil means no reminder.
var reminderTime: Date?
@Relationship(deleteRule: .cascade, inverse: \HabitEntry.habit)
var entries: [HabitEntry] = []
init(
name: String,
symbolName: String = "star.fill",
colorHex: String = "#34C759",
createdAt: Date = .now,
reminderTime: Date? = nil
) {
self.name = name
self.symbolName = symbolName
self.colorHex = colorHex
self.createdAt = createdAt
self.reminderTime = reminderTime
}
}
extension Habit {
/// Whether this habit was checked off on the given day.
func isCompleted(on day: Date, calendar: Calendar = .current) -> Bool {
entries.contains { calendar.isDate($0.date, inSameDayAs: day) }
}
var isCompletedToday: Bool {
isCompleted(on: .now)
}
/// Number of consecutive days (counting back from today) the habit was done.
var currentStreak: Int {
let calendar = Calendar.current
let doneDays = Set(entries.map { calendar.startOfDay(for: $0.date) })
var day = calendar.startOfDay(for: .now)
// A streak stays alive if today isn't done yet but yesterday was.
if !doneDays.contains(day) {
day = calendar.date(byAdding: .day, value: -1, to: day)!
}
var streak = 0
while doneDays.contains(day) {
streak += 1
day = calendar.date(byAdding: .day, value: -1, to: day)!
}
return streak
}
/// Longest run of consecutive completed days, ever.
var longestStreak: Int {
let calendar = Calendar.current
let days = Set(entries.map { calendar.startOfDay(for: $0.date) }).sorted()
guard !days.isEmpty else { return 0 }
var longest = 1
var run = 1
for i in 1..<days.count {
let expectedPrev = calendar.date(byAdding: .day, value: -1, to: days[i])!
if calendar.isDate(expectedPrev, inSameDayAs: days[i - 1]) {
run += 1
} else {
run = 1
}
longest = max(longest, run)
}
return longest
}
var totalCompletions: Int {
entries.count
}
}