Noțiuni de bază cu modelul de arhitectură VIPER

modelul arhitectural VIPER este o alternativă la MVC sau MVVM. Și în timp ce cadrele SwiftUI și Combine creează o combinație puternică care face munca rapidă de a construi UIs complexe și de a muta date în jurul unei aplicații, acestea vin, de asemenea, cu propriile provocări și opinii despre arhitectură.

este o credință comună că toată logica aplicației ar trebui să intre acum într-o vizualizare SwiftUI, dar nu este cazul.

VIPER oferă o alternativă la acest scenariu și poate fi utilizat împreună cu SwiftUI și se combină pentru a ajuta la construirea de aplicații cu o arhitectură curată care separă în mod eficient diferitele funcții și responsabilități necesare, cum ar fi interfața cu utilizatorul, logica de afaceri, stocarea datelor și crearea de rețele. Acestea sunt apoi mai ușor de testat, întreținut și extins.

în acest tutorial, veți construi o aplicație folosind modelul de arhitectură VIPER. Aplicația este, de asemenea, numită în mod convenabil VIPER: vizual interesant planificat easy Roadtrips. Inteligent, nu? :]

acesta va permite utilizatorilor să construiască excursii rutiere prin adăugarea de puncte intermediare la un traseu. Pe parcurs, veți afla, de asemenea, despre SwiftUI și combinați pentru proiectele dvs. iOS.

ecranul principal al aplicației VIPER

Noțiuni de bază

descărcați materialele proiectului din butonul Descărcare materiale din partea de sus sau de jos a tutorialului. Deschideți proiectul de pornire. Aceasta include un cod pentru a vă începe:

  • ContentView va lansa alte vizualizări ale aplicației pe măsură ce le construiți.
  • există câteva vizualizări ajutătoare în grupul de vizualizări funcționale: una pentru înfășurarea vizualizării hărții MapKit, o vizualizare specială „split image”, care este utilizată de TripListCell. Veți adăuga acestea pe ecran într-un pic.
  • în grupul entități, veți vedea clasele legate de modelul de date. Trip și Waypoint vor servi mai târziu ca entități ale arhitecturii VIPER. Ca atare, ele dețin doar date și nu includ nicio logică funcțională.
  • în grupul surse de date, există funcțiile de ajutor pentru salvarea sau încărcarea datelor.
  • Peek înainte, dacă vă place în grupul WaypointModule. Aceasta are o implementare VIPER a ecranului de editare Waypoint. Este inclus cu starterul, astfel încât să puteți completa aplicația până la sfârșitul acestui tutorial.

acest eșantion utilizează un site de partajare a fotografiilor autorizat în mod permisiv. Pentru a trage imagini în aplicație, va trebui să creați un cont gratuit și să obțineți o cheie API.

Urmați instrucțiunile de aicihttps://.com/accounts/register/ pentru a crea un cont. Apoi, copiați cheia API înapiKey variabilă Găsită în ImageDataProvider.swift. O puteți găsi în documentele API din imaginile de căutare.

dacă construiți și rulați acum, nu veți vedea nimic prea interesant.

aplicația VIPER la proiectul de pornire

cu toate acestea, până la sfârșitul tutorialului, veți avea o aplicație complet funcțională de planificare a călătoriilor rutiere.

ce este vipera?

VIPER este un model arhitectural ca MVC sau MVVM, dar separă codul în continuare printr-o singură responsabilitate. MVC în stil Apple îi motivează pe dezvoltatori să pună toată logica într-o subclasă UIViewController. VIPER, la fel ca MVVM înainte, încearcă să remedieze această problemă.

fiecare dintre literele din Viper reprezintă o componentă a arhitecturii: View, Interactor, Presenter, Entity și Router.

  • vizualizarea este interfața cu utilizatorul. Aceasta corespunde unui SwiftUI View.
  • Interactorul este o clasă care mediază între prezentator și date. Este nevoie de direcție de la Prezentator.
  • prezentatorul este „polițistul de trafic” al arhitecturii, direcționând datele între vizualizare și interactor, luând acțiuni ale utilizatorului și apelând la router pentru a muta utilizatorul între vizualizări.
  • o entitate reprezintă Datele aplicației.
  • routerul gestionează navigarea între ecrane. Acest lucru este diferit de cel din SwiftUI, unde vizualizarea arată orice vizualizări noi.

