Sari la conținut
megapromotingHai să discutăm

Expertiză · Aplicații mobile

Aplicații pentru telefon, construite astfel încât logica să poată fi verificată fără telefon.

Construim aplicații native iOS în Swift, cu senzori și HealthKit, și aplicații cu un singur cod pentru iOS, Android și web atunci când proiectul nu cere senzori. Nucleul de logică se testează separat de ecran.

Construit dejaSunt trei implementări proprii, nu una. Un depozit cu cinci ținte de aplicație (patru iOS și una macOS) definite în `project.yml`, ale cărui teste de logică le-am rulat azi: 160 din 160 trec. O a doua aplicație iOS, în alt proiect, cu 40 de fișiere Swift și HealthKit. Și o a treia direcție, cu un singur cod pentru iOS, Android și web, a cărei variantă web răspunde astăzi. Rezerva care schimbă răspunsul la întrebarea pe care o pune orice cumpărător înainte de toate: niciuna dintre aplicațiile noastre nu este publicată în App Store sau Google Play. Motivul e unul singur și are nume — `DEVELOPMENT_TEAM` este gol în configurația de proiect, adică lipsește identificatorul de echipă din Apple Developer Program. Mașina are o identitate de semnare validă, suficientă pentru instalare pe dispozitiv propriu, insuficientă pentru distribuție în magazin. E o problemă de cont, nu de cod, dar rămâne o problemă și o scriem aici, nu la subsol.

O aplicație mobilă are două părți care se strică diferit. Partea care citește senzorii și calculează ceva — un unghi, un scor, o stare — și partea care desenează ecrane. Le ținem separate, și nu din motive estetice: în depozitul nostru principal de iOS, fișierele de nucleu nu importă interfața, așa că pot fi compilate direct cu `swiftc` pe Mac și rulate ca un program obișnuit, fără simulator și fără telefon. Suita are 160 de verificări grupate în 16 etape; am rulat-o azi și trece integral. Asta e motivul pentru care putem spune ce face exact un algoritm, în loc să spunem că „funcționează bine”.

Pe iOS lucrăm în Swift 6, cu țintă minimă iOS 17. Același depozit definește cinci aplicații ca ținte separate: patru pentru iPhone și una pentru macOS, iar cea de macOS refolosește cincisprezece fișiere din nucleul celei de iOS, fără copiere. Senzorii se citesc la sursă: interfața Apple pentru senzorii din căști expune orientarea capului, iar aplicația compară unghiul curent cu o poziție pe care utilizatorul o calibrează la începutul sesiunii. Pragurile nu sunt „reglaje interne”: sub 7° e drept, între 7° și 13° e alunecare, peste 13° ținut trei secunde declanșează alerta, iar netezirea folosește o medie exponențială cu coeficientul 0,15. Toate cele patru valori stau în patru linii consecutive de cod și pot fi arătate.

Când proiectul nu are nevoie de senzori, nu recomandăm nativ. A treia direcție din portofoliu e un singur cod care se compilează pentru iPhone, Android și browser — React Native cu Expo — iar varianta web e publicată și răspunde azi. Diferența practică pentru client: un chestionar, un calculator sau un tablou de bord nu justifică două echipe și două depozite; o aplicație care ascultă un senzor la douăzeci de probe pe secundă, da.

Ce nu putem încă. Nicio aplicație a noastră nu e în App Store sau Google Play. Nu pentru că nu compilează — compilează, se instalează pe simulator și trece testele — ci pentru că în configurația de proiect identificatorul de echipă Apple e gol, adică nu există o înscriere în Apple Developer Program legată de aceste build-uri. Pe Mac există o identitate de semnare validă, de tip dezvoltare, care ajunge pentru instalare pe propriul dispozitiv și nu ajunge pentru magazin. Pentru un proiect de client, contul de dezvoltator se deschide pe firma clientului și rămâne al lui — asta e discuția care trebuie purtată la început, nu la final.

Ce cuprinde

Lucrarea, pe componente

Citim senzorul la sursă, nu printr-o bibliotecă intermediară

