The Tally App, or "Tapp," is a SwiftUI iOS app where users can store, share, and update tallies β for example, counting the number of times you used the restroom, the number of swears you let slip, or how often you thought about the Roman Empire. What sets Tapp apart from other tally apps is sharing: add a friend by username and your tallies sync between you.
Join the Tapp Party Today!
- Platform: iOS 26.1+
- Language: Swift 5.0
- UI: SwiftUI (SwiftUI App lifecycle,
@maininTappApp.swift) - IDE: Xcode 26.1.1+
- Backend: Firebase (FirebaseCore, FirebaseAuth, FirebaseFirestore via SPM 12.x)
Tapp/
βββ Tapp/
β βββ TappApp.swift # @main, Firebase configure, RootView auth gate, theme
β βββ AuthStore.swift # Username/password auth, profile, account mutations
β βββ UserProfile.swift # Codable model for users/{uid}
β βββ UsernameClaim.swift # usernames/{name} uniqueness ledger
β βββ UserDirectory.swift # uid β (name, username, avatar color) cache
β βββ FriendsStore.swift # Friends array + add/remove (mutual)
β βββ Tally.swift # Codable model + TallyRole helpers
β βββ TallyStore.swift # Live-sync tallies + writes + scheduled resets
β βββ TallyListPreferences.swift # Per-user sort toggle (UserDefaults)
β βββ TallyLastSeenStore.swift # Landing avatar flash state
β βββ LocalTallyColors.swift # Per-device tally colors (UserDefaults)
β βββ ColorPickerSheet.swift # 12-swatch palette + Default
β βββ PermissionsSheet.swift # Friends/permissions table + AvatarBadge
β βββ CountFormatter.swift # Arabic / Roman number rendering
β βββ StickTallyView.swift # Custom tally-mark groups (stick number type)
β βββ FireworksOverlay.swift # Increment + landing fireworks (Reduce Motion aware)
β βββ TallyResetSheet.swift # Off / now / daily / monthly / yearly resets
β βββ TallyResetDates.swift # Local-midnight reset scheduling helpers
β βββ UserPreferences.swift # NumberType + Theme cycling
β βββ TappDesignMetrics.swift # Visual spec constants
β βββ TallyAccessibility.swift # VoiceOver labels + row custom actions
β βββ ColorContrast.swift # WCAG AA palette validation
β βββ ShakeModifier.swift # View-only tap feedback
β βββ SignupView.swift # Name + Username + Password signup
β βββ LoginView.swift # Username + Password login
β βββ TalliesView.swift # Home: list, sort, landing effects, fireworks
β βββ FullScreenTallyView.swift # Full-screen tally + per-tally settings
β βββ SettingsView.swift # Account, friends, numbers, theme, log out
β βββ AvatarColorAssignment.swift
β βββ GoogleService-Info.plist # gitignored β provide your own
β βββ Assets.xcassets/
βββ TappTests/
βββ TappUITests/
βββ Tapp.xcodeproj/
Implemented:
- Username + password auth. Signup: Name, Username, Password (Firebase uses a synthetic email
username@tapp.usersunder the hood; users never type an email at signup). Login sheet for returning users.AuthStoreexposeschangeName,changeUsername,changeEmail,changePassword,signOut,deleteAccount. - Username claim via
usernames/{name}(transactional). - Home page. Settings gear; sort toggle (Last updated vs Created, persisted per user); Add Tally modal with permissions table; tally rows with owner avatar, lock for view-only, local color tint, tap to increment, long-press for full screen. Landing: updater avatar flash + optional fireworks (max 3 concurrent).
- Full Screen Tally. Increment area; long-press name/count to edit; bottom row: Color, Friends & Permissions, Resets, Fireworks, Delete (owner vs non-owner labels).
- Resets. Off / reset now / daily / monthly / yearly; client applies overdue resets on the next tally write.
- Fireworks. Per-tally toggle; plays on increment (row + full screen) and on landing when enabled; Reduce Motion β sparkle fade.
- Numbers & theme. Settings cycles Arabic β Roman β Stick and System β Light β Dark; applies app-wide (
.preferredColorSchemefor theme;CountFormatter/StickTallyViewfor counts). - Friends. Add by username (instant mutual); remove cascades shared tallies.
- Accessibility. VoiceOver labels on icon buttons; custom actions for Increment, Open Full Screen Tally, Edit Name, Edit Count; Dynamic Type on full-screen count; WCAG AA contrast on palette tints.
- Visual spec. Row height 88pt, 36pt circular controls, 56pt add button, etc. (
TappDesignMetrics).
The app uses Firebase Email/Password with a synthetic email derived from the username (username@tapp.users). Users only see username + password in the UI. Changing username updates the synthetic email in Firebase Auth. Settings still exposes Email for users who want to attach a real address via Firebase updateEmail.
This project does not ship with a GoogleService-Info.plist β you need your own Firebase project.
git clone https://github.com/benjaminross6/Tally-App.git
cd Tally-App- Go to https://console.firebase.google.com and create a project.
- Add an iOS app with your bundle ID (e.g.
com.yourname.tapp). - Download
GoogleService-Info.plist.
Place GoogleService-Info.plist in the Tapp/ folder next to TappApp.swift (file-system synchronized groups pick it up automatically). The file is gitignored.
Open Tapp.xcodeproj, set the Tapp target bundle ID to match Firebase, pick your development team, and repeat for test targets if needed.
- Authentication β Sign-in method β enable Email/Password (email link / passwordless is not required).
- Firestore β create database β publish rules (below).
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null;
allow create, delete: if request.auth != null && request.auth.uid == userId;
allow update: if request.auth != null && (
request.auth.uid == userId ||
request.resource.data.diff(resource.data).affectedKeys().hasOnly(['Friends', 'Tallies'])
);
}
match /usernames/{name} {
allow read: if request.auth != null;
allow create: if request.auth != null
&& request.resource.data.uid == request.auth.uid;
allow update: if request.auth != null
&& request.resource.data.uid == request.auth.uid
&& (resource == null || resource.data.uid == request.auth.uid);
allow delete: if request.auth != null
&& resource.data.uid == request.auth.uid;
}
match /tallies/{tallyId} {
function isOwner() {
return request.auth != null
&& resource.data.Owner == /databases/$(database)/documents/users/$(request.auth.uid);
}
function isShared() {
return request.auth != null
&& resource.data['Shared With'].hasAny([
/databases/$(database)/documents/users/$(request.auth.uid)
]);
}
allow create: if request.auth != null
&& request.resource.data.Owner == /databases/$(database)/documents/users/$(request.auth.uid);
allow read, update: if isOwner() || isShared();
allow delete: if isOwner();
}
}
}- Open the project in Xcode, select a simulator or device, βR.
- Create Account with name, username, and password β or Log in if you already have an account.
Firestore field names match Swift CodingKeys (some use spaces).
Name,Username,Email(synthetic or user-updated)Joined,Tallies[],Friends[]NumberType(arabic|roman|stick)Theme(system|light|dark)AvatarColor(hex, assigned at signup)
Name,Count,Owner,Shared With[],PermissionsmapCreated,LastUpdated,LastUpdatedByFireworksEnabled,ResetSchedule,NextResetAt
uid
tapp.tallyColorIndex.<tallyId>β local row tinttapp.sortEnabled.<userId>β home sort: last updated vs createdtapp.lastSeenTallyUpdates.<userId>.<tallyId>β landing updater avatar
Possible future work:
- Forgot-password / account recovery flow on the login screen
- Server-side reset scheduler (today resets are best-effort on client write)
- Broader UI test coverage
Issues and pull requests welcome. For non-trivial changes, open an issue first.
MIT Β© Ben Ross