această separare este susținută de paradigma arhitecturii curate a „unchiului” Bob Martin.

diagrama VIPER

când te uiți la diagramă, poți vedea că există o cale completă pentru ca datele să curgă între vizualizare și entități.

SwiftUI are propriul său mod de a face lucrurile. Maparea responsabilităților VIPER asupra obiectelor de domeniu va fi diferită dacă comparați acest lucru cu tutorialele pentru aplicațiile UIKit.

Compararea arhitecturilor

oamenii discută adesea despre VIPER cu MVC și MVVM, dar este diferit de aceste modele.

MVC, sau Model-View-Controller, este modelul pe care majoritatea oamenilor îl asociază cu arhitectura aplicațiilor iOS din 2010. Cu această abordare, definiți vizualizarea într-un storyboard, iar controlerul este asociat UIViewController subclasă. Controlerul modifică vizualizarea, acceptă introducerea utilizatorului și interacționează direct cu modelul. Controlerul se umflă cu logica de vedere și logica de afaceri.

MVVM este o arhitectură populară care separă logica de vizualizare de logica de afaceri într-un Model de vizualizare. Modelul de vizualizare interacționează cu modelul.

marea diferență este că un model de vizualizare, spre deosebire de un controler de vizualizare, are doar o referință unidirecțională la vizualizare și la model. MVVM este o potrivire bună pentru SwiftUI și există un întreg tutorial pe această temă.

VIPER merge un pas mai departe prin separarea logicii de vizualizare de logica modelului de date. Numai prezentatorul vorbește cu vederea și numai interacătorul vorbește cu modelul (entitatea). Prezentatorul și interactorul se coordonează între ei. Prezentatorul este preocupat de afișarea și acțiunea utilizatorului, iar interactorul este preocupat de manipularea datelor.

un șarpe viperă, pentru distracție

definirea unei entități

VIPER este un acronim distractiv pentru această arhitectură, dar ordinea sa nu este proscriptivă.

cel mai rapid mod de a obține ceva pe ecran este să începeți cu entitatea. Entitatea este obiectul(obiectele) de date pentru proiect. În acest caz, principalele entități sunt Trip, care conține o listă de puncte intermediare, care sunt opririle din călătorie.

Aplicația conține o clasă DataModel care deține o listă de călătorii. Modelul folosește un fișier JSON pentru persistența locală, dar îl puteți înlocui cu un back-end de la distanță fără a fi nevoie să modificați codul de nivel UI. Acesta este unul dintre avantajele arhitecturii curate: atunci când schimbați o parte — cum ar fi stratul de persistență — este izolată de alte zone ale codului.

adăugarea unui Interactor

creați un nou fișier Swift numit TripListInteractor.swift.

adăugați următorul cod în fișier:

class TripListInteractor { let model: DataModel init (model: DataModel) { self.model = model }}

aceasta creează clasa interactor și îi atribuie unDataModel, pe care îl veți folosi mai târziu.

Configurarea prezentatorului

acum, creați un nou fișier Swift numit TripListPresenter.swift. Aceasta va fi pentru clasa de prezentatori. Prezentatorului îi pasă de furnizarea de date către interfața de utilizare și de medierea acțiunilor utilizatorului.

adăugați acest cod la fișierul:

import SwiftUIimport Combineclass TripListPresenter: ObservableObject { private let interactor: TripListInteractor init(interactor: TripListInteractor) { self.interactor = interactor }}

aceasta creează o clasă de prezentator care are referire la interactor.

deoarece este sarcina prezentatorului să umple vizualizarea cu date, doriți să expuneți lista de călătorii din modelul de date.

adăugați o nouă variabilă la clasă:

@Published var trips: = 

aceasta este lista călătoriilor pe care utilizatorul le va vedea în vizualizare. Declarându-l cu @Published wrapper de proprietate, vizualizarea va putea asculta modificările aduse proprietății și se va actualiza automat.

următorul pas este să sincronizați această listă cu modelul de date din interactor. Mai întâi, adăugați următoarea proprietate helper:

private var cancellables = Set<AnyCancellable>()