Pe iOS folosim direct interfețele Apple: `CMHeadphoneMotionManager` pentru orientarea capului din căști, cu delegat pentru conectare și deconectare, și verificare explicită a stării de autorizare înainte de a porni fluxul. Dacă senzorul nu e disponibil, starea trece în `waiting`, nu în eroare tăcută.

Nucleul de logică nu știe că există un ecran

Fișierele de analiză, calibrare, istoric și politici sunt Swift simplu. Se compilează cu `swiftc` împreună cu fișierul de teste și se execută ca binar de linie de comandă. Rezultatul de azi: 160 de verificări, 16 etape a câte 10, toate trecute. Un test care rulează în două secunde pe Mac se rulează la fiecare modificare; unul care cere simulator, nu.

Interfața se construiește peste nucleu, pe iOS și pe macOS deodată

SwiftUI pentru ambele. Ținta de macOS din același depozit include cincisprezece fișiere din nucleul iOS ca surse partajate, nu ca fișiere copiate, deci o corecție în algoritm ajunge simultan în ambele aplicații.

Un singur cod pentru iOS, Android și web, când asta e alegerea corectă

Expo peste React Native, cu `react-native-web` pentru varianta de browser, fonturi încărcate din pachet și pictograme de aplicație separate pentru Android, inclusiv varianta monocromă cerută de sistem. Aceeași aplicație rulează pe telefon și în pagină, dintr-un singur depozit.

Date de sănătate: o politică declarată pentru fiecare tip de dată

Nu „citim HealthKit”, ci un tabel în cod: unsprezece tipuri de dată (unghiuri posturale, mișcare brută din căști, puls și variabilitate, somn, mers, expunere audio, sesiuni ghidate și altele) și cinci regimuri posibile (numai local, citire din HealthKit, scriere de antrenament, scriere de sesiune de relaxare, scriere de traseu). Fiecare tip primește explicit unul dintre regimuri. A doua aplicație iOS din portofoliu, dintr-un proiect privat, folosește aceeași abordare pe 40 de fișiere Swift.

Fișierele de confidențialitate cerute de Apple, prezente de la început

Patru ținte au `PrivacyInfo.xcprivacy` inclus ca resursă de build, nu adăugat în grabă la prima respingere. Descrierile de utilizare pentru mișcare, cameră, microfon, HealthKit și notificări sunt scrise în română, în configurația de proiect, nu generate automat.

Ce iese din telefon: în cazul aplicației iOS de referință, nimic

Căutarea după `URLSession` în sursele țintei iOS întoarce zero rezultate. Istoricul se scrie pe dispozitiv, prin `FileManager` și `UserDefaults`, cu opțiune de ștergere a fișierului. Singurul cod de rețea din depozit e într-o funcție a aplicației de macOS, într-un fișier separat. Când o aplicație nu are nevoie de server, nu îi punem unul.

Parcursul de început, testat ca stare, nu ca ecran

Onboardingul are 24 de pași cu identificatori unici și ordonați, iar unii pași cer o confirmare explicită (limitele senzorului, funcționarea în prim-plan, confidențialitatea, pregătirea) fără de care butonul de continuare rămâne blocat. Starea se codifică și se reia. Zece dintre cele 160 de teste verifică exact asta.

Semnare și distribuție, spuse la început

Verificăm ce cont de dezvoltator există, pe ce firmă, cine îl administrează și ce lipsește ca build-ul să poată fi semnat pentru magazin. E o listă scurtă și plictisitoare, dar e diferența dintre o aplicație care merge pe telefonul nostru și una care ajunge la utilizatori.

Cum arată

Patru straturi, de jos în sus: senzorii platformei (mișcare din căști, HealthKit, notificări); nucleul de logică în Swift simplu, care nu importă interfața și de aceea poate fi rulat pe Mac fără telefon; interfața SwiftUI, aceeași pentru iPhone și macOS; și stratul de distribuție — fișier de confidențialitate, permisiuni declarate, semnare. Ultimul strat e singurul pe care nu l-am parcurs până la capăt, și de aceea e desenat deschis.

01

Decidem nativ sau cod comun, pe baza senzorilor

