Model View Controller Pattern Explained
YouTubeLets go over one of the most common design patterns we see in iOS development: MVC. So far we've been writing all of our code in views and controllers, so now lets complete the implementation by writing our model objects. We'll create video and channel models to continue making progress in our YouTube application.
<div class='filename'>VideCell.swift</div>
!codebreak
!syntax-highlight
import UIKit
class Video: NSObject {
var thumbnailImageName: String?
var title: String?
var numberOfViews: NSNumber?
var uploadDate: NSDate?
var channel: Channel?
}
class Channel: NSObject {
var name: String?
var profileImageName: String?
}
!codebreak
<div class='filename'>VideoCell.swift</div>
!codebreak
!syntax-highlight
class VideoCell: BaseCell {
var video: Video? {
didSet {
titleLabel.text = video?.title
thumbnailImageView.image = UIImage(named: (video?.thumbnailImageName)!)
if let profileImageName = video?.channel?.profileImageName {
userProfileImageView.image = UIImage(named: profileImageName)
}
if let channelName = video?.channel?.name, numberOfViews = video?.numberOfViews {
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .DecimalStyle
let subtitleText = "\(channelName) • \(numberFormatter.stringFromNumber(numberOfViews)!) • 2 years ago "
subtitleTextView.text = subtitleText
}
//measure title text
if let title = video?.title {
let size = CGSizeMake(frame.width - 16 - 44 - 8 - 16, 1000)
let options = NSStringDrawingOptions.UsesFontLeading.union(.UsesLineFragmentOrigin)
let estimatedRect = NSString(string: title).boundingRectWithSize(size, options: options, attributes: [NSFontAttributeName: UIFont.systemFontOfSize(14)], context: nil)
if estimatedRect.size.height > 20 {
titleLabelHeightConstraint?.constant = 44
} else {
titleLabelHeightConstraint?.constant = 20
}
}
}
}
let thumbnailImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "taylor_swift_blank_space")
imageView.contentMode = .ScaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
let userProfileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "taylor_swift_profile")
imageView.layer.cornerRadius = 22
imageView.layer.masksToBounds = true
return imageView
}()
let separatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1)
return view
}()
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Taylor Swift - Blank Space"
label.numberOfLines = 2
return label
}()
let subtitleTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = "TaylorSwiftVEVO • 1,604,684,607 views • 2 years ago"
textView.textContainerInset = UIEdgeInsetsMake(0, -4, 0, 0)
textView.textColor = UIColor.lightGrayColor()
return textView
}()
var titleLabelHeightConstraint: NSLayoutConstraint?
override func setupViews() {
addSubview(thumbnailImageView)
addSubview(separatorView)
addSubview(userProfileImageView)
addSubview(titleLabel)
addSubview(subtitleTextView)
addConstraintsWithFormat("H:|-16-[v0]-16-|", views: thumbnailImageView)
addConstraintsWithFormat("H:|-16-[v0(44)]", views: userProfileImageView)
//vertical constraints
addConstraintsWithFormat("V:|-16-[v0]-8-[v1(44)]-36-[v2(1)]|", views: thumbnailImageView, userProfileImageView, separatorView)
addConstraintsWithFormat("H:|[v0]|", views: separatorView)
//top constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .Top, relatedBy: .Equal, toItem: thumbnailImageView, attribute: .Bottom, multiplier: 1, constant: 8))
//left constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .Left, relatedBy: .Equal, toItem: userProfileImageView, attribute: .Right, multiplier: 1, constant: 8))
//right constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .Right, relatedBy: .Equal, toItem: thumbnailImageView, attribute: .Right, multiplier: 1, constant: 0))
//height constraint
titleLabelHeightConstraint = NSLayoutConstraint(item: titleLabel, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 0, constant: 44)
addConstraint(titleLabelHeightConstraint!)
//top constraint
addConstraint(NSLayoutConstraint(item: subtitleTextView, attribute: .Top, relatedBy: .Equal, toItem: titleLabel, attribute: .Bottom, multiplier: 1, constant: 4))
//left constraint
addConstraint(NSLayoutConstraint(item: subtitleTextView, attribute: .Left, relatedBy: .Equal, toItem: userProfileImageView, attribute: .Right, multiplier: 1, constant: 8))
//right constraint
addConstraint(NSLayoutConstraint(item: subtitleTextView, attribute: .Right, relatedBy: .Equal, toItem: thumbnailImageView, attribute: .Right, multiplier: 1, constant: 0))
//height constraint
addConstraint(NSLayoutConstraint(item: subtitleTextView, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 0, constant: 30))
}
}
!codebreak
<div class='filename'>HomeController.swift</div>
!codebreak
!syntax-highlight
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var videos: [Video] = {
var kanyeChannel = Channel()
kanyeChannel.name = "KanyeIsTheBestChannel"
kanyeChannel.profileImageName = "kanye_profile"
var blankSpaceVideo = Video()
blankSpaceVideo.title = "Taylor Swift - Blank Space"
blankSpaceVideo.thumbnailImageName = "taylor_swift_blank_space"
blankSpaceVideo.channel = kanyeChannel
blankSpaceVideo.numberOfViews = 23932843093
var badBloodVideo = Video()
badBloodVideo.title = "Taylor Swift - Bad Blood featuring Kendrick Lamar"
badBloodVideo.thumbnailImageName = "taylor_swift_bad_blood"
badBloodVideo.channel = kanyeChannel
badBloodVideo.numberOfViews = 57989654934
return [blankSpaceVideo, badBloodVideo]
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Home"
navigationController?.navigationBar.translucent = false
let titleLabel = UILabel(frame: CGRectMake(0, 0, view.frame.width - 32, view.frame.height))
titleLabel.text = "Home"
titleLabel.textColor = UIColor.whiteColor()
titleLabel.font = UIFont.systemFontOfSize(20)
navigationItem.titleView = titleLabel
collectionView?.backgroundColor = UIColor.whiteColor()
collectionView?.registerClass(VideoCell.self, forCellWithReuseIdentifier: "cellId")
collectionView?.contentInset = UIEdgeInsetsMake(50, 0, 0, 0)
collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, 0)
setupMenuBar()
setupNavBarButtons()
}
func setupNavBarButtons() {
let searchImage = UIImage(named: "search_icon")?.imageWithRenderingMode(.AlwaysOriginal)
let searchBarButtonItem = UIBarButtonItem(image: searchImage, style: .Plain, target: self, action: #selector(handleSearch))
let moreButton = UIBarButtonItem(image: UIImage(named: "nav_more_icon")?.imageWithRenderingMode(.AlwaysOriginal), style: .Plain, target: self, action: #selector(handleMore))
navigationItem.rightBarButtonItems = [moreButton, searchBarButtonItem]
}
func handleMore() {
}
func handleSearch() {
print(123)
}
let menuBar: MenuBar = {
let mb = MenuBar()
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|[v0(50)]", views: menuBar)
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return videos.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellId", forIndexPath: indexPath) as! VideoCell
cell.video = videos[indexPath.item]
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let height = (view.frame.width - 16 - 16) * 9 / 16
return CGSizeMake(view.frame.width, height + 16 + 88)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
}