Acest set este un loc pentru a stoca abonamente combinate, astfel încât durata lor de viață este legată de clasă. în acest fel, orice abonamente vor rămâne active atâta timp cât prezentatorul este în jur.

adăugați următorul cod la sfârșitulinit(interactor:):

interactor.model.$trips .assign(to: \.trips, on: self) .store(in: &cancellables)

interactor.model.$trips creează un editor care urmărește modificările modelului de datetrips colecție. Valorile sale sunt atribuite propriei colecții trips a acestei clase, creând o legătură care menține călătoriile prezentatorului actualizate atunci când modelul de date se schimbă.

în cele din urmă, acest abonament este stocat încancellables astfel încât să îl puteți curăța mai târziu.

construirea unei vizualizări

acum trebuie să construiți prima vizualizare: vizualizarea listei de călătorii.

crearea unei vizualizări cu un prezentator

creați un fișier nou din șablonul SwiftUI View și denumiți-l TripListView.swift.

adăugați următoarea proprietate la TripListView:

@ObservedObject var presenter: TripListPresenter

aceasta leagă prezentatorul de vizualizare. Apoi, remediați previzualizările schimbând corpul TripListView_Previews.previews la:

let model = DataModel.samplelet interactor = TripListInteractor(model: model)let presenter = TripListPresenter(interactor: interactor)return TripListView(presenter: presenter)

acum, înlocuiți conținutul TripListView.body cu:

List { ForEach (presenter.trips, id: \.id) { item in TripListCell(trip: item) .frame(height: 240) }}

aceasta creează un List unde sunt enumerate călătoriile prezentatorului și generează un TripListCell pre-furnizat pentru fiecare.

fereastra de previzualizare a vizualizării listei de călătorii

modificarea modelului din vizualizarea

până în prezent, ați văzut fluxul de date de la entitate la interactor prin prezentator pentru a popula vizualizarea. Modelul VIPER este și mai util atunci când trimiteți acțiuni ale utilizatorilor înapoi pentru a manipula modelul de date.

pentru a vedea acest lucru, veți adăuga un buton pentru a crea o nouă călătorie.

Mai întâi, adăugați următoarele la clasa din TripListInteractor.rapid:

func addNewTrip() { model.pushNewTrip()}

aceasta înfășoară modelul pushNewTrip(), care creează un nou Trip în partea de sus a listei trips.

apoi, în TripListPresenter.swift, adăugați acest lucru la clasa:

func makeAddNewButton() -> some View { Button(action: addNewTrip) { Image(systemName: "plus") }}func addNewTrip() { interactor.addNewTrip()}

aceasta creează un buton cu sistemul+ imagine cu o acțiune care apeleazăaddNewTrip(). Aceasta transmite acțiunea către interactor, care manipulează modelul de date.

du-te înapoi la TripListView.swift și adăugați următoarele dupăList brace de închidere:

.navigationBarTitle("Roadtrips", displayMode: .inline).navigationBarItems(trailing: presenter.makeAddNewButton())

aceasta adaugă butonul și un titlu la bara de navigare. Acum modificați return în TripListView_Previews după cum urmează:

return NavigationView { TripListView(presenter: presenter)}

aceasta vă permite să vedeți bara de navigare în modul Previzualizare.

reluați previzualizarea live pentru a vedea butonul.

Lista de călătorii cu buton în Previzualizare Live

văzându-l în acțiune

acum este un moment bun să ne întoarcem și să conectămTripListView la restul aplicației.

deschideți ContentView.swift. În corpul view, înlocuiți VStack cu:

TripListView(presenter: TripListPresenter(interactor: TripListInteractor(model: model)))

aceasta creează vizualizarea împreună cu prezentatorul și interactorul său. Acum construiește și fugi.

atingerea butonului + va adăuga o nouă călătorie în listă.

Lista de călătorii cu o nouă călătorie adăugată

ștergerea unei călătorii

utilizatorii care creează călătorii vor dori probabil să le poată șterge în cazul în care fac o greșeală sau când călătoria se termină. Acum că ați creat calea de date, adăugarea de acțiuni suplimentare pe ecran este simplă.

înTripListInteractor, adăugați:

func deleteTrip(_ index: IndexSet) { model.trips.remove(atOffsets: index)}