Întrebarea nu e de gust, ci de hardware: are aplicația nevoie de mișcare, sănătate, Bluetooth, cameră în timp real, fundal? Dacă da, nativ. Dacă e vorba de formulare, calcule, liste și un tablou de bord, un singur cod pentru iOS, Android și web face aceeași treabă cu jumătate de întreținere. Livrăm decizia scrisă, cu motivul.

02

Construim nucleul înainte de ecrane și îl acoperim cu teste care rulează fără telefon

Algoritmul, pragurile, stările și persistența. Testele se compilează direct pe Mac și se rulează în câteva secunde. Livrăm suita și rezultatul ei, nu o promisiune de calitate.

03

Punem interfața peste nucleu și rulăm pe simulator și pe dispozitiv

SwiftUI pe iOS și macOS, sau React Native când am ales codul comun. Verificăm parcursul de început, permisiunile și comportamentul când senzorul lipsește sau se deconectează în mijlocul sesiunii — cazul care se strică des în practică.

04

Pregătim distribuția, cu contul clientului

Identificator de pachet, versiune, pictograme, fișier de confidențialitate, descrieri de permisiuni, identificator de echipă din Apple Developer Program. Aici se blochează proiectele care nu au discutat contul la început — inclusiv ale noastre, ceea ce e exact motivul pentru care punem pasul acesta pe hârtie.

Ce vede utilizatorulAplicațiaRețea și securitateGăzduire și dateFIECARE STRAT, ALES ȘI EXPLICAT
Patru straturi, de jos în sus: senzorii platformei (mișcare din căști, HealthKit, notificări); nucleul de logică în Swift simplu, care nu importă interfața și de aceea poate fi rulat pe Mac fără telefon; interfața SwiftUI, aceeași pentru iPhone și macOS; și stratul de distribuție — fișier de confidențialitate, permisiuni declarate, semnare. Ultimul strat e singurul pe care nu l-am parcurs până la capăt, și de aceea e desenat deschis.

Datele

Ce atingem, unde stau și cât rămân

Întrebările pe care le pune oricine are un responsabil cu protecția datelor — puse aici înainte să le pună el.

Unde stau datele când aplicația nu are server
Pe dispozitiv. Istoricul postural se serializează într-un fișier propriu prin `FileManager`, iar preferințele în `UserDefaults`, cu chei prefixate pe aplicație. Există comandă de ștergere a fișierului de istoric. Nimic nu pleacă spre un server pentru simplul motiv că aplicația nu conține cod de rețea.
Datele de sănătate se citesc numai cu acord, și numai cele declarate
HealthKit cere acordul utilizatorului la nivel de tip de dată, iar aplicația declară în configurație de ce le cere. Politica din cod separă explicit ce se citește de ce se scrie înapoi în Sănătate. Nu se face diagnostic și nu se transmite nimic către noi.
Ce cere Apple să declari, indiferent ce faci cu datele
Fișierul de confidențialitate al aplicației și descrierile de utilizare pentru fiecare permisiune (mișcare, cameră, microfon, sănătate, notificări). Sunt condiție de acceptare în magazin, nu opțiune, și se scriu în limba în care vorbește utilizatorul.
Cât se păstrează
Cât decide utilizatorul, când datele stau pe telefon: dezinstalarea aplicației le duce cu ea, iar ștergerea din aplicație este o operație explicită. Când un proiect are totuși server, retenția se scrie în contract înainte de prima linie de cod, pentru că determină ce se poate și ce nu se poate construi.
Contul de dezvoltator și cheile de semnare
Rămân ale clientului, pe firma clientului. Noi lucrăm cu acces delegat. Motivul e practic, nu principial: o aplicație publicată pe contul furnizorului devine imposibil de mutat în ziua în care furnizorul se schimbă.

Un caz

O aplicație de iPhone care măsoară un singur lucru, și îl măsoară verificabil

Situația

Un proiect propriu, nu al unui client: un cronometru de concentrare care observă cât de mult s-a înclinat capul față de o poziție calibrată de utilizator la începutul sesiunii. Aveam nevoie de un caz în care afirmațiile despre algoritm să poată fi arătate, nu descrise.

Ce am construit

