├── YouTubeHomeFeed ├── Assets.xcassets │ ├── Contents.json │ ├── home.imageset │ │ ├── home.png │ │ └── Contents.json │ ├── menu.imageset │ │ ├── menu.png │ │ └── Contents.json │ ├── dhoni.imageset │ │ ├── dhoni.jpg │ │ └── Contents.json │ ├── account.imageset │ │ ├── account.png │ │ └── Contents.json │ ├── trending.imageset │ │ ├── trending.png │ │ └── Contents.json │ ├── dhoni_profile.imageset │ │ ├── dhoni_profile.jpg │ │ └── Contents.json │ ├── subscriptions.imageset │ │ ├── subscriptions.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Video.swift ├── Utility.swift ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Extensions.swift ├── AppDelegate.swift ├── MenuBar.swift ├── HomeController.swift └── VideoCell.swift ├── YouTubeHomeFeed.xcodeproj ├── xcuserdata │ └── VamshiKrishna.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── YouTubeHomeFeed.xcscheme ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── VamshiKrishna.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── project.pbxproj └── README.md /YouTubeHomeFeed/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/home.imageset/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VamshiIITBHU14/YouTubeClone/HEAD/YouTubeHomeFeed/Assets.xcassets/home.imageset/home.png -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/menu.imageset/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VamshiIITBHU14/YouTubeClone/HEAD/YouTubeHomeFeed/Assets.xcassets/menu.imageset/menu.png -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/dhoni.imageset/dhoni.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VamshiIITBHU14/YouTubeClone/HEAD/YouTubeHomeFeed/Assets.xcassets/dhoni.imageset/dhoni.jpg -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/account.imageset/account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VamshiIITBHU14/YouTubeClone/HEAD/YouTubeHomeFeed/Assets.xcassets/account.imageset/account.png -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/trending.imageset/trending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VamshiIITBHU14/YouTubeClone/HEAD/YouTubeHomeFeed/Assets.xcassets/trending.imageset/trending.png -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/dhoni_profile.imageset/dhoni_profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VamshiIITBHU14/YouTubeClone/HEAD/YouTubeHomeFeed/Assets.xcassets/dhoni_profile.imageset/dhoni_profile.jpg -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/subscriptions.imageset/subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VamshiIITBHU14/YouTubeClone/HEAD/YouTubeHomeFeed/Assets.xcassets/subscriptions.imageset/subscriptions.png -------------------------------------------------------------------------------- /YouTubeHomeFeed.xcodeproj/xcuserdata/VamshiKrishna.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /YouTubeHomeFeed.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YouTubeHomeFeed.xcodeproj/project.xcworkspace/xcuserdata/VamshiKrishna.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VamshiIITBHU14/YouTubeClone/HEAD/YouTubeHomeFeed.xcodeproj/project.xcworkspace/xcuserdata/VamshiKrishna.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/dhoni.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "dhoni.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "home.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/menu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "menu.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/account.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "account.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/trending.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "trending.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/dhoni_profile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "dhoni_profile.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/subscriptions.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "subscriptions.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Video.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Video.swift 3 | // YouTubeHomeFeed 4 | // 5 | // Created by Vamshi Krishna on 07/05/17. 6 | // Copyright © 2017 VamshiKrishna. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class Video: NSObject { 12 | var thumbNailImage:String? 13 | var title:String? 14 | var channel:Channel? 15 | var numberOfViews:NSNumber? 16 | var uploadDate:NSDate? 17 | } 18 | 19 | class Channel:NSObject{ 20 | var name:String? 21 | var profileImage:String? 22 | } 23 | -------------------------------------------------------------------------------- /YouTubeHomeFeed.xcodeproj/xcuserdata/VamshiKrishna.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YouTubeHomeFeed.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 96E770EC1EBE24FF00E3E1EE 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/Utility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utility.swift 3 | // YouTubeHomeFeed 4 | // 5 | // Created by Vamshi Krishna on 07/05/17. 6 | // Copyright © 2017 VamshiKrishna. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class Utility: NSObject { 12 | private override init() { } 13 | static let shared = Utility() 14 | 15 | func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect { 16 | return CGRect(x: x, y: y, width: width, height: height) 17 | } 18 | 19 | func CGSizeMake( _ width:CGFloat, _ height:CGFloat) -> CGSize{ 20 | return CGSize(width: width, height: height) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YouTubeClone 2 | This project is a clone of YouTube. But the main intention is to show how to write clean code, using proper MVC patterns and re-usable coding methodologies. 3 | It includes features like: 4 | 1) Building UICollectionView all from code without using Storyboards 5 | 2) Customisable navigation bar to match that of YouTube 6 | 3) Creating a custom Menu bar using UICollectionView 7 | 4) NSURLSession task to complete a fetch request & iterate through all the objects in this JSON dictionary & construct all video objects 8 | 5) Load images Asynchronously 9 | 6) Creating a Slide-In Menu 10 | 7) Lazy var instantiation explained 11 | 8) JSON parsing in most optimised way 12 | 9) Play a remote video using AVPlayer inside the app 13 | 10) Video player controls like Pause, Play, Progress indicator etc. 14 | 15 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /YouTubeHomeFeed/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | UIViewControllerBasedStatusBarAppearance 8 | 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // YouTubeHomeFeed 4 | // 5 | // Created by Vamshi Krishna on 07/05/17. 6 | // Copyright © 2017 VamshiKrishna. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor{ 12 | static func returnRGBColor(r:CGFloat, g:CGFloat, b:CGFloat, alpha:CGFloat) -> UIColor{ 13 | return UIColor(red: r/255, green: g/255, blue: b/255, alpha: alpha) 14 | } 15 | } 16 | 17 | extension UIView{ 18 | func addConstraintsWithFormat(format:String, views: UIView...){ 19 | 20 | var viewsDictionary = [String:UIView]() 21 | for (index, view) in views.enumerated(){ 22 | let key = "v\(index)" 23 | view.translatesAutoresizingMaskIntoConstraints = false 24 | viewsDictionary[key] = view 25 | 26 | } 27 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary)) 28 | } 29 | } 30 | 31 | let imageCache = NSCache() 32 | 33 | class CustomImageView : UIImageView{ 34 | 35 | var imageURLString:String? 36 | func loadImageFromURLString(urlString:String){ 37 | imageURLString = urlString 38 | let url = URL(string: urlString) 39 | image = nil 40 | if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage{ 41 | self.image = imageFromCache 42 | return 43 | } 44 | let request = URLRequest(url: url!) 45 | let session = URLSession(configuration: URLSessionConfiguration.default) 46 | 47 | let task = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in 48 | if error != nil{ 49 | print(error!) 50 | return 51 | } 52 | DispatchQueue.main.async(execute: { 53 | let imageToCache = UIImage(data:data!) 54 | if(self.imageURLString == urlString){ 55 | self.image = imageToCache 56 | } 57 | imageCache.setObject(imageToCache!, forKey: urlString as AnyObject) 58 | 59 | }) 60 | }) 61 | task.resume() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // YouTubeHomeFeed 4 | // 5 | // Created by Vamshi Krishna on 06/05/17. 6 | // Copyright © 2017 VamshiKrishna. All rights reserved. 7 | // Source: https://www.letsbuildthatapp.com 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | 18 | window = UIWindow(frame:UIScreen.main.bounds) 19 | window?.makeKeyAndVisible() 20 | let layout = UICollectionViewFlowLayout() 21 | window?.rootViewController = UINavigationController(rootViewController:HomeController(collectionViewLayout:layout)) 22 | application.statusBarStyle = .lightContent 23 | 24 | let statusBarBackgroundView = UIView() 25 | statusBarBackgroundView.backgroundColor = UIColor.returnRGBColor(r: 230, g: 131, b: 31, alpha: 1) 26 | window?.addSubview(statusBarBackgroundView) 27 | window?.addConstraintsWithFormat(format: "H:|[v0]|", views: statusBarBackgroundView) 28 | window?.addConstraintsWithFormat(format: "V:|[v0(20)]|", views: statusBarBackgroundView) 29 | 30 | //get rid of black line under Navigation Bar 31 | UINavigationBar.appearance().shadowImage = UIImage() 32 | UINavigationBar.appearance().setBackgroundImage (UIImage(), for:.default) 33 | return true 34 | } 35 | 36 | func applicationWillResignActive(_ application: UIApplication) { 37 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 38 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 39 | } 40 | 41 | func applicationDidEnterBackground(_ application: UIApplication) { 42 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 43 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 44 | } 45 | 46 | func applicationWillEnterForeground(_ application: UIApplication) { 47 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 48 | } 49 | 50 | func applicationDidBecomeActive(_ application: UIApplication) { 51 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 52 | } 53 | 54 | func applicationWillTerminate(_ application: UIApplication) { 55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 56 | } 57 | 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/MenuBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuBar.swift 3 | // YouTubeHomeFeed 4 | // 5 | // Created by Vamshi Krishna on 07/05/17. 6 | // Copyright © 2017 VamshiKrishna. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MenuBar:UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout{ 12 | 13 | lazy var collectionView:UICollectionView = { 14 | let layout = UICollectionViewFlowLayout() 15 | let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) 16 | cv.backgroundColor = UIColor.returnRGBColor(r: 230, g: 32, b: 32, alpha: 1) 17 | cv.dataSource = self 18 | cv.delegate = self 19 | return cv 20 | }() 21 | 22 | let reuseIdentifier = "Cell" 23 | let imageNames = ["home", "trending", "subscriptions", "account"] 24 | override init(frame: CGRect) { 25 | super.init(frame: frame) 26 | collectionView.register(MenuCell.self, forCellWithReuseIdentifier: reuseIdentifier) 27 | addSubview(collectionView) 28 | addConstraintsWithFormat(format: "H:|[v0]|", views: collectionView) 29 | addConstraintsWithFormat(format: "V:|[v0]|", views: collectionView) 30 | 31 | 32 | } 33 | 34 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 35 | return 4 36 | } 37 | 38 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 39 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! MenuCell 40 | cell.imageView.image = UIImage(named: imageNames[indexPath.item])?.withRenderingMode(.alwaysTemplate) 41 | cell.imageView.tintColor = UIColor.returnRGBColor(r: 91, g: 14, b: 13, alpha: 1) 42 | collectionView.selectItem(at: IndexPath(item:0, section:0), animated: false, scrollPosition:[]) 43 | return cell 44 | } 45 | 46 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 47 | return Utility.shared.CGSizeMake(frame.width/4, frame.height) 48 | } 49 | 50 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { 51 | return 0 52 | } 53 | required init?(coder aDecoder: NSCoder) { 54 | fatalError("init(coder:) has not been implemented") 55 | } 56 | } 57 | 58 | class MenuCell:BaseCell{ 59 | 60 | var imageView:UIImageView = { 61 | let iv = UIImageView() 62 | return iv 63 | }() 64 | 65 | override var isHighlighted: Bool{ 66 | didSet{ 67 | imageView.tintColor = isHighlighted ? UIColor.white : UIColor.returnRGBColor(r: 91, g: 14, b: 13, alpha: 1) 68 | } 69 | } 70 | 71 | override var isSelected: Bool{ 72 | didSet{ 73 | imageView.tintColor = isSelected ? UIColor.white : UIColor.returnRGBColor(r: 91, g: 14, b: 13, alpha: 1) 74 | } 75 | } 76 | 77 | override func setUpViews() { 78 | super.setUpViews() 79 | addSubview(imageView) 80 | addConstraintsWithFormat(format: "H:[v0(28)]", views: imageView) 81 | addConstraintsWithFormat(format: "V:[v0(28)]", views: imageView) 82 | addConstraint(NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)) 83 | addConstraint(NSLayoutConstraint(item: imageView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /YouTubeHomeFeed.xcodeproj/xcuserdata/VamshiKrishna.xcuserdatad/xcschemes/YouTubeHomeFeed.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/HomeController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeController.swift 3 | // YouTubeHomeFeed 4 | // 5 | // Created by Vamshi Krishna on 06/05/17. 6 | // Copyright © 2017 VamshiKrishna. All rights reserved. 7 | // Source: https://www.letsbuildthatapp.com 8 | 9 | import UIKit 10 | 11 | private let reuseIdentifier = "Cell" 12 | 13 | class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout { 14 | 15 | var videos:[Video]? 16 | 17 | func fetchVideos(){ 18 | let url = URL(string: "https://s3-us-west-2.amazonaws.com/youtubeassets/home.json") 19 | let request = URLRequest(url: url!) 20 | let session = URLSession(configuration: URLSessionConfiguration.default) 21 | 22 | let task = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in 23 | if error != nil{ 24 | print(error!) 25 | return 26 | } 27 | do{ 28 | let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) 29 | self.videos = [Video]() 30 | for dictionary in json as! [[String:AnyObject]]{ 31 | let video = Video() 32 | video.title = dictionary["title"] as? String 33 | video.thumbNailImage = dictionary["thumbnail_image_name"] as? String 34 | video.numberOfViews = dictionary["number_of_views"] as? NSNumber 35 | let channelDictionary = dictionary["channel"] as! [String:AnyObject] 36 | let channel = Channel() 37 | channel.name = channelDictionary["name"] as? String 38 | channel.profileImage = channelDictionary["profile_image_name"] as? String 39 | video.channel = channel 40 | self.videos?.append(video) 41 | } 42 | DispatchQueue.main.async(execute: { 43 | self.collectionView?.reloadData() 44 | }) 45 | 46 | } catch let jsonError{ 47 | print(jsonError) 48 | } 49 | }) 50 | task.resume() 51 | } 52 | override func viewDidLoad() { 53 | super.viewDidLoad() 54 | fetchVideos() 55 | navigationController?.navigationBar.isTranslucent = false 56 | navigationController?.navigationBar.barTintColor = UIColor(red: 230/255, green: 32/255, blue: 32/255, alpha: 1) 57 | 58 | let titleLabel = UILabel(frame:Utility.shared.CGRectMake(0, 0, view.frame.width-32, view.frame.height)) 59 | titleLabel.textColor = UIColor.white 60 | titleLabel.text = "Home" 61 | titleLabel.font = UIFont.systemFont(ofSize: 22) 62 | navigationItem.titleView = titleLabel 63 | 64 | self.collectionView?.backgroundColor = UIColor.white 65 | self.collectionView!.register(VideoCell.self, forCellWithReuseIdentifier: reuseIdentifier) 66 | self.collectionView?.contentInset = UIEdgeInsetsMake(50, 0, 0, 0) 67 | self.collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, 0) 68 | 69 | setupNavBarButtons() 70 | setupMenuBar() 71 | } 72 | 73 | func setupNavBarButtons(){ 74 | let searchImage = UIImage(named: "subscriptions")?.withRenderingMode(.alwaysOriginal) 75 | let menuImage = UIImage(named: "menu")?.withRenderingMode(.alwaysOriginal) 76 | let searchBarButton = UIBarButtonItem(image: searchImage, style: .plain, target: self, action: #selector(handleSearch)) 77 | let menuBarButton = UIBarButtonItem(image: menuImage, style: .plain, target: self, action: #selector(handleMore)) 78 | navigationItem.rightBarButtonItems = [searchBarButton, menuBarButton] 79 | } 80 | 81 | func handleMore(){ 82 | 83 | } 84 | func handleSearch(){ 85 | 86 | } 87 | 88 | let menuBar:MenuBar = { 89 | let mb = MenuBar() 90 | return mb 91 | }() 92 | 93 | private func setupMenuBar(){ 94 | view.addSubview(menuBar) 95 | view.addConstraintsWithFormat(format: "H:|[v0]|", views: menuBar) 96 | view.addConstraintsWithFormat(format: "V:|[v0(50)]|", views: menuBar) 97 | 98 | } 99 | 100 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 101 | return videos?.count ?? 0 102 | } 103 | 104 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 105 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! VideoCell 106 | cell.video = videos?[indexPath.item] 107 | 108 | 109 | return cell 110 | } 111 | 112 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 113 | return 0 114 | } 115 | 116 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 117 | let height = (view.frame.width - 16 - 16) * 9/16 118 | return Utility.shared.CGSizeMake(view.frame.width, height+88) 119 | } 120 | 121 | 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /YouTubeHomeFeed/VideoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoCell.swift 3 | // YouTubeHomeFeed 4 | // 5 | // Created by Vamshi Krishna on 07/05/17. 6 | // Copyright © 2017 VamshiKrishna. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseCell:UICollectionViewCell{ 12 | override init(frame: CGRect) { 13 | super.init(frame: frame) 14 | setUpViews() 15 | } 16 | 17 | func setUpViews(){ 18 | 19 | } 20 | required init?(coder aDecoder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | } 24 | 25 | class VideoCell:BaseCell{ 26 | 27 | var video :Video?{ 28 | didSet{ 29 | titleLabel.text = video?.title 30 | setupThumbNailImage() 31 | setupProfileImage() 32 | let numberFormatter = NumberFormatter() 33 | numberFormatter.numberStyle = .decimal 34 | 35 | if let channelName = video?.channel?.name, let numberOfViewsAfterFormatting = numberFormatter.string(from: (video?.numberOfViews)!){ 36 | subTitleView.text = "\(channelName) : \(numberOfViewsAfterFormatting)" + "\n" + "2 Years ago" 37 | } 38 | 39 | //measuringTitleLabelTextHeight 40 | if let title = video?.title{ 41 | let size = Utility.shared.CGSizeMake(frame.width - 16 - 8 - 44 - 16 , 1000) 42 | let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin) 43 | let estimatedRect = NSString(string: title).boundingRect(with: size, options: options, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 13)], context: nil) 44 | 45 | if estimatedRect.size.height > 20{ 46 | titleLabelHeightConstraint?.constant = 44 47 | } 48 | else{ 49 | titleLabelHeightConstraint?.constant = 20 50 | } 51 | } 52 | } 53 | } 54 | 55 | func setupProfileImage(){ 56 | if let profileImageURL = video?.channel?.profileImage{ 57 | userProfileImageView.loadImageFromURLString(urlString: profileImageURL) 58 | } 59 | } 60 | 61 | func setupThumbNailImage(){ 62 | if let thumbNailImageURL = video?.thumbNailImage{ 63 | self.thumbnailImageView.loadImageFromURLString(urlString: thumbNailImageURL) 64 | } 65 | } 66 | let thumbnailImageView:CustomImageView = { 67 | let imageView = CustomImageView() 68 | imageView.contentMode = .scaleAspectFill 69 | imageView.clipsToBounds = true 70 | return imageView 71 | }() 72 | 73 | let titleLabel:UILabel = { 74 | let label = UILabel() 75 | label.translatesAutoresizingMaskIntoConstraints = false 76 | label.numberOfLines = 2 77 | return label 78 | }() 79 | 80 | let subTitleView: UITextView = { 81 | let textView = UITextView() 82 | textView.textContainerInset = UIEdgeInsetsMake(0, -4, 0, 0) 83 | textView.textColor = UIColor.lightGray 84 | textView.translatesAutoresizingMaskIntoConstraints = false 85 | textView.isEditable = false 86 | textView.isSelectable = false 87 | return textView 88 | }() 89 | 90 | let userProfileImageView: CustomImageView = { 91 | let imageView = CustomImageView() 92 | imageView.layer.cornerRadius = 22 93 | imageView.layer.masksToBounds = true 94 | imageView.contentMode = .scaleAspectFill 95 | return imageView 96 | }() 97 | 98 | let separatorView:UIView = { 99 | let view = UIView() 100 | view.backgroundColor = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1) 101 | return view 102 | }() 103 | 104 | var titleLabelHeightConstraint:NSLayoutConstraint? 105 | 106 | override func setUpViews(){ 107 | addSubview(thumbnailImageView) 108 | addSubview(separatorView) 109 | addSubview(userProfileImageView) 110 | addSubview(titleLabel) 111 | addSubview(subTitleView) 112 | 113 | addConstraintsWithFormat(format: "H:|-16-[v0]-16-|", views: thumbnailImageView) 114 | addConstraintsWithFormat(format: "H:|-16-[v0(44)]|", views: userProfileImageView) 115 | //vertical constraints 116 | addConstraintsWithFormat(format: "V:|-16-[v0]-8-[v1(44)]-36-[v2(1)]|", views: thumbnailImageView,userProfileImageView, separatorView) 117 | addConstraintsWithFormat(format: "H:|[v0]|", views: separatorView) 118 | 119 | //top constraint 120 | addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .top, relatedBy: .equal, toItem: thumbnailImageView, attribute: .bottom, multiplier: 1, constant: 8)) 121 | //left constraint 122 | addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .left, relatedBy: .equal, toItem: userProfileImageView, attribute: .right, multiplier: 1, constant: 8)) 123 | //right constraint 124 | addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .right, relatedBy: .equal, toItem: thumbnailImageView, attribute: .right, multiplier: 1, constant: 0)) 125 | //height constraint 126 | titleLabelHeightConstraint = NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 0, constant: 20) 127 | addConstraint(titleLabelHeightConstraint!) 128 | 129 | //left constraint 130 | addConstraint(NSLayoutConstraint(item: subTitleView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .left, multiplier: 1, constant: 0)) 131 | //right constraint 132 | addConstraint(NSLayoutConstraint(item: subTitleView, attribute: .right, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1, constant: 0)) 133 | //top constraint 134 | addConstraint(NSLayoutConstraint(item: subTitleView, attribute: .top, relatedBy: .equal, toItem: titleLabel, attribute: .bottom, multiplier: 1, constant: 4)) 135 | //height constraint 136 | addConstraint(NSLayoutConstraint(item: subTitleView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 0, constant: 30)) 137 | } 138 | 139 | func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect { 140 | return CGRect(x: x, y: y, width: width, height: height) 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /YouTubeHomeFeed.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9614B6441EBED6FB008CFA46 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9614B6431EBED6FB008CFA46 /* Extensions.swift */; }; 11 | 9614B6471EBED78C008CFA46 /* VideoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9614B6461EBED78C008CFA46 /* VideoCell.swift */; }; 12 | 9614B64A1EBEDE72008CFA46 /* MenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9614B6491EBEDE72008CFA46 /* MenuBar.swift */; }; 13 | 9614B64E1EBEED41008CFA46 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9614B64D1EBEED41008CFA46 /* Utility.swift */; }; 14 | 9614B6501EBF5B0B008CFA46 /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9614B64F1EBF5B0B008CFA46 /* Video.swift */; }; 15 | 96E770F11EBE24FF00E3E1EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E770F01EBE24FF00E3E1EE /* AppDelegate.swift */; }; 16 | 96E770F61EBE24FF00E3E1EE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 96E770F41EBE24FF00E3E1EE /* Main.storyboard */; }; 17 | 96E770F81EBE24FF00E3E1EE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 96E770F71EBE24FF00E3E1EE /* Assets.xcassets */; }; 18 | 96E770FB1EBE24FF00E3E1EE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 96E770F91EBE24FF00E3E1EE /* LaunchScreen.storyboard */; }; 19 | 96E771051EBE267800E3E1EE /* HomeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E771041EBE267800E3E1EE /* HomeController.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 9614B6431EBED6FB008CFA46 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 24 | 9614B6461EBED78C008CFA46 /* VideoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCell.swift; sourceTree = ""; }; 25 | 9614B6491EBEDE72008CFA46 /* MenuBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBar.swift; sourceTree = ""; }; 26 | 9614B64D1EBEED41008CFA46 /* Utility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; 27 | 9614B64F1EBF5B0B008CFA46 /* Video.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = ""; }; 28 | 96E770ED1EBE24FF00E3E1EE /* YouTubeHomeFeed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YouTubeHomeFeed.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 96E770F01EBE24FF00E3E1EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 30 | 96E770F51EBE24FF00E3E1EE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | 96E770F71EBE24FF00E3E1EE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | 96E770FA1EBE24FF00E3E1EE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 33 | 96E770FC1EBE24FF00E3E1EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | 96E771041EBE267800E3E1EE /* HomeController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeController.swift; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 96E770EA1EBE24FF00E3E1EE /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | 9614B6421EBED6ED008CFA46 /* Helpers */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 9614B6431EBED6FB008CFA46 /* Extensions.swift */, 52 | 9614B64D1EBEED41008CFA46 /* Utility.swift */, 53 | ); 54 | name = Helpers; 55 | sourceTree = ""; 56 | }; 57 | 9614B6451EBED777008CFA46 /* View */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 9614B6461EBED78C008CFA46 /* VideoCell.swift */, 61 | ); 62 | name = View; 63 | sourceTree = ""; 64 | }; 65 | 9614B6481EBED7D7008CFA46 /* Controller */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 96E771041EBE267800E3E1EE /* HomeController.swift */, 69 | ); 70 | name = Controller; 71 | sourceTree = ""; 72 | }; 73 | 96E770E41EBE24FF00E3E1EE = { 74 | isa = PBXGroup; 75 | children = ( 76 | 96E770EF1EBE24FF00E3E1EE /* YouTubeHomeFeed */, 77 | 96E770EE1EBE24FF00E3E1EE /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 96E770EE1EBE24FF00E3E1EE /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 96E770ED1EBE24FF00E3E1EE /* YouTubeHomeFeed.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 96E770EF1EBE24FF00E3E1EE /* YouTubeHomeFeed */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 96E770F01EBE24FF00E3E1EE /* AppDelegate.swift */, 93 | 9614B6491EBEDE72008CFA46 /* MenuBar.swift */, 94 | 9614B64F1EBF5B0B008CFA46 /* Video.swift */, 95 | 9614B6451EBED777008CFA46 /* View */, 96 | 9614B6481EBED7D7008CFA46 /* Controller */, 97 | 9614B6421EBED6ED008CFA46 /* Helpers */, 98 | 96E770F41EBE24FF00E3E1EE /* Main.storyboard */, 99 | 96E770F71EBE24FF00E3E1EE /* Assets.xcassets */, 100 | 96E770F91EBE24FF00E3E1EE /* LaunchScreen.storyboard */, 101 | 96E770FC1EBE24FF00E3E1EE /* Info.plist */, 102 | ); 103 | path = YouTubeHomeFeed; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | 96E770EC1EBE24FF00E3E1EE /* YouTubeHomeFeed */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = 96E770FF1EBE24FF00E3E1EE /* Build configuration list for PBXNativeTarget "YouTubeHomeFeed" */; 112 | buildPhases = ( 113 | 96E770E91EBE24FF00E3E1EE /* Sources */, 114 | 96E770EA1EBE24FF00E3E1EE /* Frameworks */, 115 | 96E770EB1EBE24FF00E3E1EE /* Resources */, 116 | ); 117 | buildRules = ( 118 | ); 119 | dependencies = ( 120 | ); 121 | name = YouTubeHomeFeed; 122 | productName = YouTubeHomeFeed; 123 | productReference = 96E770ED1EBE24FF00E3E1EE /* YouTubeHomeFeed.app */; 124 | productType = "com.apple.product-type.application"; 125 | }; 126 | /* End PBXNativeTarget section */ 127 | 128 | /* Begin PBXProject section */ 129 | 96E770E51EBE24FF00E3E1EE /* Project object */ = { 130 | isa = PBXProject; 131 | attributes = { 132 | LastSwiftUpdateCheck = 0830; 133 | LastUpgradeCheck = 0830; 134 | ORGANIZATIONNAME = VamshiKrishna; 135 | TargetAttributes = { 136 | 96E770EC1EBE24FF00E3E1EE = { 137 | CreatedOnToolsVersion = 8.3.1; 138 | DevelopmentTeam = BJBC582Q56; 139 | ProvisioningStyle = Automatic; 140 | }; 141 | }; 142 | }; 143 | buildConfigurationList = 96E770E81EBE24FF00E3E1EE /* Build configuration list for PBXProject "YouTubeHomeFeed" */; 144 | compatibilityVersion = "Xcode 3.2"; 145 | developmentRegion = English; 146 | hasScannedForEncodings = 0; 147 | knownRegions = ( 148 | en, 149 | Base, 150 | ); 151 | mainGroup = 96E770E41EBE24FF00E3E1EE; 152 | productRefGroup = 96E770EE1EBE24FF00E3E1EE /* Products */; 153 | projectDirPath = ""; 154 | projectRoot = ""; 155 | targets = ( 156 | 96E770EC1EBE24FF00E3E1EE /* YouTubeHomeFeed */, 157 | ); 158 | }; 159 | /* End PBXProject section */ 160 | 161 | /* Begin PBXResourcesBuildPhase section */ 162 | 96E770EB1EBE24FF00E3E1EE /* Resources */ = { 163 | isa = PBXResourcesBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | 96E770FB1EBE24FF00E3E1EE /* LaunchScreen.storyboard in Resources */, 167 | 96E770F81EBE24FF00E3E1EE /* Assets.xcassets in Resources */, 168 | 96E770F61EBE24FF00E3E1EE /* Main.storyboard in Resources */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXResourcesBuildPhase section */ 173 | 174 | /* Begin PBXSourcesBuildPhase section */ 175 | 96E770E91EBE24FF00E3E1EE /* Sources */ = { 176 | isa = PBXSourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 96E771051EBE267800E3E1EE /* HomeController.swift in Sources */, 180 | 9614B6471EBED78C008CFA46 /* VideoCell.swift in Sources */, 181 | 9614B64E1EBEED41008CFA46 /* Utility.swift in Sources */, 182 | 9614B6441EBED6FB008CFA46 /* Extensions.swift in Sources */, 183 | 96E770F11EBE24FF00E3E1EE /* AppDelegate.swift in Sources */, 184 | 9614B6501EBF5B0B008CFA46 /* Video.swift in Sources */, 185 | 9614B64A1EBEDE72008CFA46 /* MenuBar.swift in Sources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXSourcesBuildPhase section */ 190 | 191 | /* Begin PBXVariantGroup section */ 192 | 96E770F41EBE24FF00E3E1EE /* Main.storyboard */ = { 193 | isa = PBXVariantGroup; 194 | children = ( 195 | 96E770F51EBE24FF00E3E1EE /* Base */, 196 | ); 197 | name = Main.storyboard; 198 | sourceTree = ""; 199 | }; 200 | 96E770F91EBE24FF00E3E1EE /* LaunchScreen.storyboard */ = { 201 | isa = PBXVariantGroup; 202 | children = ( 203 | 96E770FA1EBE24FF00E3E1EE /* Base */, 204 | ); 205 | name = LaunchScreen.storyboard; 206 | sourceTree = ""; 207 | }; 208 | /* End PBXVariantGroup section */ 209 | 210 | /* Begin XCBuildConfiguration section */ 211 | 96E770FD1EBE24FF00E3E1EE /* Debug */ = { 212 | isa = XCBuildConfiguration; 213 | buildSettings = { 214 | ALWAYS_SEARCH_USER_PATHS = NO; 215 | CLANG_ANALYZER_NONNULL = YES; 216 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 217 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 218 | CLANG_CXX_LIBRARY = "libc++"; 219 | CLANG_ENABLE_MODULES = YES; 220 | CLANG_ENABLE_OBJC_ARC = YES; 221 | CLANG_WARN_BOOL_CONVERSION = YES; 222 | CLANG_WARN_CONSTANT_CONVERSION = YES; 223 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 224 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 225 | CLANG_WARN_EMPTY_BODY = YES; 226 | CLANG_WARN_ENUM_CONVERSION = YES; 227 | CLANG_WARN_INFINITE_RECURSION = YES; 228 | CLANG_WARN_INT_CONVERSION = YES; 229 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 230 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 231 | CLANG_WARN_UNREACHABLE_CODE = YES; 232 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 233 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 234 | COPY_PHASE_STRIP = NO; 235 | DEBUG_INFORMATION_FORMAT = dwarf; 236 | ENABLE_STRICT_OBJC_MSGSEND = YES; 237 | ENABLE_TESTABILITY = YES; 238 | GCC_C_LANGUAGE_STANDARD = gnu99; 239 | GCC_DYNAMIC_NO_PIC = NO; 240 | GCC_NO_COMMON_BLOCKS = YES; 241 | GCC_OPTIMIZATION_LEVEL = 0; 242 | GCC_PREPROCESSOR_DEFINITIONS = ( 243 | "DEBUG=1", 244 | "$(inherited)", 245 | ); 246 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 247 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 248 | GCC_WARN_UNDECLARED_SELECTOR = YES; 249 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 250 | GCC_WARN_UNUSED_FUNCTION = YES; 251 | GCC_WARN_UNUSED_VARIABLE = YES; 252 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 253 | MTL_ENABLE_DEBUG_INFO = YES; 254 | ONLY_ACTIVE_ARCH = YES; 255 | SDKROOT = iphoneos; 256 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 257 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 258 | }; 259 | name = Debug; 260 | }; 261 | 96E770FE1EBE24FF00E3E1EE /* Release */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | CLANG_ANALYZER_NONNULL = YES; 266 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_WARN_BOOL_CONVERSION = YES; 272 | CLANG_WARN_CONSTANT_CONVERSION = YES; 273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 274 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 275 | CLANG_WARN_EMPTY_BODY = YES; 276 | CLANG_WARN_ENUM_CONVERSION = YES; 277 | CLANG_WARN_INFINITE_RECURSION = YES; 278 | CLANG_WARN_INT_CONVERSION = YES; 279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 280 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 286 | ENABLE_NS_ASSERTIONS = NO; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu99; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 297 | MTL_ENABLE_DEBUG_INFO = NO; 298 | SDKROOT = iphoneos; 299 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 300 | VALIDATE_PRODUCT = YES; 301 | }; 302 | name = Release; 303 | }; 304 | 96E771001EBE24FF00E3E1EE /* Debug */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 308 | DEVELOPMENT_TEAM = BJBC582Q56; 309 | INFOPLIST_FILE = YouTubeHomeFeed/Info.plist; 310 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 311 | PRODUCT_BUNDLE_IDENTIFIER = VamshiKrishna.YouTubeHomeFeed; 312 | PRODUCT_NAME = "$(TARGET_NAME)"; 313 | SWIFT_VERSION = 3.0; 314 | }; 315 | name = Debug; 316 | }; 317 | 96E771011EBE24FF00E3E1EE /* Release */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 321 | DEVELOPMENT_TEAM = BJBC582Q56; 322 | INFOPLIST_FILE = YouTubeHomeFeed/Info.plist; 323 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 324 | PRODUCT_BUNDLE_IDENTIFIER = VamshiKrishna.YouTubeHomeFeed; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | SWIFT_VERSION = 3.0; 327 | }; 328 | name = Release; 329 | }; 330 | /* End XCBuildConfiguration section */ 331 | 332 | /* Begin XCConfigurationList section */ 333 | 96E770E81EBE24FF00E3E1EE /* Build configuration list for PBXProject "YouTubeHomeFeed" */ = { 334 | isa = XCConfigurationList; 335 | buildConfigurations = ( 336 | 96E770FD1EBE24FF00E3E1EE /* Debug */, 337 | 96E770FE1EBE24FF00E3E1EE /* Release */, 338 | ); 339 | defaultConfigurationIsVisible = 0; 340 | defaultConfigurationName = Release; 341 | }; 342 | 96E770FF1EBE24FF00E3E1EE /* Build configuration list for PBXNativeTarget "YouTubeHomeFeed" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 96E771001EBE24FF00E3E1EE /* Debug */, 346 | 96E771011EBE24FF00E3E1EE /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | /* End XCConfigurationList section */ 352 | }; 353 | rootObject = 96E770E51EBE24FF00E3E1EE /* Project object */; 354 | } 355 | --------------------------------------------------------------------------------