aceasta elimină elementele din colecțiatrips din modelul de date. Deoarece este o proprietate @Published, UI se va actualiza automat din cauza abonamentului la modificări.

înTripListPresenter, adăugați:

func deleteTrip(_ index: IndexSet) { interactor.deleteTrip(index)}

aceasta transmite comanda delete către interactor.

în cele din urmă, în TripListView, adăugați următoarele după bretele de capăt ale ForEach:

.onDelete(perform: presenter.deleteTrip)

adăugarea unui.onDelete la un element dintr-un SwiftUIList permite automat glisarea pentru a șterge comportamentul. Acțiunea este apoi trimisă prezentatorului, lovind întregul lanț.

construi și a alerga, și veți fi acum posibilitatea de a elimina excursii!

cu onDelete, acțiunea de ștergere este activată.

rutare la vizualizarea detaliată

acum este momentul să adăugați în partea de Router a VIPER.

un router va permite utilizatorului să navigheze din vizualizarea listei de călătorii în vizualizarea detaliilor călătoriei. Vizualizarea detaliată a călătoriei va afișa o listă a punctelor intermediare împreună cu o hartă a traseului.

utilizatorul va putea edita lista punctelor intermediare și numele călătoriei din acest ecran.

Yay Router!

Configurarea ecranelor de detalii ale călătoriei

înainte de a afișa ecranul de detalii, va trebui să îl creați.

urmând exemplul anterior, creați două fișiere Swift noi: TripDetailPresenter.swift și TripDetailInteractor.swift și o vizualizare SwiftUI numită TripDetailView.swift.

setați conținutul TripDetailInteractor la:

import Combineimport MapKitclass TripDetailInteractor { private let trip: Trip private let model: DataModel let mapInfoProvider: MapDataProvider private var cancellables = Set<AnyCancellable>() init (trip: Trip, model: DataModel, mapInfoProvider: MapDataProvider) { self.trip = trip self.mapInfoProvider = mapInfoProvider self.model = model }}

Acest lucru creează o nouă clasă pentru interactorul ecranului detaliu excursie. Aceasta interacționează cu două surse de date: un individ Trip și informații de hartă de la MapKit. Există, de asemenea, un set pentru abonamentele anulabile pe care le veți adăuga mai târziu.

apoi, înTripDetailPresenter, setați conținutul său la:

import SwiftUIimport Combineclass TripDetailPresenter: ObservableObject { private let interactor: TripDetailInteractor private var cancellables = Set<AnyCancellable>() init(interactor: TripDetailInteractor) { self.interactor = interactor }}

aceasta creează un prezentator ciot cu o referință pentru interactor și set anulabil. Vei construi asta într-un pic.

în TripDetailView, adăugați următoarea proprietate:

@ObservedObject var presenter: TripDetailPresenter

aceasta adaugă o referință la Prezentator în vizualizarea.

pentru a obține din nou clădirea previzualizărilor, schimbați acel ciot în:

static var previews: some View { let model = DataModel.sample let trip = model.trips let mapProvider = RealMapDataProvider() let presenter = TripDetailPresenter(interactor: TripDetailInteractor( trip: trip, model: model, mapInfoProvider: mapProvider)) return NavigationView { TripDetailView(presenter: presenter) } }

acum vizualizarea va construi, dar previzualizarea este încă doar „Bună ziua, lume!”

doar previzualizarea vizualizării implicite

rutare

înainte de a construi vizualizarea detaliată, veți dori să o conectați la restul aplicației printr-un router din lista de călătorii.

creați un nou fișier Swift numit TripListRouter.swift.

setați conținutul său la:

import SwiftUIclass TripListRouter { func makeDetailView(for trip: Trip, model: DataModel) -> some View { let presenter = TripDetailPresenter(interactor: TripDetailInteractor( trip: trip, model: model, mapInfoProvider: RealMapDataProvider())) return TripDetailView(presenter: presenter) }}

această clasă emite un nouTripDetailView care a fost populat cu un interactor și prezentator. Routerul se ocupă de trecerea de la un ecran la altul, configurând clasele necesare pentru următoarea vizualizare.

într — o paradigmă UI imperativă — cu alte cuvinte, cu UIKit-un router ar fi responsabil pentru prezentarea controlerelor de vizualizare sau activarea segues.