Am separat nucleul de interfață și am scris pragurile în cod, nu în documentație: sub 7° drept, între 7° și 13° alunecare, peste 13° ținut trei secunde declanșează alerta haptică, cu netezire exponențială la coeficientul 0,15. Datele vin din interfața Apple pentru senzorii din căști, cu tratare explicită a deconectării. Onboardingul are 24 de pași, dintre care patru cer confirmarea limitelor înainte de a lăsa utilizatorul să meargă mai departe. Am construit din același nucleu și o variantă de macOS.

Ce a ieșit

Suita de logică se compilează cu `swiftc` și rulează pe Mac în câteva secunde: 160 de verificări în 16 etape, toate trecute la ultima rulare. Aplicația se instalează și pornește pe simulator. Orice afirmație din paragraful de mai sus poate fi verificată deschizând patru linii de cod.

Ce nu spune cazul

Nu este în App Store. Configurația de proiect are identificatorul de echipă Apple gol, deci build-ul nu poate fi semnat pentru distribuție — o lipsă de înscriere în programul Apple, nu o lipsă de funcționalitate. Și încă o limită, a produsului nu a codului: măsoară înclinarea capului, nu sănătatea coloanei, și funcționează cu căștile care expun senzori de mișcare prin interfața Apple.

Întrebări

Ce ne întreabă oamenii înainte să sune

Aveți o aplicație publicată în App Store, ca să o pot vedea?

Nu. Niciuna dintre aplicațiile noastre nu e în App Store sau Google Play, și e corect să întrebi asta prima. Ce putem arăta: cinci ținte de aplicație definite în același proiect, dintre care patru pentru iPhone și una pentru macOS, care compilează și se instalează pe simulator, plus suita de logică de 160 de verificări, rulată la cerere în fața ta. Blocajul la publicare este `DEVELOPMENT_TEAM` gol în configurația de proiect, adică lipsa unui identificator de echipă din Apple Developer Program. Pe Mac există o identitate de semnare validă, de tip dezvoltare — ajunge pentru instalare pe dispozitiv propriu, nu pentru magazin.

Atunci de unde știu că puteți duce o aplicație până în magazin?

Din nimic, dacă ceri o dovadă de publicare — nu avem una și nu o inventăm. Ce poți verifica e restul lanțului: cod care compilează, teste care trec, fișiere de confidențialitate prezente, permisiuni declarate, pictograme și versiuni configurate. Pasul care lipsește e o înscriere plătită într-un program Apple, făcută pe firma care deține aplicația. Într-un proiect de client, acel cont e al clientului și se deschide la început, exact ca să nu fie el pasul care surprinde pe cineva la sfârșit.

Nativ sau o singură aplicație pentru iOS și Android?

Depinde de senzori. Dacă aplicația citește mișcare, sănătate, Bluetooth sau cameră în timp real, nativ — interfețele acelea sunt ale platformei și orice strat intermediar adaugă întârziere și moduri noi de a se strica. Dacă aplicația este în esență formulare, calcule și ecrane de rezultat, un singur cod acoperă iOS, Android și browser dintr-un depozit. Avem ambele variante în portofoliu și le alegem cu argumentul scris, nu implicit.

Faceți și aplicații de Android?

Prin codul comun, da: același proiect Expo produce build pentru Android, cu pictograma adaptivă și varianta monocromă cerută de sistem, deja configurate. Android nativ, în Kotlin, nu avem în portofoliu — dacă un proiect îl cere, o spunem înainte, nu după semnare.

Cum verificați că logica e corectă dacă nu aveți telefonul meu?

Prin faptul că nucleul nu depinde de ecran. Fișierele de analiză se compilează separat, cu compilatorul Swift, și se rulează ca program pe Mac. Suita curentă are 160 de verificări în 16 etape și se execută în câteva secunde. Ce nu se poate verifica așa — cum se simte o vibrație, cum arată un ecran pe un telefon anume, cum se comportă căștile când ies dintr-o ureche — se testează pe dispozitiv, și se spune care e care.

Aplicația poate citi datele mele din Sănătate?

