Kom godt i gang med Viper Architecture Pattern

VIPER architectural pattern er et alternativ til MVC eller MVVM. Og mens de hurtige og kombinerede rammer skaber en stærk kombination, der gør hurtigt arbejde med at opbygge komplekse UI ‘ er og flytte data omkring en app, kommer de også med deres egne udfordringer og meninger om arkitektur.

det er en almindelig tro på, at al applogikken nu skal gå ind i en hurtig visning, men det er ikke tilfældet. VIPER tilbyder et alternativ til dette scenario og kan bruges sammen med Viper og kombinere for at hjælpe med at opbygge apps med en ren arkitektur, der effektivt adskiller de forskellige funktioner og ansvar, der kræves, såsom brugergrænsefladen, forretningslogik, datalagring og netværk. Disse er så lettere at teste, vedligeholde og udvide.

i denne vejledning bygger du en app ved hjælp af Viper-arkitekturmønsteret. Appen kaldes også bekvemt VIPER: visuelt interessante planlagte nemme Roadtrips. Smart, ikke? :]

det giver brugerne mulighed for at opbygge vejture ved at tilføje rutepunkter til en rute. Undervejs vil du også lære om at kombinere og kombinere til dine iOS-projekter.

VIPER app hovedskærm

Kom godt i gang

Hent projektmaterialet fra knappen Hent materialer øverst eller nederst i vejledningen. Åbn startprojektet. Dette inkluderer noget kode for at komme i gang:

  • ContentView starter appens andre visninger, når du bygger dem.
  • Der er nogle hjælpervisninger i gruppen funktionelle Visninger: en til indpakning af MapKit-kortvisningen, en særlig “split image” – visning, som bruges af TripListCell. Du vil tilføje disse til skærmen i en lille smule.
  • i gruppen enheder kan du se de klasser, der er relateret til datamodellen. Tur og vejpunkt vil tjene senere som enhederne i VIPER-arkitekturen. Som sådan holder de bare data og inkluderer ikke nogen funktionel logik.
  • i gruppen datakilder er der hjælpefunktioner til at gemme eller indlæse data.
  • kig fremad, hvis du vil i gruppen Vejpointmodule. Dette har en VIPER-implementering af Rutepunktsredigeringsskærmen. Det er inkluderet i starteren, så du kan gennemføre appen i slutningen af denne tutorial.

denne prøve bruger et tilladeligt licenseret fotodelingssted. For at trække billeder ind i appen skal du oprette en gratis konto og få en API-nøgle.

følg instruktionerne her https://.com/accounts/register/ for at oprette en konto. Kopier derefter din API-nøgle tilapiKey variablen, der findes i ImageDataProvider.Hurtig. Du kan finde det i API docs under Søg billeder.

Hvis du bygger og kører nu, vil du ikke se noget for interessant.

VIPER app på starter project

men i slutningen af tutorial, vil du have en fuldt funktionel road-trip planlægning app.

Hvad er VIPER?

VIPER er et arkitektonisk mønster som MVC eller MVVM, men det adskiller koden yderligere ved et enkelt ansvar. Apple-stil MVC motiverer udviklere til at sætte al logik i en UIViewController underklasse. VIPER, som MVVM før det, søger at løse dette problem.

hver af bogstaverne i VIPER står for en komponent i arkitekturen: visning, Interactor, præsentator, enhed og Router.

  • visningen er brugergrænsefladen. Dette svarer til en hurtig View.
  • Interactor er en klasse, der formidler mellem præsentanten og dataene. Det tager retning fra præsentanten.
  • præsentanten er arkitekturens” traffic cop”, der styrer data mellem visningen og interactor, tager brugerhandlinger og ringer til router for at flytte brugeren mellem visninger.
  • en enhed repræsenterer applikationsdata.
  • routeren håndterer navigation mellem skærme. Det er anderledes, end det er i Svendborg, hvor udsigten viser nye synspunkter.

denne adskillelse er båret ud af” Onkel ” Bob Martins rene Arkitekturparadigme.

VIPER Diagram

når du ser på diagrammet, kan du se, at der er en komplet sti for dataene at flyde mellem visningen og enheder.

har sin egen måde at gøre tingene på. Kortlægningen af VIPER-ansvar på domæneobjekter vil være anderledes, hvis du sammenligner dette med tutorials til UIKit-apps.

sammenligning af arkitekturer

folk diskuterer ofte VIPER med MVC og MVVM, men det er forskelligt fra disse mønstre.

MVC, eller model-Vis-Controller, er det mønster, de fleste mennesker forbinder med 2010s iOS-apparkitektur. Med denne tilgang definerer du visningen i et storyboard, og controlleren er en tilknyttet UIViewController underklasse. Controlleren ændrer visningen, accepterer brugerinput og interagerer direkte med Modellen. Controlleren oppustes med visningslogik og forretningslogik.

MVVM er en populær arkitektur, der adskiller visningslogikken fra forretningslogikken i en Visningsmodel. Visningsmodellen interagerer med Modellen.

den store forskel er, at en visningsmodel, i modsætning til en visningskontroller, kun har en envejsreference til visningen og modellen. MVVM er en god pasform til hurtig, og der er en hel tutorial om emnet.VIPER går et skridt videre ved at adskille visningslogikken fra datamodellogikken. Kun præsentanten taler til visningen, og kun interaktoren taler til modellen (enhed). Præsentanten og interactor koordinerer med hinanden. Præsentanten er bekymret for visning og brugerhandling, og interaktoren er bekymret for at manipulere dataene.

en Viper slange, for sjov

definition af en enhed

VIPER er et sjovt akronym for denne arkitektur, men dens rækkefølge er ikke proscriptiv.

den hurtigste måde at få noget på skærmen er at starte med enheden. Enheden er dataobjektet(E) for projektet. I dette tilfælde er de vigtigste enheder tur, som indeholder en liste over vejpunkter, som er stop i turen.

appen indeholder en DataModel klasse, der indeholder en liste over ture. Modellen bruger en JSON-fil til lokal vedholdenhed, men du kan erstatte dette med en fjern bagende uden at skulle ændre nogen af UI-niveau-koden. Det er en af fordelene ved ren arkitektur: når du ændrer en del — som persistenslaget — er den isoleret fra andre områder af koden.

tilføjelse af en Interactor

Opret en ny hurtig fil med navnet TripListInteractor.Hurtig.

tilføj følgende kode til filen:

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

dette opretter interactor-klassen og tildeler den enDataModel, som du vil bruge senere.

opsætning af programlederen

Opret nu en ny hurtig fil med navnet TripListPresenter.Hurtig. Dette vil være for præsentationsklassen. Præsentanten bekymrer sig om at levere data til brugergrænsefladen og formidle brugerhandlinger.

Tilføj denne kode til filen:

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

dette opretter en præsentationsklasse, der har henvisning til interaktoren.

da det er programlederens opgave at udfylde visningen med data, vil du afsløre listen over ture fra datamodellen.

Tilføj en ny variabel til klassen:

@Published var trips: = 

Dette er listen over ture, som brugeren vil se i visningen. Ved at erklære det med @Published egenskabsindpakning, vil visningen kunne lytte til ændringer i ejendommen og opdatere sig selv automatisk.

det næste trin er at synkronisere denne liste med datamodellen fra interaktoren. Tilføj først følgende hjælperegenskab:

private var cancellables = Set<AnyCancellable>()

dette sæt er et sted at gemme kombinerede abonnementer, så deres levetid er bundet til klassens. på den måde forbliver alle abonnementer aktive, så længe præsentanten er i nærheden.

tilføj følgende kode til slutningen af init(interactor:):

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

interactor.model.$tripsopretter en udgiver, der sporer ændringer til datamodelens trips samling. Dens værdier er tildelt denne klasses egentrips samling, hvilket skaber et link, der holder præsentantens ture opdateret, når datamodellen ændres.

endelig gemmes dette abonnement icancellables så du kan rydde det op senere.

opbygning af en visning

Du skal nu opbygge den første visning: triplistevisningen.

oprettelse af en visning med en præsentationsvært

Opret en ny fil fra skabelonen hurtigvisning, og navngiv den Triplistvisning.Hurtig.

tilføj følgende egenskab til TripListView:

@ObservedObject var presenter: TripListPresenter

dette forbinder præsentanten til visningen. Løs derefter forhåndsvisningerne ved at ændre kroppen af TripListView_Previews.previews til:

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

udskift nu indholdet af TripListView.body med:

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

dette skaber et List hvor præsentantens ture er opregnet, og det genererer en forudleveret TripListCell for hver.

eksempelvindue for triplistevisningen

ændring af modellen fra visningen

indtil videre har du set datastrøm fra enheden til interaktoren gennem præsentanten for at udfylde visningen. VIPER-mønsteret er endnu mere nyttigt, når du sender brugerhandlinger ned igen for at manipulere datamodellen.

for at se det, tilføjer du en knap for at oprette en ny tur.

Tilføj først følgende til klassen i TripListInteractor.Hurtig:

func addNewTrip() { model.pushNewTrip()}

dette ombryder modelenspushNewTrip(), som opretter en nyTrip øverst på trips-listen.

derefter i TripListPresenter.Hurtig, tilføj dette til klassen:

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

dette opretter en knap med systemet+billede med en handling, der kalderaddNewTrip(). Dette videresender handlingen til interaktoren, som manipulerer datamodellen.

gå tilbage til Triplistvisning.hurtig og tilføj følgende efter List lukkebøjle:

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

dette tilføjer knappen og en titel til navigationslinjen. Rediger nu return i TripListView_Previews som følger:

return NavigationView { TripListView(presenter: presenter)}

dette giver dig mulighed for at se navigationslinjen i forhåndsvisningstilstand.

Genoptag live-forhåndsvisningen for at se knappen.

Tripliste med knap i live forhåndsvisning

ser det i aktion

nu er det et godt tidspunkt at gå tilbage og oprette forbindelseTripListView til resten af applikationen.

Åbn Indholdvisning.Hurtig. I kroppen af view skal du erstatte VStack med:

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

dette skaber visningen sammen med dens præsentator og interactor. Byg og løb.

Hvis du trykker på knappen+, tilføjes en ny tur til listen.

Trip List med en ny tur tilføjet

sletning af en tur

brugere, der opretter ture, vil sandsynligvis også være i stand til at slette dem, hvis de laver en fejl, eller når turen er forbi. Nu hvor du har oprettet datastien, er det ligetil at tilføje yderligere handlinger til skærmen.

iTripListInteractor, Tilføj:

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

dette fjerner elementer fratrips indsamling i datamodellen. Fordi det er en @Published ejendom, opdateres brugergrænsefladen automatisk på grund af dets abonnement på ændringerne.

iTripListPresenter, Tilføj:

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

dette videresender sletningskommandoen til interaktoren.

endelig, iTripListView, tilføj følgende efter slutbøjlen af ForEach:

.onDelete(perform: presenter.deleteTrip)

tilføjelse af et.onDelete til et element i en hurtigList aktiverer automatisk Stryg for at slette adfærd. Handlingen sendes derefter til præsentanten og sparker hele kæden af.

Byg og kør, og du vil nu være i stand til at fjerne ture!

med onDelete er sletningen aktiveret.

Routing til detaljevisningen

nu er det tid til at tilføje i Routerdelen af VIPER.

en router giver brugeren mulighed for at navigere fra triplistevisningen til tripdetaljevisningen. Turdetaljevisningen viser en liste over rutepunkterne sammen med et kort over ruten.

brugeren vil være i stand til at redigere listen over rutepunkter og turnavnet fra denne skærm.

Yay Router!

opsætning af Tripdetaljeskærme

før du viser detaljeskærmen, skal du oprette den.

Opret To nye hurtige filer i det foregående eksempel: TripDetailPresenter.hurtig og TripDetailInteractor.hurtig og en hurtig visning ved navn Tripdetailvisning.Hurtig.

Indstil indholdet af TripDetailInteractor til:

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 }}