SwiftUI declară toate vizualizările țintă ca parte a vizualizării curente și le arată în funcție de starea vizualizării. Pentru a mapa VIPER pe SwiftUI, vizualizarea este acum responsabilă pentru afișarea/ascunderea vizualizărilor, routerul este un constructor de vizualizări de destinație, iar prezentatorul coordonează între ele.

în TripListPresenter.swift, adăugați routerul ca proprietate:

private let router = TripListRouter()

acum ați creat routerul ca parte a prezentatorului.

apoi, adăugați această metodă:

func linkBuilder<Content: View>( for trip: Trip, @ViewBuilder content: () -> Content ) -> some View { NavigationLink( destination: router.makeDetailView( for: trip, model: interactor.model)) { content() }}

aceasta creează unNavigationLink la o vizualizare detaliată oferită de router. Când îl plasați într-un NavigationView, linkul devine un buton care împinge destination pe stiva de navigare.

bloculcontent poate fi orice vizualizare SwiftUI arbitrară. Dar în acest caz,TripListView va oferi unTripListCell.

mergeți la TripListView.swift și modificați conținutulForEach la:

self.presenter.linkBuilder(for: item) { TripListCell(trip: item) .frame(height: 240)}

aceasta foloseșteNavigationLink de la Prezentator, setează celula ca conținut și o pune în listă.

Construiți și rulați, iar acum, când utilizatorul atinge celula, le va direcționa către o „Hello World”TripDetailView.

ecran de detalii Hello World

finalizarea vizualizării detaliate

există câteva detalii despre călătorie pe care trebuie să le completați în continuare pentru ca utilizatorul să poată vedea traseul și să editeze punctele intermediare.

începeți prin adăugarea unui titlu de călătorie:

înTripDetailInteractor, adăugați următoarele proprietăți:

var tripName: String { trip.name }var tripNamePublisher: Published<String>.Publisher { trip.$name }

aceasta expune doarStringversiunea numelui călătoriei și unPublisher pentru momentul în care se schimbă numele.

de asemenea, adăugați următoarele:

func setTripName(_ name: String) { trip.name = name}func save() { model.save()}

prima metodă permite prezentatorului să schimbe numele călătoriei, iar a doua va salva modelul în stratul de persistență.

acum, treceți laTripDetailPresenter. Adăugați următoarele proprietăți:

@Published var tripName: String = "No name"let setTripName: Binding<String>

acestea oferă cârligele pentru vizualizarea pentru a citi și a seta numele călătoriei.

apoi, adăugați următoarele lainit metodă:

// 1setTripName = Binding<String>( get: { interactor.tripName }, set: { interactor.setTripName($0) })// 2interactor.tripNamePublisher .assign(to: \.tripName, on: self) .store(in: &cancellables)

acest cod:

  1. creează o legare pentru a seta numele călătoriei. TextField va folosi acest lucru în vizualizare pentru a putea citi și scrie din valoare.
  2. atribuie numele călătoriei de la editorul interactorului la proprietateatripName a prezentatorului. Aceasta menține valoarea sincronizată.

separarea numelui călătoriei în proprietăți ca aceasta vă permite să sincronizați valoarea fără a crea o buclă infinită de actualizări.

apoi, adăugați acest lucru:

func save() { interactor.save()}

aceasta adaugă o caracteristică de salvare, astfel încât utilizatorul să poată salva orice detalii editate.

în cele din urmă, du-te la TripDetailView, și înlocuiți body cu:

var body: some View { VStack { TextField("Trip Name", text: presenter.setTripName) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding() } .navigationBarTitle(Text(presenter.tripName), displayMode: .inline) .navigationBarItems(trailing: Button("Save", action: presenter.save))}

VStack pentru acum deține un TextField pentru editarea numelui călătoriei. Modificatorii barei de navigare definesc titlul folosind prezentatorul publicat tripName, deci se actualizează pe măsură ce utilizatorul tastează și un buton de salvare care va persista orice modificări.

Construiți și rulați, iar acum Puteți edita titlul călătoriei.

Editați numele în vizualizarea detaliată

Salvați După editarea numelui călătoriei, iar modificările vor apărea după relansarea aplicației.

modificările persistă după salvarea

