Skip to content

Repository files navigation

SunHat

Reminders that wait for the right weather.

iOS Build Platform Swift License: MIT

Most reminder apps ask when. SunHat asks what conditions.
Set "water the garden when it's been dry for 48 hours" or
"golden hour photo walk when it's above 68F and clear," and SunHat
watches the forecast and tells you when reality matches your intent.

Built for iOS with SwiftUI, SwiftData, and WeatherKit.
No accounts. The free version shows banner ads; the optional SunHat Ad-Free subscription removes them.


What SunHat does

A normal calendar reminder for "go for a run" goes off whether it's sunny or sleeting. SunHat takes a different approach: some plans depend on the weather, not the clock.

You describe the conditions you care about. SunHat checks the forecast in the background and sends a notification when those conditions show up. That's the whole idea, and everything in the app supports that one loop.

A few things you can set up:

  • "Go for a run when it's 55-70F and clear"
  • "Water the garden when it's been dry for 48 hours"
  • "Beach day when it's above 80F with no rain in the forecast"
  • "Golden hour photo walk when it's above 68F and sunny"

Features

Feature Details
7 trigger types Exact temperature, temperature range, sky conditions, feels-like, dry period, consecutive days, and composite (temp + humidity + wind)
Smart predictions Forecast-based confidence scoring so you know when conditions are likely to be met
Background monitoring iOS BackgroundTasks checks the weather and notifies you when conditions line up
Hourly forecast Real WeatherKit hourly data on the dashboard, not made-up numbers
Temperature history Trend charts with yesterday, last week, and monthly averages
Quiet hours and limits Control when and how often you get notified
Manual city selection Use GPS or pick any city yourself
App Intents and Shortcuts Create reminders from Siri and Shortcuts
Spotlight indexing Find reminders from iOS search
Data export and deletion Full GDPR-style export and deletion, covered by schema-parity tests
Liquid Glass UI Native iOS 26 design with .glassEffect() surfaces
Ad-free option The free version shows banner ads (Google AdMob); the SunHat Ad-Free subscription removes them. No accounts, and SunHat collects no analytics of its own

Screenshots

Captured on iOS 26 (iPhone). SunHat is iPhone-first. iPad, widget, and watch surfaces are planned (see Project status).

Dark

Welcome Dashboard Weather
Create a reminder Reminders Settings

Light

Welcome Dashboard Weather
Create a reminder Reminders Settings

Requirements

Xcode 26+
Swift 6.2
Minimum iOS 26.5
Dependencies Google Mobile Ads + User Messaging Platform (SPM), for the ad-supported free tier. Everything else is Apple frameworks.

No CocoaPods, no Carthage. The only SPM packages are Google Mobile Ads and its User Messaging Platform dependency, which exist to support the ad-funded free tier; every other import is an Apple framework, and that's intentional.

Getting started

git clone https://github.com/weskcode/sunhat.git
cd sunhat
open SunHat.xcodeproj

Build and run with Cmd+R. Run the tests with Cmd+U. From the command line:

xcodebuild -scheme SunHat -configuration Debug -destination 'generic/platform=iOS Simulator' build
xcodebuild -scheme SunHat -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -only-testing:SunHatTests test

WeatherKit setup

Live weather needs a WeatherKit entitlement tied to your own Apple Developer team:

  1. Turn on the WeatherKit capability for your App ID in the Apple Developer portal.
  2. Change PRODUCT_BUNDLE_IDENTIFIER to something you own (it ships as org.wesley.sunhat).
  3. Regenerate your provisioning profile after adding the capability. A profile created before the capability was added will fail at runtime.

Without it the app builds and runs, but weather fetches fail. Failures log the raw provider error under subsystem org.wesley.sunhat, category AppleWeatherKitAPI (visible in Console.app).

Architecture

MVVM on top of SwiftData, with protocol seams for anything that touches the outside world.

SunHat/
  Models/          SwiftData @Model types (WeatherReminder, TriggerCondition, WeatherData)
  ViewModels/      Presentation state. @Observable for new code, ObservableObject where Combine remains
  Views/           SwiftUI, grouped by feature (Dashboard, Weather, Reminders, Settings, Onboarding)
  Services/
    Weather/       WeatherKit provider and caching behind WeatherProviding
    Trigger/       Condition evaluation engine (the core logic)
    Location/      CoreLocation wrapper behind LocationManaging
    Notifications/ Deep-link handoff, data clearing, category registry
  Utilities/       Theme, motion, formatting helpers

The trigger engine is the heart of the app. TriggerEngine+ForecastAnalysis.swift decides whether current or forecast conditions satisfy a reminder's TriggerCondition. It's the most correctness-sensitive code in the project, so it has the densest test coverage. Changes there need tests.

Dependency-injection seams (WeatherProviding, LocationManaging, SettingsOpening, NotificationPermissionProviding) let view models be tested without touching the network, GPS, or system APIs. Use them instead of singletons in new code.

Design language

iOS 26 Liquid Glass. Card surfaces use .glassEffect(). Page backgrounds use Color(.systemBackground). Don't use .regularMaterial (it's superseded) and don't nest glass inside glass. Respect accessibilityReduceMotion for anything animated.

Testing

Unit tests live in SunHatTests/ (Swift Testing for newer suites, XCTest for older ones). UI tests in SunHatUITests/ are XCTest.

The app's location permission affects UI test results. notDetermined triggers a system permission dialog the tests don't dismiss, and denied fails dashboard tests that expect a clean initial state. Keep it granted:

xcrun simctl privacy <device-udid> grant location org.wesley.sunhat

Project status

Version 1.0, iPhone-first, preparing for App Store submission.

Working: Weather-triggered reminders across 7 trigger types, background monitoring, notifications, App Intents and Shortcuts, Spotlight indexing, data export and deletion, notification deep-linking, complete privacy deletion, and full English/Spanish localization (String Catalog, neutral international Spanish).

Tested: A unit test suite covers the trigger engine, weather service, privacy deletion parity, notification delivery, location persistence, and localization catalog completeness.

Planned: WidgetKit and Lock Screen widgets, a watchOS complication, an iPad-optimized layout, CloudKit sync (already prepared in code, needs provisioning), and additional locales beyond English/Spanish if a market need arises.

Toolchain note

The project builds with Xcode 26 and Swift 6.2. App Store submission requires a release Xcode, not a beta.

CI runs on macos-latest via GitHub Actions. The build-for-testing step uses the SunHatUnitTests scheme.

Principles

These are the rules the code is held to. Read them before contributing.

  1. Never invent weather data. If a provider returns no hourly forecast, the UI says so. It does not fill in plausible-looking numbers. Historical comparisons show "Not enough history yet" instead of a placeholder. This rule has caught real regressions, and there are tests guarding it.
  2. Never imply official authority. Threshold notices are branded "SunHat Advisory" and show the exact threshold. They are not government or WeatherKit severe-weather alerts and must never look like them.
  3. Privacy is a feature, not a page. Location stays on-device. Coordinates go only to the weather provider, and only to fetch a forecast. "Delete all my data" must actually delete everything, including state stored outside SwiftData.
  4. Controls must do what they say. A toggle that doesn't persist, or a screen that doesn't work, gets deleted rather than shipped.

Contributing

See CONTRIBUTING.md. The short version: keep the weather-trigger loop central, never fabricate data, add tests for trigger-engine and view-model logic, and match the surrounding code's style.

License

MIT. See LICENSE.


Built with SwiftUI, SwiftData, and WeatherKit.

About

SunHat is an iOS app that watches the forecast and sends you a reminder when the weather matches your custom conditions.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages