Why I Built Albedo
I wanted to build a relatively advanced app right from the start. My goal was to dive deep into the code, explore Apple's documentation in detail, challenge myself by running into bugs and learning to solve them, and develop disciplined, well-organized code. In terms of design, I aimed to keep the look and feel very native, drawing inspiration from the button styles used in Apple Maps.
As for the app's focus, I'm passionate about astronomy. I often use the Heavens Above website to track satellites, and I wanted to create something just as precise; but with a polished and enjoyable mobile experience.
Technical Challenge
One key challenge was implementing the complex physics calculations to determine the exact minute when satellites enter and exit Earth's shadow, plus calculating magnitude variations throughout each pass. Obviously the architecture wasn't simple, I was at the beginning of my learning journey. I had to break and rebuild the app several times until I truly understood SwiftUI architecture.
Live Ground Tracks & Pass Prediction
Watch real-time satellite ground tracks with MapKit integration, fully optimized for dark mode. See the next pass for your selected satellite and get precise visibility chances based on weather conditions.
public static func calculateOrbitPaths(for satellite: Satellite) throws -> [CLLocationCoordinate2D] {
var coordinates: [CLLocationCoordinate2D] = []
let startDate = Date()
for minute in 0...90 {
let futureDate = startDate.addingTimeInterval(Double(minute) * 60)
let position = try satellite.geoPosition(julianDays: futureDate.julianDate)
let coordinate = CLLocationCoordinate2D(
latitude: position.lat,
longitude: position.lon - floor((position.lon + 180) / 360) * 360
)
coordinates.append(coordinate)
}
return coordinates
}Ground tracks calculation method: SGP4 for low Earth orbit satellites (90-minute revolutions), SDP4 for high orbit satellites. Implementation from my custom Swift Package Manager AlbedoKit.


Deep Dive into Pass Details
Explore every aspect of a satellite pass across 7 days of visibility. Get rise and set times, detailed weather conditions, satellite magnitude variations, and astronomical context.
do {
async let hourlyForecast = weatherService.weather(
for: userLocation,
including: .hourly(startDate: nowDate, endDate: sevenDaysAhead)
)
async let dailyForecast = weatherService.weather(
for: userLocation,
including: .daily(startDate: nowDate, endDate: sevenDaysAhead)
)
let (hourly, daily) = try await (hourlyForecast, dailyForecast)
return (hourly, daily)
} catch {
throw WeatherError.failedToFetchWeather
}Albedo powered by WeatherKit for pass forecasting, visibility probability calculations, and weather conditions analysis.


Visual Tracking & Navigation
Swift Charts integration provides clear visual representations of satellite data. An integrated compass helps you track exactly where the satellite will pass in the sky.
switch model.selectionState {
case .selected(let selectedMagnitude):
RuleMarkSelection(selectedMagnitude: selectedMagnitude, shadowEvent: model.pass.shadowEvent)
case .unselectedWithShadowEvent(let shadowState):
ShadowRuleMark(shadowState: shadowState)
case .unselected:
EmptyRuleMark()
}Chart selection switcher using chartXSelection to manage interactive data visualization.

What's under the hood
Technologies and frameworks powering Albedo's precision
Apple Frameworks
Ground tracks
Visibility forecasts
Data visualization
In-app purchases
Position tracking
Other Packages
I initially created my own package, AlbedoKit, to handle magnitude calculations and Earth shadow computations. I proposed integrating it into SatelliteKit, but the maintainer preferred to keep that library focused on its core functionality. Instead, he suggested contributing to SatelliteUtilities, a project specialized in satellite processing such as ground tracks, where I successfully added my work.
In addition, I built a custom backend with Vapor to implement intelligent caching. This allows users to share cached API data, reducing redundant API calls and improving performance.
Backend
TLE parsing & mechanics
Moon illumination
Coordinate transforms
Architecture
Throughout Albedo's development, I iteratively refactored the application architecture to deeply understand SwiftUI patterns. I evolved from basic MV to MVVM, then embraced Clean Architecture principles for better separation of concerns. The final iteration features a horizontal, modular architecture built with Swift Package Manager, enabling excellent scalability, comprehensive testing, and seamless feature additions without disrupting the entire codebase.