utilizarea unui al doilea prezentator pentru hartă

adăugarea de widget-uri suplimentare pe un ecran va urma același model de:

  • adăugarea de funcționalități la interactor.
  • conectarea funcționalității prin prezentator.
  • Adăugarea widget-urilor la vizualizare.

mergeți laTripDetailInteractor și adăugați următoarele proprietăți:

@Published var totalDistance: Measurement<UnitLength> = Measurement(value: 0, unit: .meters)@Published var waypoints: = @Published var directions: = 

acestea furnizează următoarele informații despre punctele intermediare într-o călătorie: distanța totală caMeasurement, lista punctelor intermediare și o listă de direcții care conectează aceste puncte intermediare.

apoi, adăugați abonamentele de urmărire la sfârșitulinit(trip:model:mapInfoProvider:):

trip.$waypoints .assign(to: \.waypoints, on: self) .store(in: &cancellables)trip.$waypoints .flatMap { mapInfoProvider.totalDistance(for: $0) } .map { Measurement(value: $0, unit: UnitLength.meters) } .assign(to: \.totalDistance, on: self) .store(in: &cancellables)trip.$waypoints .setFailureType(to: Error.self) .flatMap { mapInfoProvider.directions(for: $0) } .catch { _ in Empty<, Never>() } .assign(to: \.directions, on: self) .store(in: &cancellables)

aceasta efectuează trei acțiuni separate bazate pe schimbarea punctelor de parcurs ale călătoriei.

primul este doar o copie a listei waypoint interactor lui. Al doilea folosește mapInfoProvider pentru a calcula distanța totală pentru toate punctele intermediare. Iar al treilea folosește același furnizor de date pentru a obține indicații între punctele intermediare.

prezentatorul folosește apoi aceste valori pentru a furniza informații utilizatorului.

mergeți laTripDetailPresenter și adăugați aceste proprietăți:

@Published var distanceLabel: String = "Calculating..."@Published var waypoints: = 

vizualizarea va utiliza aceste proprietăți. Conectați-le pentru urmărirea modificărilor datelor adăugând următoarele la sfârșitul init(interactor:):

interactor.$totalDistance .map { "Total Distance: " + MeasurementFormatter().string(from: $0) } .replaceNil(with: "Calculating...") .assign(to: \.distanceLabel, on: self) .store(in: &cancellables)interactor.$waypoints .assign(to: \.waypoints, on: self) .store(in: &cancellables)

primul abonament ia distanța brută de la interactor și îl formatează pentru afișare în vizualizare, iar al doilea doar copiază peste punctele intermediare.

luând în considerare vizualizarea hărții

înainte de a trece la vizualizarea detaliată, luați în considerare vizualizarea hărții. Acest widget este mai complicat decât celelalte.

În plus față de desen caracteristicile geografice, aplicația se suprapune, de asemenea, pini pentru fiecare punct și traseul dintre ele.

Acest lucru necesită propriul set de logică de prezentare. Puteți utiliza TripDetailPresenter sau, în acest caz, creați un TripMapViewPresenterseparat. Acesta va reutiliza TripDetailInteractor deoarece împărtășește același model de date și este o vizualizare numai în citire.

creați un nou fișier Swift numit TripMapViewPresenter.swift. Setați conținutul său la:

import MapKitimport Combineclass TripMapViewPresenter: ObservableObject { @Published var pins: = @Published var routes: = let interactor: TripDetailInteractor private var cancellables = Set<AnyCancellable>() init(interactor: TripDetailInteractor) { self.interactor = interactor interactor.$waypoints .map { $0.map { let annotation = MKPointAnnotation() annotation.coordinate = $0.location return annotation } } .assign(to: \.pins, on: self) .store(in: &cancellables) interactor.$directions .assign(to: \.routes, on: self) .store(in: &cancellables) }}

aici, prezentatorul hărții expune două tablouri pentru a ține adnotări și rute. Îninit(interactor:), mapați obiectelewaypoints de la interactor laMKPointAnnotation astfel încât să poată fi afișate ca pini pe hartă. Apoi copiațidirections laroutes matrice.

pentru a utiliza prezentatorul, creați o nouă vizualizare SwiftUI numită TripMapView.swift. Setați conținutul său la:

