The post Human Target song playthrough appeared first on Jaul Records.
Mixing session for “Teetering” by Wolfox
The post Mixing session for “Teetering” by Wolfox appeared first on Jaul Records.
Recording session for “Tired of Sex” by Weezer
The post Recording session for “Tired of Sex” by Weezer appeared first on Jaul Records.
Recording session for “Teetering” by Wolfox
The post Recording session for “Teetering” by Wolfox appeared first on Jaul Records.
Let’s Play “Spec Ops: The Line” pt. 3
The post Let’s Play “Spec Ops: The Line” pt. 3 appeared first on JaulPwns.
Let’s Play “Spec Ops: The Line” pt. 2
The post Let’s Play “Spec Ops: The Line” pt. 2 appeared first on JaulPwns.
Let’s Play “Spec Ops: The Line”
The post Let’s Play “Spec Ops: The Line” appeared first on JaulPwns.
Over-engineering HTML tables with React, TypeScript, Electron
https://github.com/Adeptry/meal-planner
The post Over-engineering HTML tables with React, TypeScript, Electron appeared first on pauljonescodes.
Over-engineering tables in TypeScript and ChakraUI
The post Over-engineering tables in TypeScript and ChakraUI appeared first on pauljonescodes.
Converting from react-data-table-component to react-table
The post Converting from react-data-table-component to react-table appeared first on pauljonescodes.
Freelance full-stack developer vlog stream Feb. 22nd, 2022
The post Freelance full-stack developer vlog stream Feb. 22nd, 2022 appeared first on pauljonescodes.
Coding a web app with IndexedDB, Electron, Chakra
The post Coding a web app with IndexedDB, Electron, Chakra appeared first on pauljonescodes.
Coding a web app with IndexedDB, Electron, Grommet
The post Coding a web app with IndexedDB, Electron, Grommet appeared first on pauljonescodes.
Coding a web app with IndexedDB and Electron
The post Coding a web app with IndexedDB and Electron appeared first on pauljonescodes.
Live coding a progressive web app
The post Live coding a progressive web app appeared first on pauljonescodes.
Human Target – Demonstration Tape EP
The post Human Target – Demonstration Tape EP appeared first on Jaul Records.
Developing iOS apps with MVC: A practical example
Last week, I explored what the design pattern “Model-View-Controller” (MVC) is and created a playground to demonstrate the idea. But what does this look like a full-fledged application with API calls and Storyboards and design requirements? This week, we’ll see how that plays out.
Design
Imagine you’ve got a new project to do, and design have given you what to implement. It’s a news app which hooks up to NewsAPI, which generously allows you to request news sources and articles for free. Here’s what your designer hands off to you:
The application is very simple, but it does everything you need a news reader to do:
- We have a launch screen
- A list a square icons for each news source
- A table of news articles, with images, the author, and the title
- A web view with the full article
Planning
How does this design decompose into MVC components?
View Controller
Let’s start with view controllers, as view controllers are almost 1:1 with a designer’s screens. In this case, the launch screen will be done simply in our LaunchScreen.storyboard
file, we’ll have a SourcesViewController
, an ArticlesViewController
, and then we’ll reach for the pre-built SFSafariViewController
for the last view.
View
What views will each of these view controllers have? The launch screen will only need a UILabel
, there isn’t much to do there. The SourcesViewController
probably needs a UICollectionView
, with a custom SourceCollectionViewCell
that we’ll make. The ArticlesViewController
would be best as a UITableView
, along with a custom ArticleTableViewCell
.
Model
To determine what our model layer looks like, we cannot go from the designs, as it’s not a visual component, but rather, we must check the API documentation from NewsAPI.
Let’s start with the documentation for the response from the source’s API call:
status | (string) – If the request was successful or not. Options: ok, error. In the case of error a code and message property will be populated. | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
sources | (array) – A list of the news sources and blogs available on News API.
|
||||||||||||||
sortBysAvailable | (array) – The available headline lists for the news source. The possible options are top, latest and popular.
|
There’s no one right way to do this, but roughly, our models are going to follow the structure and variables of our API. What I read here is that we’re going to need a SourcesResponse model with the status variable and the sources as an array of Source models. A Source model has an ID, a name, a description, a URL, a category (probably best to make this an enum, as it has a closed set of possible values), a language, and a country (again both these last ones are best as enums).
Let’s check out the documentation on the articles response:
status | (string) – If the request was successful or not. Options: ok, error. In the case of error a code and message property will be populated. | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
source | (string) – The identifier of the source requested. | ||||||||||||
sortBy | (string) – Which type of article list is being returned. Options: top, latest, popular. | ||||||||||||
articles | (array) The list of headline metadata requested.
|
Again, there’s no one right or settled way to turn JSON into model objects, but what I see here is that we’re going to need a ArticlesResponse model with an array of Article objects, which each have an author, description, title, url, urlToImage, and published at, all as Strings.
So in all, if we give a Swift struct to each entity and a Swift enum to each closed set of values, we’ll end up with these models:
- Article
- ArticlesResponse
- Source
- SourcesResponse
- Category
- Country
- Language
- Sort
Visualisation
How does this look like in the graphs we made last week? Let’s take the ArticlesViewController as an example:
But of course, this single MVC group is going to have to work with all the others in the full implementation. That looks something like this:
But this is beginning to get messy, so let’s separate all of our files into their respective layers:
Development
Now that we have a plan, it’s time to set it in motion. Fortunately, not only are we working with a designer, but an iOS architect that has created a shell of project for us using the information above, but it’s going to be up to us to add the implementation. You can download that shell here. You’ll note that it includes an API client pre-built for us, and the creation of an API client is outside the purview of our MVC discussion but it’s a very worthwhile topic I’ll devote time to in a latter post.
View
First, you should build your Storyboard to spec. First, add the label to the LaunchScreen.storyboard file. You’ll need one UINavigationController and two UIViewControllers, one with a UICollectionView and one with a UITableView. The collection view and table view will need a custom cell each, with an image view and labels each. Refer to the design to get this just right.
Now that we have these built in Storyboard, we’ll need to create corresponding source files for each cell type. Here’s what that SourceCollectionViewCell should look like:
class SourceCollectionViewCell: UICollectionViewCell, ReuseIdentifiable { static var ReuseIdentifier : String { return "SourceCollectionViewCell" } @IBOutlet weak var iconImageView: UIImageView! @IBOutlet weak var nameLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() prepareForReuse() } override func prepareForReuse() { super.prepareForReuse() iconImageView.image = nil nameLabel.text = "" } }
You should use this as an example to build ArticleTableViewCell, which is much the same in form. You should also use this opportunity to connect the table view and collection view as IBOutlets to their respective UIViewController and set their delegates and data sources to, also, their respective view controller.
Model
Now that we have the easy stuff out the way, let’s build our first model. The article is a good place to start, I suspect it looks something like this:
struct Article { let author: String? let description: String? let title: String? let url: String? let urlToImage: String? let publishedAt: String? }
We’re choosing a struct because it is a lighter entity in the Swift language than a class, but really there are reasons to go either way. These models do not exist in a vacuum however, we need to transform these models from JSON into structs. Fortunately, our architect has given a convenient means of doing so with the JSONTransformable protocol, which is very simple:
protocol JSONTransformable { init(json: Any) }
When our model structs conform to this, we’ll add the implementation for converting them from JSON represented as an “Any”, which could be an array or dictionary depending on our API responses. What does our struct look like with this implementation?
struct Article: JSONTransformable { let author: String? let description: String? let title: String? let url: String? let urlToImage: String? let publishedAt: String? init(json: Any) { let jsonDictionary = json as? [String : Any] author = jsonDictionary?["author"] as? String description = jsonDictionary?["description"] as? String title = jsonDictionary?["title"] as? String url = jsonDictionary?["url"] as? String urlToImage = jsonDictionary?["urlToImage"] as? String publishedAt = jsonDictionary?["publishedAt"] as? String } }
And with a little magic from Swift generics in our API client, these can now be converted from their JSON representations returned from the API. Let’s give the Articles response model the same treatment:
struct ArticlesResponse: JSONTransformable { let status: String? let articles: [Article]? init(json: Any) { let jsonDictionary = json as? [String : Any] status = jsonDictionary?["status"] as? String if let articlesJSONArray = jsonDictionary?["articles"] as? [[String: Any]] { var anArticles: [Article] = [] for sourceJSONDictionary in articlesJSONArray { anArticles.append(Article(json: sourceJSONDictionary)) } articles = anArticles } else { articles = nil } } }
Notice how this initializer uses the JSON-based initializer from our Article class, meaning that when we request this response from our API, this initializer will cascade through all the article JSON entries to build structs for each. We’ll need to do the same for both the Source and SourcesResponse models, which is left as an exercise to the reader! (You’ll also find it as a download at the end.
But the Article and Source models aren’t the only types of models we have, there are also the enum models for the Language and Country, etc. Let’s see how one of those looks:
enum Category: String { case business = "business" case entertainment = "entertainment" case gaming = "gaming" case general = "general" case music = "music" case politics = "politics" case science = "science-and-nature" case sport = "sport" case technology = "technology" }
This has the typed name on the left and the value on the right side of the equals sign, making it easy to parse these using the raw value initializer of these objects. You’ll see this in the initializer for Source. You should use this as an example to create the models for Country, Language, and Sort.
View Controller
Now that we have our models finished and our views finished, it’s time to write the glue code between our two layers: the view controllers. First, let’s get our Source view controller working:
class SourcesViewController: UIViewController { let api = NewsAPI() var sources: [Source] = [] @IBOutlet weak var collectionView: UICollectionView! @IBOutlet weak var collectionViewFlowLayout: UICollectionViewFlowLayout! override func viewDidLoad() { super.viewDidLoad() api.load(.sources(categories: nil, languages: nil, countries: nil)) { [weak self] (response: SourcesResponse?, error:Error?) in self?.sources = response?.sources ?? [] self?.collectionView.reloadData() } } } extension SourcesViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sources.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell : SourceCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) cell.iconImageView.setIconImage(from: sources[indexPath.row].url) cell.nameLabel.text = sources[indexPath.row].name return cell } } extension SourcesViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let numberOfCellsPerLineFloat: CGFloat = 2 let itemWidthWithoutConsideringInteritemSpace = collectionViewFlowLayout.collectionViewWidthWithoutInsets / numberOfCellsPerLineFloat let numberOfInteritemSpaces = (numberOfCellsPerLineFloat - 1) let amountOfInteritemSpacePerCell = (collectionViewFlowLayout.minimumInteritemSpacing / numberOfCellsPerLineFloat) let itemWidth = itemWidthWithoutConsideringInteritemSpace - (numberOfInteritemSpaces * amountOfInteritemSpacePerCell) return CGSize(width: itemWidth, height: 208) } }
Notice how the view controller has access to both the model and view, but neither the model nor the view has access to each other. This property results in one of the big benefits of MVC, in that if either your model or view changes, as I assure you they will, you minimize the amount of code you will need to re-write because your models and views are completely independent, and in fact, could be re-used in other parts of your applications or even other applications.
But this isn’t quite the whole picture, let’s get our ArticlesViewController working:
class ArticlesViewController: UIViewController { let api = NewsAPI() var source: Source! var articles: [Article] = [] @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { title = source.name api.load(.articles(source: source, sort: nil)) { [weak self] (response:ArticlesResponse?, error:Error?) in self?.articles = response?.articles ?? [] self?.tableView.reloadData() } } } extension ArticlesViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return articles.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: ArticleTableViewCell = tableView.dequeueReusableCell(for: indexPath) cell.authorLabel.text = articles[indexPath.row].author cell.previewImageView.setImage(from: articles[indexPath.row].urlToImage) cell.theTitleLabel.text = articles[indexPath.row].title return cell } }
If you try to transition between the two view controllers by selecting a source, you will notice a crash. That’s because we need to pass the selected source from the Source view controller to the Articles view controller in it’s prepareForSegue implementation (Be sure to add an identifier in your Storyboard, I also chose to represent my Segues as an enum).
class SourcesViewController: UIViewController { /* omitted to save space */ override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch segue.identifier ?? "" { case Segue.sourcesToArticles.rawValue: let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first ?? IndexPath(item: 0, section: 0) let destination = segue.destination as! ArticlesViewController destination.source = sources[selectedIndexPath.row] default: () } } }
This should result in a successful transition from one view controller to the next, but when you select an article from the table view of the Articles view controller, unfortunately we run into another crash. This is because we need to add the implementation of UITableViewDelegate’s didSelectItemAtIndexPath to our Articles view controller. So let’s take a look:
extension ArticlesViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let article = articles[indexPath.row] if let url = URL(string: article.url ?? "") { let svc = SFSafariViewController(url: url) present(svc, animated: true, completion: nil) } } }
Now, if we run it, it should push a web view controller when we do select an article.
Final Product
Here’s a video of what this app will look like with everything finished!
It looks great! I’m sure our designer will be proud!
Conclusion
We’ve seen how you can take a design and decompose it into views and view controllers, take an API spec and decompose it into model entities, and use that plan to implement an iOS app in Swift. To see some of the benefits this approach has and the arguments for it, I recommend checking out my last post on this topic. You should also know that this approach isn’t the only right way to develop in Swift for iOS or even nearly perfect. But it is the official design pattern for iOS and for good reason: it’s a reasonable place to start. In a forthcoming post, we’ll explore adding view models to this example. I’ll also write a post about the considerations in designing an API client. If you have any questions, feel free to add a comment below, reach out to me, or if you happen to be reading this before September 20th, 2017, come check this out live at the Noble iOS Meetup. Here’s the full file for those that want to check out the full working version.
I’m hosting an introductory iOS meet up in NYC!
Noble Desktop have started the Noble iOS meet up hosted by yours truly! Here’s the first topic:
In our first Meetup we’ll be exploring the popular design pattern: MVC.
If you’re new to iOS, it’s important to understand how the Model-View-Controller (MVC) design pattern can be used to organize your code into three independent sections: the model, the view, and the controller.
Paul Jones will present how MVC works, demonstrate examples, and discuss the benefits of when to use this approach in your own projects!
It’s next week, Wednesday, September 20, 2017 from 6:00 PM to 8:00 PM at Noble Desktop’s office on 594 Broadway, Suite 1202, New York, NY. If you’re looking for a way to get started with iOS and explore new topics, come on out!
What is Model-View-Controller (MVC) and why does iOS use it?
Last week I explored some implementation details of MMVM on iOS, but I realized that for people just beginning with iOS, this post was going to be of no help because it assumes knowledge of the more basic Model-View-Controller (MVC) design pattern. To rectify that, today we’ll explore MVC. This post is meant for someone that is new to iOS, perhaps they’re in a bootcamp or a class or teaching themselves; this will only be useful to very junior developers, but let’s roll.
Some theory and graphs
MVC is a way of organizing your code that allows you to divvy up the work of writing a feature of an application into three independent parts: the model, the view, and the controller. The model is your representation of your app’s subject – for instance, if you’re making a social networking app, a natural “model” class would be the “User” class, where username, bio, and profile picture are stored. The view, on the other hand, is what the user actually sees, and it will more likely than not manifest itself as a “UIView” class, such as UITextView or UIImageView, which you might have in a “UserProfileView” for your social network, for example. The controller is what sits in between these two layers of code, requesting from the model the information to pass to the view for presentation to the user. Here’s a graph of what this relationship looks like:
This graphs makes it clear how the user actions flow through your layers and how this results in an update to what the user sees. The “view” picks up the user’s interaction and sends the message of being tapped or swiped to the controller, which uses this message to tell the model layer to update itself, which in turn sends a message back to the controller of success or failure to update, which cascade into the view, finally getting presented to the user there.
Your application isn’t limited to just one of these objects, quite the contrary, an application is going to have many MVC groups working together to create the final product:
As a project grows like this, you’ll eventually find that you stop thinking about features in terms of 3 unique MVC items each, but more as “layers”. The reason for this is that for big projects, a controller will end up with a multitude of views and models each, where a checkout view controller, for example, might also use a user model and product model to populate its checkout view and the subviews.
More practically, some code
Let’s write some code that illustrates just the bare bones principles outlined above: that we should strictly separate our views from our models, and use a controller to glue the two together. Our example app is going to be a tap counter that has a User for sake of staying true to some of the images above.
Model
The easy part should come first, here’s what a model looks like:
struct UserModel { var username: String var tapCount: Int }
This struct models our “problem”: we want to display a username and the number of times that user has tapped our app, and so that’s all our struct represents. There is no mention of UIViews of UIViewControllers, this model is completely independent of that. In fact, all you need to be able to utilize this model is the Swift language, wherever it might be running, on a Mac, iPhone, iPad, and perhaps on day on the server too, but that’s a topic for another day.
View
Now let’s create a view. Our view is going to need a button to receive the tap, and a label to display the username and the tap count. On iOS and with Swift, that looks like this:
class ProfileView: UIView { let button = UIButton() let label = UILabel() }
Because we’re doing this in a playground, we’re going to need to configure our UIView programmatically. This isn’t so relevant to modern iOS, but it also isn’t very difficult. In our ProfileView, we’re going to implement a custom initializer like this:
override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .blue button.setTitleColor(UIColor.white, for: .normal) button.setTitleColor(UIColor.gray, for: .highlighted) button.addTarget(self, action: #selector(didTapButton(sender:)), for: .touchUpInside) button.backgroundColor = .black button.layer.borderWidth = 1 button.layer.borderColor = UIColor.white.cgColor button.layer.cornerRadius = 5 label.textColor = .white let stackView = UIStackView(arrangedSubviews: [button, label]) stackView.axis = .vertical stackView.alignment = .center stackView.spacing = 20 addSubview(stackView) stackView.bindFrameToSuperviewBounds() /* see implementation later in this exercise */ }
Because we want a strict separation between the model layer and the view layer, we’re not going to add a variable for the user in our view, as that could lead to non-canonical user instances floating about, that is, versions of the user that aren’t controlled by their rightful controller, we’re going to add a function that configures our view for a user, like so:
func configure(with user: UserModel) { button.setTitle("Tap here", for: .normal) label.text = "\(user.username) Tap Count: \(user.tapCount)" }
Going back to our graphs from earlier, we see that the UIView is meant to handle user interaction, and in our initializer we declared that our button was to tell this view when it is touched-up-inside, so we’re going to need to implement that function. Before we do that, to keep our separation between Controller and View layers, we’re also going to need a delegate using a Swift protocol to sit between the two layers. That’ll look like this:
protocol ProfileViewDelegate: NSObjectProtocol { func profileViewDidTapButton() } class ProfileView: UIView { /* ommitted to save space */ weak var delegate: ProfileViewDelegate? /* ommitted to save space */ func didTapButton(sender: UIButton) { delegate?.profileViewDidTapButton() } }
Controller
Now that both our view and model are completely set up, it’s time to write some code that glues these two layers together. We’re going to create a UIViewController and give it a view and a model as properties:
class ProfileViewController: UIViewController { let profileView = ProfileView(frame: CGRect(x: 0, y: 0, width: 200, height: 100)) var userModel = UserModel(username: "PLJNS", tapCount: 0) }
We’re now going to need to configure our view with our model and position it, let’s do that in viewDidLoad:
override func viewDidLoad() { super.viewDidLoad() profileView.configure(with: userModel) profileView.center = view.center profileView.delegate = self view.addSubview(profileView) }
We now should find that this is all well and good, except that we need our view controller to conform to our view’s delegate’s protocol so that they can communicate when the user taps, let’s add it in an extension:
extension ProfileViewController: ProfileViewDelegate { func profileViewDidTapButton() { userModel.tapCount += 1 profileView.configure(with: userModel) } }
Okay, but why?
The benefits of adopting this style of divvying up the work are that it allows you to sometimes reuse code, it decouples view logic from model (or “business”) logic making your code more amenable to change, and it can reduce the amount of bugs you have by helping not to introduce them. In our example, we could take our UserModel everywhere. We could have another view controller conform to our view’s protocol. We could change the UILabel to a UITextView. We can do this because all of the parts are independent from one another.
This isn’t the only way to organize your project, and as you develop bigger projects you may find that this approach has downside too. For instance, our model and view layer aren’t really that separated in that the know about one another’s existence, so if you move from a UserModel to something else you’d have to update that code. One solution to this problem is know as MVVM, which I cover here. You might also find that because a “controller” is just whatever isn’t a view or model, it ends up being an awful lot indeed, perhaps 10s of thousands of lines if you’re not careful. This is playfully known as massive view controller, and it can be a serious hindrance to your application. One design patterns to solving this problem is known as VIPER, which I’ll cover in a future post.
You can find this code in this post in Playground form here.
Considerations in implementing MVVM in iOS with Swift
I’ve been researching iOS design patterns searching for new techniques for writing correct, fault-tolerant, and maintainable code as quickly as possible, and I’ve realized a terminological inexactitude has led to some confusion in design discussions I’ve had in the past. I think most people may be talking about implementing a layer of abstraction between the view controller and the model, delegating querying the model for information. This has it’s uses, but what I’ll be covering here is placing a layer of abstraction between the view and controller and calling that a view model.
One popular approach to application architecture in iOS is known as MVVM, or Model-View-ViewModel, developed by Microsoft around 2005. What I thought a view model was about was a “model” of a UIView’s desired input. Roughly, the “view model” of a UILabel would be a String, because you can set and get the string of the label. So imagine you had a custom UIView that looks something like this:
class ProfileView: UIView { @IBOutlet weak var userImageView: UIImageView! @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var biographyTextView: UITextView! }
Instead of exposing the image view, label, and text view to the user of this class, which is desirable because the implementation of label or text view or that might very well change in the future, you “model” this view with a lightweight structure that looks something like this:
struct Model { let userImage: UIImage? let fullName: String? let biography: String? }
Which enables you label the subviews of profile view as private and to write a derived property on your profile view that looks like this:
var model: Model? { get { return Model(userImage: userImageView.image, fullName: nameLabel.text, biography: biographyTextView.text) } set { userImageView.image = model?.userImage nameLabel.text = model?.fullName biographyTextView.text = model?.biography } }
This allows you to hide the implementation of the view to users of it, define how you want the input of a view to be structured (“full name” or “first name” and “last name”, for instance), and you can then write extensions on your actual model layer to get the input you want. Like this, for instance:
class User { var image: UIImage? var firstName: String? var lastName: String? var biography: String? } extension User { var profileViewModel: ProfileView.Model { return ProfileView.Model(userImage: image, fullName: "\(firstName) \(lastName)", biography: biography) } }
Another way of implementing this pattern that might have some benefits if you’re interested in testing is with protocols. You won’t be able to add the model as a nested class of your view, which can make organizing code easier. Instead of having the view model generated in an extension, you could make the view model a protocol:
protocol ProfileViewModelProtocol { var userImage: UIImage? { get } var fullName: String? { get } var biography: String? { get } }
And then instead of making the view model a derived property on the user, you add conformance to this protocol in an extension on your model:
extension User: ProfileViewModelProtocol { var userImage: UIImage? { return image } var fullName: String? { return "\(firstName ?? "") \(lastName ?? "")" } var biography: String? { return bio } }
And then the property of the profile view becomes something like this:
var model: ProfileViewModelProtocol? { didSet { userImageView.image = model?.userImage nameLabel.text = model?.fullName biographyTextView.text = model?.biography } }
But this only defines communication one way. Models change. So how does this approach to MVVM tackle that? Let’s say the user can modify the biography in this profile. We’ll need to implement UITextViewDelegate on ProfileView and define a ProfileViewDelegate:
protocol ProfileViewDelegate: NSObjectProtocol { func biographyDidChange(_ biography: String) } class ProfileView: UIView, UITextViewDelegate { /* ... */ weak var delegate: ProfileViewDelegate? func textViewDidChange(_ textView: UITextView) { delegate?.biographyDidChange(textView.text) } }
And the pattern all comes together in our hypothetical ProfileViewController:
class ProfileViewController: UIViewController, ProfileViewDelegate { @IBOutlet fileprivate weak var profileView: ProfileView! var user: User? override func viewDidLoad() { super.viewDidLoad() profileView.model = user } func biographyDidChange(_ biography: String) { user?.bio = biography // inform API } }
To make this version clear, here’s a nice graphic I made for you:
This graphic makes the benefits clear in that there’s a level of abstraction in-between the view and the controller which is responsible for translating between the model layer and the view layer. If I find a good example of the other variety of MVVM, I’ll investigate and write up a post about it. Here’s a playground with all the code from this post.