```html How to Build a Piano Practice App in SwiftUI (2026)

How to Build a Piano Practice App in SwiftUI

A Piano Practice app helps musicians track sessions, read sheet music, and sharpen their ear with interactive interval and chord recognition exercises — perfect for students and hobbyists who want a focused, ad-free practice companion on iPhone.

iOS 17+ · Xcode 16+ · SwiftUI Complexity: Advanced Updated: 2026-05-12

Prerequisites

Step-by-Step Build

1. Define the SwiftData practice model

Model each piece, session, and ear-training result as persistent @Model classes so progress survives app restarts.

import SwiftData
import Foundation

@Model
final class Piece {
    var title: String
    var composer: String
    var tempoTarget: Int          // BPM
    var sessions: [PracticeSession]

    init(title: String, composer: String, tempoTarget: Int = 80) {
        self.title = title
        self.composer = composer
        self.tempoTarget = tempoTarget
        self.sessions = []
    }
}

@Model
final class PracticeSession {
    var date: Date
    var durationSeconds: Int
    var tempoAchieved: Int
    var earTrainingScore: Double  // 0.0–1.0

    init(date: Date = .now, duration: Int, tempo: Int, score: Double) {
        self.date = date
        self.durationSeconds = duration
        self.tempoAchieved = tempo
        self.earTrainingScore = score
    }
}

2. Build the practice dashboard view

Present a NavigationStack-backed list of pieces with swipe actions and a floating "Start Session" button that pushes into the practice screen.

import SwiftUI
import SwiftData

struct PieceListView: View {
    @Query(sort: \Piece.title) private var pieces: [Piece]
    @Environment(\.modelContext) private var context
    @State private var showAdd = false

    var body: some View {
        NavigationStack {
            List(pieces) { piece in
                NavigationLink(destination: PracticeSessionView(piece: piece)) {
                    PieceRow(piece: piece)
                }
                .swipeActions { Button("Delete", role: .destructive) {
                    context.delete(piece)
                }}
            }
            .navigationTitle("My Pieces")
            .toolbar { Button("Add", systemImage: "plus") { showAdd = true } }
            .sheet(isPresented: $showAdd) { AddPieceSheet() }
        }
    }
}

3. Implement sheet music rendering and ear training

Use Canvas to draw a five-line staff with noteheads, then drive AVAudioEngine to synthesize tones for real-time interval recognition challenges.

import SwiftUI
import AVFoundation

struct StaffView: View {
    let notes: [MusicNote]       // your model type (pitch, duration, position)
    var body: some View {
        Canvas { ctx, size in
            let lineSpacing: CGFloat = size.height / 8
            for i in 0..<5 {
                let y = lineSpacing * CGFloat(i + 2)
                ctx.stroke(Path { p in p.move(to: .init(x: 0, y: y))
                    p.addLine(to: .init(x: size.width, y: y)) },
                    with: .color(.primary), lineWidth: 1.2)
            }
            for note in notes {
                let cx = CGFloat(note.xPosition) * size.width
                let cy = lineSpacing * CGFloat(note.staffLine)
                ctx.fill(Ellipse().path(in: .init(x: cx-8, y: cy-5, width: 16, height: 10)),
                         with: .color(.primary))
            }
        }
    }
}

// Ear training: play two tones and ask user to name the interval
func playInterval(rootMidi: Int, intervalSemitones: Int, engine: AVAudioEngine) {
    let player = AVAudioPlayerNode()
    engine.attach(player)
    engine.connect(player, to: engine.mainMixerNode, format: nil)
    try? engine.start()
    [rootMidi, rootMidi + intervalSemitones].enumerated().forEach { idx, midi in
        let freq = 440.0 * pow(2.0, Double(midi - 69) / 12.0)
        let buf  = sineBuffer(frequency: freq, duration: 1.0,
                              format: engine.mainMixerNode.outputFormat(forBus: 0))
        player.scheduleBuffer(buf, at: idx == 0 ? nil :
            AVAudioTime(hostTime: 0), completionHandler: nil)
    }
    player.play()
}

Common Pitfalls

Monetization: Auto-Renewable Subscription

A subscription tier fits Piano Practice apps naturally — offer a free tier with basic session tracking, then gate sheet music imports, advanced ear training exercises, and detailed analytics behind a monthly or annual plan using StoreKit 2's Product.purchase() API. Use Transaction.currentEntitlements to gate premium views on every app launch, and implement a SubscriptionStoreView (iOS 17+) to display your paywall with App Store–hosted localizations. Annual plans (e.g. $29.99/yr) convert better than monthly for practice apps because users think in "academic year" cycles — consider offering a 7-day free trial to reduce friction.

Ship Faster with Soarias

An advanced app like this — with custom Canvas rendering, AVFoundation audio synthesis, SwiftData persistence, and a StoreKit 2 paywall — can easily consume three to four weeks of scaffolding before a single note plays. Soarias ($79, one-time) integrates directly with Claude Code to generate your entire SwiftUI project structure, including the data model, audio engine wrappers, staff-drawing canvas, and subscription paywall, from a plain-English prompt. It then handles fastlane setup, App Store screenshot generation, and metadata submission — turning a 21-day build into a focused long weekend of refinement on the parts only you can decide.

Related Tutorials

FAQ

Do I need an Apple Developer account to build this app?

You can build and run the app on a personal device for free using a personal team in Xcode. However, to distribute on TestFlight, submit to the App Store, or use production StoreKit subscriptions, you need an Apple Developer Program membership at $99/year.

How do I submit a Piano Practice app to the App Store?

Archive your app in Xcode (Product → Archive), validate it through Organizer, then upload to App Store Connect. Fill in your app's metadata, screenshots for all required device sizes, privacy nutrition labels, and — because this app uses a subscription — a StoreKit configuration and a clear description of the subscription terms. Apple's review typically takes 1–3 business days for new submissions.

Last reviewed: 2026-05-12 by the Soarias team.

```