import SwiftUIstruct TripMapView: View { @ObservedObject var presenter: TripMapViewPresenter var body: some View { MapView(pins: presenter.pins, routes: presenter.routes) }}#if DEBUGstruct TripMapView_Previews: PreviewProvider { static var previews: some View { let model = DataModel.sample let trip = model.trips let interactor = TripDetailInteractor( trip: trip, model: model, mapInfoProvider: RealMapDataProvider()) let presenter = TripMapViewPresenter(interactor: interactor) return VStack { TripMapView(presenter: presenter) } }}#endif

aceasta folosește ajutorulMapView și îl furnizează cu pini și rute de la Prezentator. previews struct construiește lanțul VIPER aplicația are nevoie pentru a previzualiza doar harta. Utilizați Live Preview pentru a vedea harta în mod corespunzător:

fereastra de previzualizare cu TripMapView

pentru a adăuga harta în aplicație, adăugați mai întâi următoarea metodă laTripDetailPresenter:

func makeMapView() -> some View { TripMapView(presenter: TripMapViewPresenter(interactor: interactor))}

aceasta face o vizualizare a hărții, oferindu-i prezentatorul.

apoi, deschideți TripDetailView.swift.

adăugați următoarele laVStack subTextField:

presenter.makeMapView()Text(presenter.distanceLabel)

Construiți și rulați pentru a vedea harta pe ecran:

vizualizare hartă lucrul în aplicație

editarea punctelor intermediare

caracteristica finală este adăugarea editării punctelor intermediare, astfel încât să puteți face propriile călătorii! Puteți rearanja lista în vizualizarea detaliată a călătoriei. Dar pentru a crea un nou punct de referință, veți avea nevoie de o nouă vizualizare pentru ca utilizatorul să introducă numele.

pentru a ajunge la o nouă vizualizare, veți dori un Router. Creați un nou fișier Swift numit TripDetailRouter.swift.

adăugați acest cod la noul fișier:

import SwiftUIclass TripDetailRouter { private let mapProvider: MapDataProvider init(mapProvider: MapDataProvider) { self.mapProvider = mapProvider } func makeWaypointView(for waypoint: Waypoint) -> some View { let presenter = WaypointViewPresenter( waypoint: waypoint, interactor: WaypointViewInteractor( waypoint: waypoint, mapInfoProvider: mapProvider)) return WaypointView(presenter: presenter) }}

aceasta creează unWaypointView care este deja configurat și gata de plecare.

cu routerul la îndemână, accesați TripDetailInteractor.swift, și se adaugă următoarele metode:

func addWaypoint() { trip.addWaypoint()}func moveWaypoint(fromOffsets: IndexSet, toOffset: Int) { trip.waypoints.move(fromOffsets: fromOffsets, toOffset: toOffset)}func deleteWaypoint(atOffsets: IndexSet) { trip.waypoints.remove(atOffsets: atOffsets)}func updateWaypoints() { trip.waypoints = trip.waypoints}

aceste metode sunt auto descriptiv. Acestea adaugă, mută, șterg și actualizează puncte intermediare.

apoi, expuneți-le la vizualizare prinTripDetailPresenter. ÎnTripDetailPresenter, adăugați această proprietate:

private let router: TripDetailRouter

aceasta va ține routerul. Creați-l adăugând acest lucru în partea de sus a init(interactor:):

self.router = TripDetailRouter(mapProvider: interactor.mapInfoProvider)

Acest lucru creează routerul pentru utilizare cu editorul waypoint. Apoi, adăugați aceste metode:

func addWaypoint() { interactor.addWaypoint()}func didMoveWaypoint(fromOffsets: IndexSet, toOffset: Int) { interactor.moveWaypoint(fromOffsets: fromOffsets, toOffset: toOffset)}func didDeleteWaypoint(_ atOffsets: IndexSet) { interactor.deleteWaypoint(atOffsets: atOffsets)}func cell(for waypoint: Waypoint) -> some View { let destination = router.makeWaypointView(for: waypoint) .onDisappear(perform: interactor.updateWaypoints) return NavigationLink(destination: destination) { Text(waypoint.name) }}

primele trei fac parte din operațiunile de pe punctul de referință. Metoda finală apelează routerul pentru a obține o vizualizare waypoint pentru waypoint și a o pune într-un NavigationLink.

