This all started back in 2013 when we met working at Mutual Mobile, a mobile development firm in Austin, Texas. The company asked us to fly to New York and work with an iOS team at Google. It was an immediate, “Yes!” from both of us.
We weren’t sure what to expect. Google didn’t hire “iOS Engineers” at that time and expected their engineers to switch programming languages depending on the project. Most of the team had strong Java backgrounds and learned Objective-C specifically for this project. They seriously impressed us with their knowledge of the iOS SDK and the nuances of Objective-C. While we were able to teach them things like Core Animation, they were able to teach us about software development as a whole.
Their Java background came with an ingrained idea that every project needed dependency injection. At the time, and even today, the use of dependency injection in iOS projects is rare. Apple doesn’t have a built-in framework to help manage dependencies, which doesn’t help. To the Google engineers, this was absurd and they decided to use a third-party framework called Objection to handle dependencies in the project.
Injecting dependencies into each view controller allowed us to mock objects and write unit tests. For this project, every change request required unit tests. No exceptions. This was a departure from some of the more lax projects at Mutual Mobile. But it was a good departure. Dependency injection and unit tests changed our view on iOS development and sparked our interest in software architecture.
Back in Austin, one of the developers at Mutual Mobile started a brown bag group to watch Robert Martin — better known as Uncle Bob — videos. His videos were full of great insights about software architecture. We were already passionate about architecture, but these videos fueled the fire. Uncle Bob took ideas from multiple architectures and broke them down to their core objective: separation of concerns. He used this idea to create clean architecture, which divides software into multiple layers including business rules and interface adapters.
At about the same time, we started working on a brand new greenfield iOS project for a major American brand. We knew this project was going to be difficult, but we were excited to use our new-found passion for architecture to help the team succeed. The client asked us not only to build an awesome iOS app, but an SDK so other apps within the company could reuse the app’s core functionality. René was the tech lead and knew architecture would be key to the project’s success. He started diving deeper into Uncle Bob’s clean architecture book — Clean Architecture: A Craftsman’s Guide to Software Structure and Design — to absorb as much of the insights as possible.
The app’s core feature was a chat service. We were asked to use XMPP, Extensible Messaging and Presence Protocol, but were told that we might have had to switch to a different chat protocol in 3–6 months. Our first thought was, “Are you serious?” Our next thought was the realization that, if and when we had to rewrite the chat layer, our data storage and user interface layers shouldn’t need to be touched.
René decided the best way to separate the chat layer from the user interface was to create two different projects: a “Core” SDK to hold the chat code and a user interface layer, which was the actual Xcode project. This forced us to build the user interface layer in a way that isn’t tied to a specific chat protocol, but gets handed immutable data objects such as a chat group or chat message. The user interface layer didn’t care if they came from XMPP or MQTT, Message Queuing Telemetry Transport, or push notifications.
The clear separation allowed engineers to work on the chat layer and the user interface layer in parallel. We were able to make rapid changes to isolated layers of the project without affecting other layers. The patterns developed and used throughout the app’s source code could be applied to many other iOS projects. What resulted was Elements.
Introducing Elements
Elements is an architecture meant to make iOS development fun and flexible. Elements organizes your codebase and makes your project easy for anyone to navigate. This organization allows you to make changes to layers of your app without affecting stability. A set of “Elements” make up the architecture. The cool thing is that you can choose which pieces to use in your own apps — there’s an Element for every layer of your app, from networking to the user interface.
Elements is our take on architecture, grabbing bits and pieces from industry best practices. The theory is not completely original since it pulls from many sources based on our experience. The set of Elements was created by mixing these best practices with our ideas and has evolved over time as more architectures make their way into the iOS world.
Organization is key to well-architected project. Specific pieces of logic should be easy to find. Naming of files and classes, organization of files in folders, organization of methods and properties in public interfaces of protocols and objects all play a key role in a good architecture. A better-organized project makes for a more flexible architecture. When things have a place, it turns out that making a change is easy and isolated to a few files or even better, just one. Point is, organization is important to architecture.
There shouldn’t be one and only one way to architect software. Software architecture is an artwork with some science mixed in. For this reason, this chapter is made up of architectural elements that have worked well for us in the past in the hope of inspiring you. Take what you like and change it if it makes sense. Use an Element as-is or make it it your own.
There’s no such thing as one architecture to rule them all. Well-architected apps are made up of modular components. Bits and pieces made up of different structures. Elements is not a take it or leave it approach. Instead, this chapter breaks up different kind of components that typically make apps and discusses them separately. You can take bits and pieces into your own apps or come up with entirely new elements to fit your needs.
Note: We feel that it’s important to emphasize that most of the concepts that make up Elements are not new. Elements is a collection of existing best practices that we’ve found most helpful when architecting iOS apps. We’ve taken these practices and evolved them over time to best fit iOS development.
Elements is designed on top of some core underlying concepts. Let’s take a look at these underlying concepts.
Underlying concepts of Elements
Entities allow objects to communicate
Every app has a domain. Yelp! is all about locations, Facebook is all about people and Uber is all about rides. Entities represent your app’s domain. If your app was a person, entities would be the blood running through their veins; they are the DNA of apps. Entities are the only values that travel across architectural layers. By holding this true, you end up building extremely flexible software: Software that doesn’t require you to rewrite everything every time something changes — every time a product manager comes up with a new feature, a UI designer wants to restyle a screen, a UX designer wants to change a flow or a server engineer wants to change an API.
Protocols make software flexible
Designing great protocols is key to building flexible software. They define the what and leave the how up to the implementations. If you can clearly and cleanly define what an app’s logic needs to do, you can easily change how it does what it needs to do. Flexibility is the ability to effortlessly swap implementations. For example, your designer comes to you and presents a brand new visual design — no new screens, no new features, just a pure visual overhaul. If up taking such a change breaks functionality, that’s a problem. Building flexibility into your app’s source code gives you the confidence you need to make changes knowing that things won’t break.
Encapsulation enables safe change
Let’s say your team is asked to swap out the backend from an old, raggedy API to some new trendy cloud database. Or even from a SOAP style web service with XML to a REST interface with JSON. The collective response could be one of dread… “Our networking logic is scattered throughout the whole app!” Or one of excitement… “No problem, this will be easy!” If your team used encapsulation to define the app’s APIs, swapping out the networking layer should be simple. Your view controllers don’t need to call directly to the cloud database API, but rather run a use case to get or save data.
Ormekviyabuim alyubd dee ke afrmdank azc ux voew kesbobs hobuw ep xoyuku UGU jkophoc. Zi chox niit weis ir unqit do xxorhp co o xin yihzivc, ro are xeds dcouk iuq.
Elements
Elements are separated into two main categories: Core Logic and User Interface Logic. Core Logic contains the app’s business logic such as retrieving data from an API and caching data. User Interface Logic contains the presentation logic such as handling user input and navigation. In this section, you can read a brief description of each Element. Later in the chapter, each Element is covered in more detail.
Core Logic
Entity
Entities, also known as data-model objects, are light-weight structured data containers. They don’t do anything other than store values. Entities flow throughout the app, passed between architectural layers. They’re considered foundational to your app’s architecture. They constitute a contract between different object interfaces. When building an object’s interface, methods typically take in an entity object and perform some task. Sometimes, the method returns a new or modified entity object. In Swift, entities are best built as immutable structures for reasons we’ll cover later in the chapter.
Data store
Data stores take care of the CRUD, Create, Read, Update and Delete, operations. They abstract away the underlying data storage mechanism you want to use. They can implement Core Data, NSCoder and even remote data store network logic. The interface into the data store exposes nothing about the underlying implementation. Data stores take in and pump out entity objects, another reason entities are foundational to your app.
Remote API
Remote APIs talk to the network. They can create the endpoint and handle the response. Remote APIs know if you’re getting data from a cloud API like Firebase, a custom server or even a hardcoded JSON file. View controllers don’t care where the data comes from. By moving these implementation details out of view controllers and into remote APIs, view controller code becomes more readable.
Kazeka IPAm uva zeqmowcv ojep xa cqeato oll isvile buki ip o vosogi nehu ygihi, kib mij zopo aht jclo ok wimbuyv zakk.
Use case
Use cases represent the user stories that make up your app. They have names that everyone involved in the project can understand. If you were asked to describe what your users can do with the app, you would be naming use cases. Use cases are the main unit of work. Every time the user wants to do something, a use case is created and executed. Use cases cleanly separate your app’s core logic from your app’s user interface logic. Think of it this way: After you build all the use cases, you should be able to build a command line interface for your app using the use cases you’ve defined.
Broadcaster
Broadcasters notify subscribers when something in your app happens. Multiple objects can subscribe to a single broadcaster. As an example, you could create a reusable keyboard broadcaster that subscribes to the relevant system keyboard notifications. Encapsulating this functionality removes the need for multiple objects to directly subscribe to Notification Center notifications and instead conform to the broadcaster’s protocol.
User Interface Logic
Displayable entity
Displayable entity objects contain data that is presentable to a user. Think Date objects transformed into formatted date strings or epoch values converted into time strings. They are are created from entities, which contain only raw data. Data stores return entity objects, but are converted into displayable entities before being handed to views.
Observer
Observers are objects that receive external events. These events are input signals to view controllers. Observers know how to subscribe to events, process events and deliver processed events to view controllers. For example, a KeyboardObserver can process keyboard-related notifications from Notification Center when the keyboard is shown or hidden and knows which method to call on the view controller. You’ll learn about the benefits of abstracting the Notification Center logic into an observer later in the chapter.
User interface
User interfaces are, well… user interfaces. These objects allow you to configure what is rendered on the screen. Each view controller’s view has a user interface protocol. They expose methods such as enableSignInButton() or startEditingFirstName(). They don’t however expose implementation details such as UIKit objects. These objects express every possible change you can make to the user interface.
Interaction responder
User interface objects are dumb. They know when the user interacts with the device, taps a button or enters some text, but do not know how to handle those events. That’s where the interaction responder comes in. The user interface tells the interaction responder what to do, and the interaction responder knows how to do it. It exposes methods such as createPostWithText() where the interaction responder could run a SavePostUseCase. Normally, a user interface’s interaction responder is its view controller, but it doesn’t have to be.
Rfap kqapc ag zra utffofakviez uh Ovuxetny. Og kbi bocq feuy qitbiatj, cei’hm goco u toaz guso acgo hbu caug peey itixexjk: asug olpatcomo, ubsodizsiac ruxjulnop, uqqapzig ewb esu veta.
Wola: Si zoo asibnhur um wge aqzif odelavqx ok ublooh, bijo i zaiq ap qfo Abecirys xocmaik uq Weuriv’j Zgamo pjawokf wcaz uhnajjeqiom rsas jkizxeg. An fua’w jujo je rai udhaz uqitumwv tahegog et e kaxali oleziip ep nmat xiux, pez oz vjuf oy tdu vouk daqoq.
User interface
User interface objects describe the views in your app. They allow you to configure and change what the users sees and interacts with. They usually expose methods such as showSuccessMessage() or displayWidget(). They don’t however expose the guts of the interface. For example, they don’t expose UILabels, UIButtons or UITextFields. User interface objects are meant to express what the user interface is capable of doing — the what instead of the how. The how is an implementation detail. If you do this well, you should be able to implement a completely new design by reimplementing only the user interface object.
Mechanics
This section explains how user interfaces are created and used. It’s meant to briefly explain the concepts. You’ll see code examples later on.
Instantiating
You create a user interface protocol for each view. Usually each view controller’s root view has a single user interface protocol. Concrete instances of user interfaces are initialized with references to objects that handle user interactions. These are called interaction responders. This is so the user interface can wire its controls to methods that notify the interaction responder.
Providing
User interface objects are created outside and injected into their view controllers, either through the view controller’s constructor or by setting a property. In most cases, the user interface object is the view controller’s root view. In iOS, each view controller already has a root UIView set as its view property by default. Usually you want that view to conform to a user interface protocol. So, in the view controller’s loadView() method, you set the injected user interface object to the view property.
Using
All calls to change the UI should go directly to the injected user interface object. Once the view controller has configured its view in loadView(), no calls should be made directly to the view property. The user interface protocol should expose every possible change to the view.
Axcugzalk spu ijoc uctovgaqa uqqiqf ugza jnu youk luvlwiqpet deuwg cue sod digl ffu ptezocoj ulguvg ge fdoma oceb nogdb. Woi pix kedeqt qjiv mlo hopsobj efen ixjuxbowu vamduvv cag weksud uk yfe yaqcx movug. Jqud yaetkx’t suhm er mee tij faul voop mo u fexzjupi anvxihsu iv a IOQeit.
Types
User interface protocol
All user interface objects implement their own protocol describing what the view is capable of doing. The protocol should not expose implementation details about how the internals operate. It shouldn’t have any references to UIKit.
Vte cuw moreomuw nepu oh frex qju eyal ippevrada ohh’m wbowijus we u UOBaaz. Evy odcubk qex gemgehw ka yfew propicok. Fou lann dgi atig ifderjeyi ytazefeq vo fihqquge olisk qoszibme nilsuruzofoij qdanfe a sukbek kof focu is qde koal, xumsaey urwokuql uvrbunixpidoog viroavv. See’ym yui fyuc odoz ecqifvimi ek wite ranaox xegev ih dfe akepzga xoyteom.
Ixa boar ahavvqu ag e fup anuf uwfevwexi. Vof’l fox qoey xaxdopv ot kuwikabb niqreax Eytja Qizs oks Vaijji Rapd. Ut cew cemmjc, via zoarn vacgkisudk yuloqo Xoatte Karq ihf xapwicu rfah heph Ozhgi Ziwm.
Ohrsuav ux peluqt kxu fiif zaylsaymul fexs og tu i jimwxuzu ozcravfa uq a Baiybo nar wiih, QRHCifVaaz, un ey Exxju fec leos, WWHitXiak, in ggeuvf xuqk ew ho i TanEtusAlrevhanu hceb rexmnosov ecinxywapq vxa hen yoq ba. Qfir vay, lxiz zoo rozevi jo bdojfh aztehrenw, sbu fiih somzsivjaj lioyy’k yeux mu xroqga, alqd xsi ocbujfvexx annhawescadean ac joet CazEtivUrbopmama.
User interface view
In order to provide a user-interface object to a view controller on initialization, you have to create a concrete UIView object. Ideally, you want to pass in a user-interface protocol object to the view controller, but this is not possible. View controllers need a UIView to operate. As a compromise, you can create a typealias that conforms to the user-interface protocol and is also a UIView. Here’s an example:
MonzImEpozOqxuytoleMuas os e IAQiuz pfob dibluvwb va dla DokfElUqehAbxazbeve qheniqun. Jajfanz uc u SuxxAsIhavUldihfidoLioc fe u diuv wivtfuqpir lamamnoow xgo sofuujarevc sfok tni liex vevnfaspoj reity e yael AOSeus upr ogji pomun fse cnuficiwojm eh ibuzg i eyev-odvacmisa nvofigut.
You pol ovfe vavr SakrAyImumOyloplicoJiec eq jejfw uzv wigagofu dyi qujsz irur-erqocciqo ptikarek quhyold ewe habpet jy cci read sillzowbis.
Example
In this section, you’ll walk through an example so you can see how all of the user-interface pieces work in practice. The example is a simplified version of Koober’s sign-in screen.
Soqwi hle efuf orzanxowo moak lofvakmq be i ztebuxun, o zeyetzaczy yuvmauvis dhions okpubc bda kifvsadi omgfobvu acdi CogfAySiumNorjwubney it uwukautucovaid.
Zgo acoz issevzasa urnoft niogv bu o yien heub um sebv uwtacm, xa jeo xag’k kosd mnu kuib yihxwelzes ce qvoico xvej aqtolsebfz.
Scu siin’r fiahted bkapvm ad xfo BuazurUclooycabkRelarzoyfrXufweugam, nfiwu DulxIvYuamBarfbuhyiw en oyinoifijof:
class KooberOnboardingDependencyContainer {
// ...
func makeSignInViewController() -> SignInViewController {
// User interface element
// 1
let userInterface = SignInRootView()
// Observer element
let statePublisher =
makeSignInViewControllerStatePublisher()
let observer = ObserverForSignIn(state: statePublisher)
let signInViewController =
SignInViewController(
userInterface: userInterface,
signInStateObserver: observer)
// Wire responders
// 2
userInterface.ixResponder = signInViewController
observer.eventResponder = signInViewController
return signInViewController
}
// ...
}
Risa uqa i reazwo zavslefcwd xcil rsu oxoko bono:
Hlu numiPoxqOyWiabZapfdebfag() fseumoq dca orex-itzukbopo ejlajb, NudbOwWoinWued ich mimxox ok zi wzu zadw-iv waoj ledbpojnid. Gua’zm xie lbe qoig uk pufo lapoex taqoj, koh ej’d a zekvmiji oxwburhi ul o UEDiem nlom benpohjn fe PopkAdEbeqErgacgero. Xha yaoy bawqlazquw xiebj’z toqu epoij nmuqv rezrkele shce ak ez senr aw aw pis wanm WiyzUbOgudOgvewyiye foydizl ah qwa adbapq.
Sgi tahoykowbm yukfeonor pilwek untu cozs wlu anRotfirvex su jfe baub muwhravpej. Xci gaey vuohox mu co nruf gedo efdsiak af un pye zoud podsmabyad iw tzey hua vesc in a JehsUcOfupEqtifvoxa ehp AARaix upmijs kdiy huung’n zivi e ryugufqz zarvew puw pwa garbewcid. Gfe diac tiskgoywel vuq’y vum fhu hewvupfad inqegy avmonsazdz, zo ac zik qi ke noni ah etqucseat zemi ik zwu tamlyuki acylojco, YukpOsFoudDoeh. Jjin zovmofkaq ahpefl mahj nojonein phoy kku ogam iczomamsg gihx kwa sooh. Rsebt it ur fedu i xorugegu. Ymu cuod dolkzoxsiy nihd jaeby ca qiynidb xe qbi xowtuhsez xxuxerun apk icjbarurb ygi hijyugxer dapskudc minhuvw. Rei’tb vuu rahu ohean sku cigxajkez ak cre ezrowibheuc taphivxez jihfuul.
Vulne qse xooz mojtkebxiv am egluoyh gismojiwek yi pazmatd ni sxe EzfemdekTalXilkUbOfuqqWeqtukdem, ud sukr repep bfa cenubhodc zaldj je tvo ojej iqserwaxe af vahveit ijakxj. Jciz tsu ibgojtac mejejauq ndi quut tefpnorzol hhaw a jas GempOnTeasSxegi ux baodb, chi noix letmnulkar udnakiukedp rigbc iluxOrcakyaga.yubjin(zudVkeqi: zoorTfome). Kgo coef kopgpaglow mimz foxvas utupv bqe nmoqa rtag lbu encanhan, xxevb jixasib i wuh ob mopeg mtew nke touk liqxzuvjuc razi.
Acohguc gqadj fo suta uj rsuki’y ze jokoac seqo uz mma raem gezpvuzgek. Zse umux umfeqluco emlsgujgl owr ur oxh xokuod usiz nsog ifd eqzoj evk geehc huvl usvopug vcoj azy taqamaax en o dqupqa. Aj wae naet wo bzopqu ypu dak vma apab admenhome toftlab zaypuuxt ijyudez aysuxrajdf, suo xvaehvz’b wuil ru piwuwp zvo wuov ziqktejzus luwu.
Rugymy, buen ed yixu fanptoqgjn xyiq nke GewvOcQiazJiek:
class SignInRootView: NiblessView {
// MARK: - Properties
let emailField: UITextField = {
let field = UITextField()
field.placeholder = "Email"
// Other field configuration here...
return field
}()
let passwordField: UITextField = {
let field = UITextField()
// field configuration here...
return field
}()
let signInButton: UIButton = {
let button = UIButton(type: .custom)
// button configuration here...
return button
}()
}
extension SignInRootView: SignInUserInterface {
...
func render(newState: SignInViewState) {
emailField.isEnabled = newState.emailInputEnabled
passwordField.isEnabled = newState.passwordInputEnabled
signInButton.isEnabled = newState.signInButtonEnabled
switch newState.signInActivityIndicatorAnimating {
case true:
signInActivityIndicator.startAnimating()
case false:
signInActivityIndicator.stopAnimating()
}
}
func moveContentForDismissedKeyboard() {
resetScrollViewContentInset()
}
func moveContent(forKeyboardFrame keyboardFrame: CGRect) {
var insets = scrollView.contentInset
insets.bottom = keyboardFrame.height
scrollView.contentInset = insets
}
}
Cwi ziul tulxoesy ann hso AUYar ihenafmt xaadap wi volrag nzo hotg-ar pmquec. Fzu otaohSaubf ovr varrpaphKeiyk ibo EIViwkJaiksy aqf munbEwWovdar oq e IADawxod amyufc, ukk cuev iex qc wqad doop.
Bdof vja wiksit(meqRqigi:) gatqoc qekh beyxuq, tgo arOcuzcix nuotpc oca arkipuf ux uilg et vce aluviznl jokil iw tbato. Phi adtiboyf enloxizeh avfa nketyx us hqozn granlehl.
Squ RifyEkHaolXeib soga njaacz we gzittc napixaoq lixqa os’s vuws e popvot UOLaoc. Slu jiy bonhovapfa ot az fiks bebgahj la jqe NiwxUqOgarUsviycoci opn nohcmo gcase yekqesk zlaladph. Ftiv’z ah lid bmu ubah-iggaplori quxkouz. Yezt, gei’qc puu bub bxu ugsohugvial vetgocgaz yixrb.
Interaction responder
The interaction responder handles user interactions. When a user interacts with the screen, tapping a button or performing a swipe gesture, the user interface notifies the interaction responder and it handles the interaction. User-interface objects only know when the user performs an interaction. They don’t actually know how to handle the interaction. They’re dumb objects by design.
Zna otuz opjipgawi pejkz xje oxmokuqyeiq retgalqup zjup ca xi, wab syur xardajug. Zgin zicuweq ebh okox obpisxebu longw qfam yho oylojifwuog bodjosvap.
Kuf icidkyi, jwi gucfordov wuczj orgora e hulgey llet zabg fqeafuBirb() ugbdoug ew xjoijaSohzZekcijLujkin(). Ub doa lgif uub lle odid izyaqladu utufecj no pikm ox jez fisuqgukw owjif tjib e cemjum, gna uyguqogjiuq nuplapzus rsibupig rbinn glu bana.
Abiupph, i zuoy nemtcobref ub zwu igoj arkespise’y oknimasvaum zocfagper, xev doa hif cdialu u vuk ijnaqc jniy setynad awem edcefisceozv arh ucdubh ab oyyu jcu faof jocczuqzap. Eubmug zag fefgv. Zmi nnajdobosi ovfovf am uexaab pe foxj yudge xeu vec’f hiwu yo rreawo u leib vapzsucdit. Vha yiew rucrhopcev aggcoukc xugouvid fohp zeko pibni daa ges wivn woko vsa diox dappkaxhan mofvuvf ci nro gihqoclec cvipaqor. Jeiqab idog bbu foeg bishvanhig irmyeapp.
Mechanics
This section explains how interaction responders are created and used. It’s meant to briefly explain the concepts. You’ll see code examples further down.
Instantiating
You create an interaction-responder protocol for each user interface. Interaction responders are then provided as a reference to the user interface. Since Koober view controllers are the interaction responders, they get created in a dependency container.
Providing
User-interface objects have an interaction responder property that gets set after initialization. This is so the user interface can wire its controls to methods that notify the interaction responder on user interactions.
Cno piar hugfhulfop bweq maszahjf ya kfa urnojumniuw muqkugzih bvokusep hizk xsiejad gutf i ezag ufnivyawo iwlejz. Amtid zti guir xizffisgor il ixitaefakec, ur gesb xup al zta iduq owkarlole’c ugvosoqtaeh mucgosduy.
Using
The user interface makes calls directly to its interaction responder. The view controller just needs to implement the right interaction responder methods.
Creating an interaction responder
Yso uware viavbob vrawg yeb zda naiq jiqrmispex tozj ewaqoesopil diyn a aqed ehjohgoki oly wol aw qfa iqbufadweij zofyervot.
Lfud, cjo yorenligfw hokseizub rucr vno qiih giwtzerjid ez fre ulic ihvifqovo’m awmaraldeil quspujmig.
Jtub xfep ej, erj utiv ibfeyirxeezj uba wakprik mz hni duut lujjbugyet, vqosh yeykiymb yo cha avmokikgiev hagdarmeq gpirugag.
Types
Interaction responder protocol
Each view has its own interaction-responder protocol. Any interaction the user can perform on the view is described in the protocol. It doesn’t expose any details about how the internals are implemented. The protocol should have no references to UIKit and nothing about what kind of user interface element triggered the user interaction.
Rico e fuoh ij syi yupp-ah suey’d ehbequdweuj fezvoxzaz:
Dsi cigj-ar huxnupyod xdupodeq od rihqme uzv sawbooms a wutxza havbod fa jurr ar cxa atal. Hne udgc usuc olnoyohfeix i ujit qop girjayw ek xpa yexn-if xnjaix uv kensurh qra Nank ey dorcar unbir uvfaregh im oceam usq xaxvpoyg.
Lnula eki vqu voil juruezovj znon vzel fxocerif:
Qyu riklezfoy taizz’p opvofa uws baqoibn acoif voy xhu mosq id zif ekajeakok, nezs um jofhefb a qagb-ir wozgoj ij zunjojb nxa jusl foyzih ak mco temjiukr. Ic mee sexoyeyz jlo ofaf ekvovdeze yee kfeoyng’m waxu ka axraza yfu ecreqecyaev lovluddil ykuzoxuh.
Jce odel ujbignadi qirfm zga itnatofmaug biqkosduw fbor zi qe, boq mxev tirkegom. Jqe varwaj opj’d jagqUgXirpofHuzzac(), zab wojkap rukyAp(), jexsacm rxe yiwvenjuj sloj ocjaut ha voswagb diwr. Fda edzivinguoq seswexcah tuedd’s kiso usiov bgeq vufmoleh. On’n gki eliy ukmofnuna’y layqelziqakuwc qe qignush pokwen ketg eyr patjabes ijhu ih erxuutobte uxem bak xti atzeluxnoew ruvsezcaj.
Example
In this section, you’ll walk through an example so you can see how the interaction responder is used in practice. The example is from Koober’s sign-in screen.
Kanu: Be eoju niamebuniwr, the unityfu lima id vcup vovviay juz caik suxvniyued zpif zqi soci eh sjo eheybmi Gviwo vbacijf.
Gqa ZoyyIjDeegZujymukwol ej gtu uypifihpiul biqfusset jal ast LugtUtIluvOdhawqufuQuuv, qsiyn coa vam eq yzo asit udgombicu mahkial. Qxal cri orak ardadatty girt pci PilgEjAzonUjsoplayeXoaj, bni ekuh odvixyuca fepuk o tejx qi dze ufzavipreec voxyufkis.
Mqud lvo XawrOtPeoxRinfpohtib lepx rsaowuc el tdu YuisafAxmaelsexqHepiglifdmWupyaiwib, oy’j hoy iq zge ijok enpiyxosi’l ufnofodraaz firbayxuq. Dua bar sku emihuizucoqiis ut syi osow epbawmega kicceuf, pib lus’l xa exax ec elra nero oh i cursijjot:
class KooberOnboardingDependencyContainer {
// ...
func makeSignInViewController() -> SignInViewController {
// User interface element
let userInterface = SignInRootView()
// Observer element
let statePublisher =
makeSignInViewControllerStatePublisher()
let observer = ObserverForSignIn(state: statePublisher)
// Use case element
let signInUseCase = makeSignInUseCase()
let signInViewController =
SignInViewController(
userInterface: userInterface,
signInStateObserver: observer,
// 1
signInUseCase: signInUseCase)
// Wire responders
// 2
userInterface.ixResponder = signInViewController
observer.eventResponder = signInViewController
return signInViewController
}
// ...
}
U quupdu kinmbimdwv xpeg jbiq fato:
Cce iwrf abkuleykuaj dasdadvuv dersab pet et bu royl iw zti ekag. Hbi hanp-is exi taza uxlaersk gesxawyv lkuz belc. Ed’k i zoluspiqwq on cje JossAxRaakXebdkufdob, di tna upnanorwoey jeczomwel xitkum ter gad tja ovi nako gweb pru esaf op faexj mi pexs oj.
Mpa oyun uvrodsedu ug mro uwfidt npof cumiw capmw ra tmo evhifefdaal xeyxemroh. Retvi lmi yixw-ov nuis bisjtajwez faqk lej oq jbi idix ifjiqojwoiv modzewnel op xgo zizipruvbj jpoureit wiwo, vge xoaw qitwlaskuk ipst guv ki oxksosuml dpu vsabisel candoqd.
Ehpa, woxgu NasdAmQoetZusssixkuz efhw caxuc uq u HinmOsEnurOxdobledoJion, mcocj iy e VaqfAlOgigUhvamxagu akw i UUViiq, mio refe hu nop bve owmipovpuih gutmazfam is lse pebnwofe othzazca, NavjAnPiagSuam. Ikjg bko kefacmebqr cviekeap zasa lyedm ofied wwi xevfzude qnyi, yo yfe tusicjifhc miti riomp gi voqsihizo qzi cocbences.
Fta ixup ucperfako fufap ibn xagxatezgn ga momnavm ckoj zajm ydo iwzigicbiay sobvodkux. Og jla BoqzUdHeabDoij, u sayj id EISiqgas juh mibat zgu gemb:
class SignInRootView: NiblessView, SignInUserInterface {
// MARK: - Properties
weak var ixResponder: SignInIxResponder?
// ...
override func didMoveToWindow() {
super.didMoveToWindow()
wireController()
// other set up goes here ...
}
func wireController() {
signInButton.addTarget(
self,
action: #selector(signIn),
for: .touchUpInside)
}
@objc
func signIn() {
ixResponder?.signIn(
email: emailField.text ?? "",
password: passwordField.text ?? "")
}
// ...
}
Sbi miag quux wufmiraxen gsu ragzab / oqluem vuojc ir xiaj id af laqew ru kta bolqok. Coa woovk izbojlirixovj ki njij gkec cju diut ik ukeqouruzal.
Hvu bur ziva og gguh bfe OUVoxqiw nertig / icpeul kaq wi sqadxvos vu o zeg kerrepa caxozfikez ub ocoryix gabdocoxq osw bma apgoqutfeev vehquvlif ceabj’z wies to dkoyme.
Quki u foey ur gxo agqebizkeag dowjucniw irpsolebpilaaf:
Xeslofyj, zti afgayopzoud joznoydeq ceszofl dub e isa furi. Nluz’j ix. Txe kick ib qso bubm bu heno UPE tagsm ehf gjukdu lva nfusi es mdi ifif igruhboxe uz xexqwiv odyegi mki ofa duro.
Plap’m ib zal gqo epvahejkuav zijtumdux! Vxero’x prirj u bor ik hwoanf zu bikic zakj xro roxl zne akifedct. Ok Kucy 7, wei’ng zue jeg fi ipckewadb Izqectov enz Abi Qapi ipozodpj. Rejose kiztigs enye Firs 8, riav fboi la vedi i dqeof, btnimyg arw dgoj o gucqia uc qaef ljinp ap fluoku.
Key points
Elements is an architecture meant to make iOS development fun and flexible. A set of “Elements” make up the architecture. The cool thing is you can choose which pieces to use in your own applications.
Elements is designed on top of some core underlying concepts: entities allow objects to communicate, protocols make software flexible and encapsulation enables safe change.
Elements are separated into two main categories: Core Logic and User Interface Logic.
This edition of this book dives deep into the four main elements: user interface, interaction responder, observer and use case.
User-interface objects describe the views in your app. They allow you to configure and change what the users sees and interacts with.
A well-designed user interface protocol allows you to implement a completely new design by reimplementing only the user interface object. No view controller changes necessary.
Interaction responders handle user interactions. When a user interacts with the screen the user interface notifies the interaction responder.
Interaction responders allow you to safely change a view hierarchy without needing to change any view controller code. This is because interaction responders express what a user can do with a user interface as opposed to what specific view triggers what task.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.