dette skaber en ny klasse til interaktoren på tripdetaljeskærmen. Dette interagerer med to datakilder: en individuel Trip og kortoplysninger fra MapKit. Der er også et sæt til de annullerbare abonnementer, som du vil tilføje senere.

derefter, i TripDetailPresenter, sæt dens indhold til:

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

dette opretter en stub-præsentator med en reference til interactor og annullerbart sæt. Du vil bygge dette ud i en smule.

i TripDetailView, tilføj følgende egenskab:

@ObservedObject var presenter: TripDetailPresenter

dette tilføjer en henvisning til præsentanten i visningen.

for at få forhåndsvisningsbygningen igen skal du ændre den stub til:

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) } }

nu vil visningen bygge, men forhåndsvisningen er stadig bare ” Hej, Verden!”

bare standardvisningen forhåndsvisning

Routing

før du bygger detaljevisningen, vil du gerne linke den til resten af appen via en router fra triplisten.

Opret en ny hurtig fil med navnet TripListRouter.Hurtig.

Indstil indholdet til:

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) }}

denne klasse udsender en ny TripDetailView der er blevet befolket med en interactor og præsentator. Routeren håndterer overgang fra en skærm til en anden, opsætning af de klasser, der er nødvendige for den næste visning.

i et imperativt UI — paradigme — med andre ord med UIKit-ville en router være ansvarlig for at præsentere visningskontrollere eller aktivere segues.

erklærer alle målvisningerne som en del af den aktuelle visning og viser dem baseret på visningstilstand. For at kortlægge VIPER er visningen nu ansvarlig for at vise/skjule visninger, routeren er en destinationsvisningsbygger, og præsentanten koordinerer mellem dem.

i TripListPresenter.Tilføj routeren som en egenskab:

private let router = TripListRouter()

du har nu oprettet routeren som en del af programlederen.

tilføj derefter denne metode:

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

dette opretter enNavigationLink til en detaljeret visning, som routeren giver. Når du placerer det i en NavigationView, bliver linket en knap, der skubber destination på navigationsstakken.

content blok kan være enhver vilkårlig hurtig visning. Men i dette tilfælde vil TripListView give en TripListCell.

gå til Triplistvisning.hurtig og ændre indholdet af ForEach til:

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

dette bruger NavigationLink fra præsentanten indstiller cellen som dens indhold og sætter den på listen.

Byg og kør, og nu, når brugeren trykker på cellen, dirigerer den dem til en “Hej Verden”TripDetailView.

Detaljeskærm Hej Verden

afslutning af detaljevisningen

der er et par turdetaljer, du stadig skal udfylde detaljevisningen, så brugeren kan se ruten og redigere rutepunkterne.

Start med at tilføje en turtitel:

iTripDetailInteractor, tilføj følgende egenskaber:

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

dette udsætter bare String version af turnavnet og a Publisher for når dette navn ændres.

tilføj også følgende:

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

den første metode gør det muligt for præsentanten at ændre turnavnet, og den anden gemmer modellen til persistenslaget.

Flyt nu tilTripDetailPresenter. Tilføj følgende egenskaber:

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

disse giver kroge til visningen for at læse og indstille turnavnet.

tilføj derefter følgende til init metode:

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

denne kode:

  1. opretter en binding for at indstille turnavnet. TextField vil bruge dette i visningen for at kunne læse og skrive fra værdien.
  2. tildeler turnavnet fra interaktorens udgiver til tripName egenskaben for præsentanten. Dette holder værdien synkroniseret.

Hvis du adskiller turnavnet i egenskaber som dette, kan du synkronisere værdien uden at oprette en uendelig opdateringssløjfe.

tilføj derefter dette:

func save() { interactor.save()}

dette tilføjer en gem-funktion, så brugeren kan gemme alle redigerede detaljer.

endelig skal du gå til TripDetailView og erstatte body med:

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 for nu har en TextField til redigering af turnavnet. Navigationslinjemodifikatorerne definerer titlen ved hjælp af præsentatorens publicerede tripName, så den opdateres som brugertyperne og en Gem-knap, der vil fortsætte eventuelle ændringer.

Byg og kør, og nu Kan du redigere turtitlen.

Rediger navnet i detaljevisningen