în cele din urmă, arătați acest lucru utilizatorului înTripDetailView adăugând următoarele laVStack subText:

HStack { Spacer() EditButton() Button(action: presenter.addWaypoint) { Text("Add") }}.padding()List { ForEach(presenter.waypoints, content: presenter.cell) .onMove(perform: presenter.didMoveWaypoint(fromOffsets:toOffset:)) .onDelete(perform: presenter.didDeleteWaypoint(_:))}

aceasta adaugă următoarele controale la vizualizarea:

  • unEditButton care pune lista în modul de editare, astfel încât utilizatorul poate muta sau șterge puncte intermediare.
  • o adăugareButton care folosește prezentatorul pentru a adăuga un nou punct de referință în listă.
  • unList care utilizează unForEach cu prezentatorul pentru a face o celulă pentru fiecare punct de referință. Lista definește o acțiuneonMoveșionDelete care permite acele acțiuni de editare și apeluri înapoi în prezentator.

construi și a alerga, și puteți personaliza acum o excursie! Asigurați-vă că salvați orice modificare.

puncte intermediare adăugate la ecranul de detalii
editorul de puncte intermediare

făcând module

cu VIPER, puteți grupa prezentatorul, interactorul, vizualizarea, routerul și codul aferent în module.

în mod tradițional, un modul AR expune interfețele pentru prezentator, interactor și router într-un singur contract. Acest lucru nu are prea mult sens cu SwiftUI, deoarece este o viziune înainte. Cu excepția cazului în care doriți să împachetați fiecare modul ca cadru propriu, puteți conceptualiza modulele ca grupuri.

ia TripListView.swift, TripListPresenter.swift, TripListInteractor.swift și TripListRouter.swift și grupați-le împreună într-un grup numit TripListModule.

faceți același lucru pentru clasele de detalii: TripDetailView.rapid, TripDetailPresenter.swift, TripDetailInteractor.swift, TripMapViewPresenter.swift, TripMapView.swift, și TripDetailRouter.swift.

adăugați-le la un nou grup numit TripDetailModule.

modulele sunt o modalitate bună de a păstra codul curat și separat. Ca o regulă bună, un modul ar trebui să fie un ecran/caracteristică conceptuală, iar routerele înmânează utilizatorul între module.

unde să mergem de aici?

Faceți clic pe butonul Descărcați materiale din partea de sus sau de jos a tutorialului pentru a descărca fișierele de proiect finalizate.

unul dintre avantajele viperei de separare este în testabilitate. Puteți testa interactorul astfel încât să poată citi și manipula modelul de date. Și puteți face toate acestea în timp ce testați independent prezentatorul pentru a schimba vizualizarea și a răspunde la acțiunile utilizatorului.

gândiți-vă la el ca la un exercițiu distractiv de încercat pe cont propriu!

datorită puterii reactive a combinei și a suportului său nativ în SwiftUI, este posibil să fi observat că straturile interactor și prezentator sunt relativ subțiri. Ei separă preocupările, dar mai ales, ei doar trec datele printr-un strat de abstractizare.

cu SwiftUI, este un pic mai natural să se prăbușească prezentator și funcționalitatea interactor într-un singurObservableObject care deține cea mai mare parte starea de vedere și interacționează direct cu entitățile.

pentru o abordare alternativă, citiți MVVM cu Combină Tutorial pentru iOS.

sperăm că v-a plăcut acest tutorial! Dacă vă gândiți la întrebări sau comentarii, aruncați-le în discuția de mai jos. Ne-ar plăcea să auzim despre arhitectura dvs. preferată și despre ce s-a schimbat în epoca SwiftUI.

raywenderlich.com săptămânal

raywenderlich.com buletinul informativ este cel mai simplu mod de a fi la curent cu tot ceea ce trebuie să știți ca dezvoltator mobil.

obțineți un rezumat săptămânal al tutorialelor și cursurilor noastre și primiți un curs gratuit de e-mail aprofundat ca bonus!

evaluare medie

4.6/5

adăugați o evaluare pentru acest conținut

Conectați-vă pentru a adăuga o evaluare

33 evaluări

Lasă un răspuns

Adresa ta de email nu va fi publicată.