BarTinder
Swipe through ingredients. Discover cocktails. Create your own recipes. The modern way to explore mixology.
Why I Built BarTinder
Since Albedo is an app focused on REST APIs and physical calculations, I also wanted to create a simpler CRUD application that allows adding, updating and deleting items using Swift Data.
Built with the simplicity and modern approach of SwiftUI, BarTinder serves as an excellent learning resource for iOS developers at any level. The app leverages the latest iOS 26 APIs and features, including Foundation Models (Apple Intelligence), Liquid Glass design elements, SwiftData for CRUD operations, and Xcode 26's Default Actor Isolation and Approachable Concurrency settings.
Since I don't have a formal UI/UX design background, I drew heavy inspiration from Apple's stock applications and design language. I pay close attention to Apple's Human Interface Guidelines to ensure the app feels truly native.
I also wanted to build BarTinder with a clean, readable architecture while respecting proper separation of concerns. I wrote extensive tests using Swift Testing to practice modern testing techniques, with Xcode Cloud automatically catching any breaking changes. By focusing on the CRUD aspect, lists, scroll views and UI/UX, I was targeting a common application pattern that is widely used across many types of products.
Technical Challenge
Allowing users to cancel cocktail edits while using @Bindable presented a significant challenge. The solution required creating a draft SwiftData context to handle temporary changes, carefully managing potential conflicts between different contexts and @Query, all while maintaining clean and maintainable code architecture throughout the project.
Swipe Your Taste
Browse through ingredient cards with familiar swipe gestures. Love an ingredient? Swipe right. Not a fan? Swipe left.
private func handleSwipe(_ value: DragGesture.Value) {
if value.translation.width >= threshold {
offset = BarTinderApp.SwipingSettings.swipeOutDistance
rotation = BarTinderApp.SwipingSettings.maxRotation
Task { await model.swipeRight(card: cardIngredient) }
} else if value.translation.width <= -threshold {
offset = -BarTinderApp.SwipingSettings.swipeOutDistance
rotation = -BarTinderApp.SwipingSettings.maxRotation
Task { await model.swipeLeft(card: cardIngredient) }
} else {
offset = 0
rotation = 0
}
}Swipe gestures managed with threshold detection, offset calculations, and rotation animations for smooth card interactions.

Discover Perfect Matches
Based on your ingredient preferences, BarTinder shows you cocktails you can make right now with what you have. Sort them by name, glass type, or difficulty, and add your favorites to the Bar for quick access.
func executeUpdatePossibleCocktails() {
let cocktails = repo.callGetContextContent()
for cocktail in cocktails {
let ingredientNames = Set(cocktail.ingredients.map(\.name))
if selectedIngredients.isSuperset(of: ingredientNames) {
cocktail.isPossible = true
}
}
repo.callContextSave()
}My first idea was to use Sets for easy ingredient comparison, and even deduce missing ingredients if needed.

Create Your Signature
Create custom cocktails with your own photos from the library, choose your ingredients, quantities, glassware, and preparation steps. Edit or delete your creations anytime.
struct CreateEditCocktail: View {
@Environment(\.modelContext) private var context
@State private var model = CocktailCreationModel()
@Bindable var cocktail: Cocktail
var body: some View {
List {
Section {
CocktailPreviewSection(selectedImage: $model.selectedPic, cocktail: cocktail)
}
// ...
}
.toolbar {
CreationToolbar(cocktail: cocktail)
}
.navigationTitle(context.insertedModelsArray.isEmpty ? "Edit Cocktail" : "New Cocktail")
.navigationBarBackButtonHidden()
.navigationBarTitleDisplayMode(.inline)
.scrollDismissesKeyboard(.interactively)
.environment(model)
}
}Since @Model uses @Observable under the hood, I can directly bind cocktail properties to UI components like TextFields and Pickers. This allows me to pass a @Bindable cocktail to the view and use the same view for both creating and editing.


Detailed Cocktail View
Dive deep into each cocktail with comprehensive details: ingredients list, glassware, and all characteristics. Add cocktails to your personal bar, edit recipes, or customize them to your taste.
@Model
final class Cocktail {
#Index<Cocktail>([\.isInBar, \.isPossible])
@Attribute(.unique)
var name: String
@Relationship(deleteRule: .cascade, inverse: \Ingredient.cocktail)
var ingredients: [Ingredient]
var isInBar: Bool
var isPossible: Bool
var imageName: String?
@Attribute(.externalStorage)
var imageData: Data?
var style: CocktailStyle
var glass: CocktailGlass
var mixingTechnique: CocktailMixingTechnique
var difficulty: CocktailDifficulty
init(...) { ... }
}
@Model
final class Ingredient {
var name: String
var measure: String
var unit: Units
var cocktail: Cocktail?
init(...) { ... }
}A cocktail has a lot to offer. It includes glassware, style, technique, and difficulty.
Plus ingredients with measurements and units.

What's under the hood
Technologies and frameworks powering BarTinder's cocktail experience
Apple Frameworks
Local persistence
Unit testing
Photo selection
Apple Intelligence