Gem Efter redigering af turnavnet, og ændringerne vises, når du genstarter appen.

ændringer vedvarer efter at have gemt

brug af en anden præsentator til kortet

tilføjelse af yderligere kontroller til en skærm følger det samme mønster af:

  • tilføjelse af funktionalitet til interaktoren.
  • bygge bro over funktionaliteten gennem præsentanten.
  • tilføjelse af kontrollerne til visningen.

gå til TripDetailInteractor, og tilføj følgende egenskaber:

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

disse giver følgende oplysninger om rutepunkterne i en tur: den samlede afstand som en Measurement, listen over rutepunkter og en liste over retninger, der forbinder disse vejpunkter.

tilføj derefter følgende abonnementer til slutningen afinit(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)

dette udfører tre separate handlinger baseret på ændring af turens rutepunkter.

den første er kun en kopi til interaktorens rutepunktsliste. Den anden bruger mapInfoProvider til at beregne den samlede afstand for alle rutepunkterne. Og den tredje bruger den samme dataudbyder til at få retninger mellem rutepunkterne.

præsentationsværten bruger derefter disse værdier til at give information til brugeren.

gå tilTripDetailPresenter, og tilføj disse egenskaber:

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

visningen vil bruge disse egenskaber. Tråd dem op til sporing af dataændringer ved at tilføje følgende til slutningen af 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)

det første abonnement tager den rå afstand fra interaktoren og formaterer den til visning i visningen, og den anden kopierer bare over vejpunkterne.

overvej kortvisningen

før du går over til detaljevisningen, skal du overveje kortvisningen. Denne kontrol er mere kompliceret end de andre.

ud over at tegne de geografiske funktioner overlejrer appen også stifter for hvert punkt og ruten mellem dem.

dette kræver sit eget sæt præsentationslogik. Du kan bruge TripDetailPresenter, eller i dette tilfælde oprette en separat TripMapViewPresenter. Det vil genbruge TripDetailInteractor da den deler den samme datamodel og er en skrivebeskyttet visning.

Opret en ny hurtig fil med navnet Tripmapvisningspræsentation.Hurtig. Indstil indholdet til:

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) }}

Her udsætter kortpræsentanten to arrays for at holde kommentarer og ruter. I init(interactor:)kortlægger duwaypointsfra interaktoren tilMKPointAnnotation objekter, så de kan vises som stifter på kortet. Du kopierer derefterdirections tilroutes array.

Hvis du vil bruge præsentationsværten, skal du oprette en ny visning med navnet Tripmapvisning.Hurtig. Indstil indholdet til:

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

dette bruger hjælperen MapView og forsyner det med stifter og ruter fra præsentanten. previews struct bygger VIPER-kæden, som appen skal forhåndsvise kun kortet. Brug Live forhåndsvisning for at se kortet korrekt:

eksempelvindue med Tripmapse

for at tilføje kortet til appen skal du først tilføje følgende metode tilTripDetailPresenter:

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

dette gør en kortvisning, der giver den sin præsentator.

næste, åben Tripdetailvisning.Hurtig.

tilføj følgende til VStack under TextField:

presenter.makeMapView()Text(presenter.distanceLabel)

Byg og kør for at se kortet på skærmen:

kortvisning arbejder i appen

redigering af rutepunkter

den endelige funktion er at tilføje rutepunktsredigering, så du kan lave dine egne ture! Du kan omarrangere listen i visningen turdetaljer. Men for at oprette et nyt rutepunkt skal du have en ny visning for at brugeren kan indtaste navnet.

for at komme til en ny visning skal du have en Router. Opret en ny hurtig fil med navnet TripDetailRouter.Hurtig.

Tilføj denne kode til den nye fil:

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) }}

dette opretter en WaypointView der allerede er konfigureret og klar til at gå.

gå til TripDetailInteractor med routeren ved hånden.følgende metoder:

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}

disse metoder er selvbeskrivende. De tilføjer, flytter, sletter og opdaterer rutepunkter.

udsæt derefter disse for visningen gennemTripDetailPresenter. I TripDetailPresenter, Tilføj denne egenskab:

private let router: TripDetailRouter

dette vil holde routeren. Opret det ved at tilføje dette til toppen af init(interactor:):

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

dette opretter routeren til brug med rutepunktseditoren. Tilføj derefter disse metoder:

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) }}

de første tre er en del af operationerne på rutepunktet. Den endelige metode kalder routeren for at få en rutepunktvisning for rutepunktet og sætte den i en NavigationLink.

endelig, vis dette til brugeren i TripDetailView ved at tilføje følgende til VStack under Text:

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(_:))}

dette tilføjer følgende kontroller til visningen:

  • an EditButton der sætter listen i redigeringstilstand, så brugeren kan flytte eller slette rutepunkter.
  • et tilføj Button, der bruger præsentationsværten til at tilføje et nyt rutepunkt til listen.
  • A Listder bruger enForEach med præsentanten til at lave en celle for hvert rutepunkt. Listen definerer en onMoveog onDelete handling, der muliggør disse redigeringshandlinger og opkald tilbage til præsentanten.

Byg og kør, og du kan nu tilpasse en tur! Sørg for at gemme eventuelle ændringer.

vejpunkter tilføjet til detaljeskærmen
Vejpunktseditoren

gør moduler

med VIPER kan du gruppere præsentanten, interactor, visning, router og tilhørende kode i moduler.

traditionelt ville et modul udsætte grænsefladerne for præsentanten, interactor og router i en enkelt kontrakt. Det giver ikke meget mening, fordi det er udsigt fremad. Medmindre du vil pakke hvert modul som sin egen ramme, kan du i stedet konceptualisere moduler som grupper.

Tag Triplistvisning.hurtig, TripListPresenter.hurtig, TripListInteractor.hurtig og Triplistruter.hurtig og gruppe dem sammen i en gruppe ved navn TripListModule.

gør det samme for detaljeklasserne: Tripdetailvisning.hurtig, Tripdetail Presenter.hurtig, TripDetailInteractor.hurtig, Tripmapvisningspræsentant.hurtig, Tripmapse.hurtig og TripDetailRouter.Hurtig.

føj dem til en ny gruppe kaldet TripDetailModule.

moduler er en god måde at holde koden ren og adskilt på. Som en god tommelfingerregel skal et modul være en konceptuel skærm/funktion, og routerne afleverer brugeren mellem moduler.

hvor skal man hen herfra?

Klik på knappen Hent materialer øverst eller nederst i vejledningen for at hente de færdige projektfiler.

en af fordelene ved adskillelsen VIPER endorses er i testbarhed. Du kan teste interaktoren, så den kan læse og manipulere datamodellen. Og du kan gøre alt det, mens du selvstændigt tester præsentanten for at ændre visningen og reagere på brugerhandlinger.

tænk på det som en sjov øvelse at prøve på egen hånd!

på grund af den reaktive effekt af mejetærskeren og dens oprindelige støtte i Hurtigtui har du måske bemærket, at interactor-og præsentationslagene er relativt tynde. De adskiller bekymringerne, men for det meste sender de bare data gennem et abstraktionslag.

det er lidt mere naturligt at kollapse præsentations-og interactorfunktionaliteten i en enkelt ObservableObject, der holder det meste af visningstilstanden og interagerer direkte med enhederne.

for en alternativ tilgang, Læs MVVM med Kombiner Tutorial til iOS.

Vi håber du nød denne tutorial! Hvis du tænker på spørgsmål eller kommentarer, slip dem i diskussionen nedenfor. Vi vil meget gerne høre om din yndlingsarkitektur, og hvad der har ændret sig i en tid med Lyngby.

raywenderlich.com ugentlig

den raywenderlich.com nyhedsbrev er den nemmeste måde at holde dig opdateret om alt hvad du behøver at vide som mobiludvikler.

få en ugentlig fordøjelse af vores tutorials og kurser, og modtag et gratis dybtgående e-mail-kursus som en bonus!

gennemsnitlig bedømmelse

4.6/5

Tilføj en bedømmelse for dette indhold

Log ind for at tilføje en bedømmelse

33 bedømmelser

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.