Numai dacă le aprobi, tip cu tip, în ecranul Apple, și numai pe cele declarate. În cod politica e explicită pentru fiecare fel de dată: unele rămân doar pe telefon, altele se citesc din HealthKit, altele se pot scrie înapoi ca antrenament sau ca sesiune de relaxare. Nu se face diagnostic, iar datele nu ajung la noi.

Ce se întâmplă cu datele dacă aplicația nu are server?

Rămân pe telefon. În aplicația iOS de referință din portofoliul nostru nu există niciun apel de rețea — căutarea după `URLSession` în sursele acelei ținte întoarce zero rezultate. Istoricul se scrie într-un fișier local și se poate șterge din aplicație. Când un proiect are nevoie de server, îl construim, dar nu îl punem ca reflex.

Putem prelua o aplicație existentă, făcută de altcineva?

Începe cu o verificare de compatibilitate, nu cu o ofertă: ce versiune de platformă țintește, ce dependențe are și care dintre ele mai sunt întreținute, dacă build-ul se reproduce pe o mașină curată, cine deține contul de dezvoltator și cheile. Sunt proiecte în care răspunsul cinstit e că rescrierea nucleului costă mai puțin decât întreținerea lui.

Faceți și aplicații de desktop?

Pe macOS, da, și din același nucleu: în depozitul nostru principal ținta de macOS refolosește cincisprezece fișiere de logică din aplicația de iPhone, ca surse partajate. Windows nu e în portofoliu.

Pe ce se sprijină fiecare afirmație de mai sus (11 surse)
  1. Cinci ținte de aplicație în același proiect: patru iOS și una macOS, plus o țintă de testeproject.yml:14-160 (AirPostureTimer, AirPostureMac, MEGAGaitCoach, MEGAInterviewDynamics, NeuroBudsLabApp, AirPostureTimerTests)
  2. 160 din 160 de verificări de logică trec, 16 etape × 10CoreLogicTests.swift (1.708 linii):ieșirea rulării: „PASS 160/160 core logic tests · 16 etape × 10”
  3. Praguri și netezire: 7°, 13°, 3 s, coeficient 0,15PostureAnalyzer.swift:32-35 (driftThresholdDegrees, slouchThresholdDegrees, gracePeriod, smoothingAlpha); 110 pentru aplicarea perioadei de grație
  4. Blocajul de publicare este identificatorul de echipă Apple, golproject.yml:12 (`DEVELOPMENT_TEAM: ""`)
  5. Ținta iOS nu conține cod de rețea → zero):singurul rezultat din depozit: AirPostureMac/ElevenLabsRealtimeCoach.swift:121, în ținta de macOS
  6. Politică declarată pe tip de dată de sănătate: 11 tipuri, 5 regimuriHealthDataPolicy.swift:3-22 (enum AirPostureHealthDatum, enum HealthDataAccessPolicy)
  7. Patru fișiere de confidențialitate `PrivacyInfo.xcprivacy`, incluse ca resurse de build):project.yml, secțiunile `sources` cu `buildPhase: resources`
  8. Onboarding de 24 de pași, cu confirmări obligatorii care blochează avansareaAirPostureOnboardingCore.swift:73 (pasul 24)
  9. A doua aplicație iOS din portofoliu: 40 de fișiere Swift, 6.105 linii, HealthKitproject.yml:VEROApp.swift, Models/HealthSnapshot.swift, Views/HealthDashboardView.swift
  10. Un singur cod pentru iOS, Android și web: Expo 56, React Native 0.85.3, react-native-webapp.json:package.json: expo ~56.0.11, react-native 0.85.3, react-native-web ^0.21.2; app.json: secțiunile ios, android (adaptiveIcon + monochromeImage), web
  11. Varianta web a aplicației cu cod comun răspunde azihttps://longevita-alpha.vercel.app/ · 2026-09-06

Citările interne arată numele fișierului și linia. Calea completă rămâne în depozitul nostru; o putem parcurge împreună, la cerere.

Ce ai vrea să funcționeze mai bine?

Povestește-ne despre procesul tău. Împreună stabilim ce merită construit, ce putem conecta și cum verificăm rezultatul.

Hai să discutăm