├── Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.05.43.png ├── Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.03.png ├── Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.09.png ├── Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.19.png ├── photosApp2.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── muskan.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcuserdata │ └── muskan.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── README.md └── photosApp2 ├── Info.plist ├── Base.lproj └── LaunchScreen.storyboard ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── AppDelegate.swift ├── ImagePreviewVC.swift └── ViewController.swift /Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.05.43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akhilendra/photosAppiOS/HEAD/Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.05.43.png -------------------------------------------------------------------------------- /Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akhilendra/photosAppiOS/HEAD/Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.03.png -------------------------------------------------------------------------------- /Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akhilendra/photosAppiOS/HEAD/Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.09.png -------------------------------------------------------------------------------- /Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akhilendra/photosAppiOS/HEAD/Simulator Screen Shot - iPhone 6 - 2017-10-05 at 12.06.19.png -------------------------------------------------------------------------------- /photosApp2.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /photosApp2.xcodeproj/project.xcworkspace/xcuserdata/muskan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akhilendra/photosAppiOS/HEAD/photosApp2.xcodeproj/project.xcworkspace/xcuserdata/muskan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /photosApp2.xcodeproj/xcuserdata/muskan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | photosApp2.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # photosAppiOS 2 | This is the source code for my photo gallery app tutorial on Youtube https://youtu.be/QS2mWk3fAWc 3 | 4 |

5 | 6 |        7 | 8 |
9 | 10 |        11 | 12 |

13 | -------------------------------------------------------------------------------- /photosApp2/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPhotoLibraryUsageDescription 6 | This app requires access to the photo library. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 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 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /photosApp2/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 | -------------------------------------------------------------------------------- /photosApp2/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 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /photosApp2/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // photosApp2 4 | // 5 | // Created by Muskan on 10/4/17. 6 | // Copyright © 2017 akhil. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 19 | // Override point for customization after application launch. 20 | 21 | let photos = PHPhotoLibrary.authorizationStatus() 22 | if photos == .notDetermined { 23 | PHPhotoLibrary.requestAuthorization({status in 24 | if status == .authorized{ 25 | self.gotoVC() 26 | } else { 27 | let alert = UIAlertController(title: "Photos Access Denied", message: "App needs access to photos library.", preferredStyle: .alert) 28 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 29 | self.window?.rootViewController?.present(alert, animated: true, completion: nil) 30 | } 31 | }) 32 | } else if photos == .authorized { 33 | gotoVC() 34 | } 35 | 36 | return true 37 | } 38 | 39 | func gotoVC() { 40 | DispatchQueue.main.async(execute: { () -> Void in 41 | self.window = UIWindow(frame: UIScreen.main.bounds) 42 | if let window = self.window { 43 | window.backgroundColor = UIColor.white 44 | 45 | let nav = UINavigationController() 46 | let mainView = ViewController() 47 | nav.viewControllers = [mainView] 48 | window.rootViewController = nav 49 | window.makeKeyAndVisible() 50 | } 51 | }) 52 | } 53 | 54 | func applicationWillResignActive(_ application: UIApplication) { 55 | // 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. 56 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 57 | } 58 | 59 | func applicationDidEnterBackground(_ application: UIApplication) { 60 | // 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. 61 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 62 | } 63 | 64 | func applicationWillEnterForeground(_ application: UIApplication) { 65 | // 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. 66 | } 67 | 68 | func applicationDidBecomeActive(_ application: UIApplication) { 69 | // 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. 70 | } 71 | 72 | func applicationWillTerminate(_ application: UIApplication) { 73 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 74 | } 75 | 76 | 77 | } 78 | 79 | -------------------------------------------------------------------------------- /photosApp2/ImagePreviewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePreviewVC.swift 3 | // photosApp2 4 | // 5 | // Created by Muskan on 10/4/17. 6 | // Copyright © 2017 akhil. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImagePreviewVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 12 | 13 | var myCollectionView: UICollectionView! 14 | var imgArray = [UIImage]() 15 | var passedContentOffset = IndexPath() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Do any additional setup after loading the view. 21 | 22 | self.view.backgroundColor=UIColor.black 23 | 24 | let layout = UICollectionViewFlowLayout() 25 | layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 26 | layout.minimumInteritemSpacing=0 27 | layout.minimumLineSpacing=0 28 | layout.scrollDirection = .horizontal 29 | 30 | myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout) 31 | myCollectionView.delegate=self 32 | myCollectionView.dataSource=self 33 | myCollectionView.register(ImagePreviewFullViewCell.self, forCellWithReuseIdentifier: "Cell") 34 | myCollectionView.isPagingEnabled = true 35 | myCollectionView.scrollToItem(at: passedContentOffset, at: .left, animated: true) 36 | 37 | self.view.addSubview(myCollectionView) 38 | 39 | myCollectionView.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleHeight.rawValue))) 40 | } 41 | 42 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 43 | return imgArray.count 44 | } 45 | 46 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 47 | let cell=collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ImagePreviewFullViewCell 48 | cell.imgView.image=imgArray[indexPath.row] 49 | return cell 50 | } 51 | 52 | override func viewWillLayoutSubviews() { 53 | super.viewWillLayoutSubviews() 54 | 55 | guard let flowLayout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } 56 | 57 | flowLayout.itemSize = myCollectionView.frame.size 58 | 59 | flowLayout.invalidateLayout() 60 | 61 | myCollectionView.collectionViewLayout.invalidateLayout() 62 | } 63 | 64 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 65 | super.viewWillTransition(to: size, with: coordinator) 66 | let offset = myCollectionView.contentOffset 67 | let width = myCollectionView.bounds.size.width 68 | 69 | let index = round(offset.x / width) 70 | let newOffset = CGPoint(x: index * size.width, y: offset.y) 71 | 72 | myCollectionView.setContentOffset(newOffset, animated: false) 73 | 74 | coordinator.animate(alongsideTransition: { (context) in 75 | self.myCollectionView.reloadData() 76 | 77 | self.myCollectionView.setContentOffset(newOffset, animated: false) 78 | }, completion: nil) 79 | } 80 | 81 | } 82 | 83 | 84 | class ImagePreviewFullViewCell: UICollectionViewCell, UIScrollViewDelegate { 85 | 86 | var scrollImg: UIScrollView! 87 | var imgView: UIImageView! 88 | 89 | override init(frame: CGRect) { 90 | super.init(frame: frame) 91 | 92 | scrollImg = UIScrollView() 93 | scrollImg.delegate = self 94 | scrollImg.alwaysBounceVertical = false 95 | scrollImg.alwaysBounceHorizontal = false 96 | scrollImg.showsVerticalScrollIndicator = true 97 | scrollImg.flashScrollIndicators() 98 | 99 | scrollImg.minimumZoomScale = 1.0 100 | scrollImg.maximumZoomScale = 4.0 101 | 102 | let doubleTapGest = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTapScrollView(recognizer:))) 103 | doubleTapGest.numberOfTapsRequired = 2 104 | scrollImg.addGestureRecognizer(doubleTapGest) 105 | 106 | self.addSubview(scrollImg) 107 | 108 | imgView = UIImageView() 109 | imgView.image = UIImage(named: "user3") 110 | scrollImg.addSubview(imgView!) 111 | imgView.contentMode = .scaleAspectFit 112 | } 113 | 114 | @objc func handleDoubleTapScrollView(recognizer: UITapGestureRecognizer) { 115 | if scrollImg.zoomScale == 1 { 116 | scrollImg.zoom(to: zoomRectForScale(scale: scrollImg.maximumZoomScale, center: recognizer.location(in: recognizer.view)), animated: true) 117 | } else { 118 | scrollImg.setZoomScale(1, animated: true) 119 | } 120 | } 121 | 122 | func zoomRectForScale(scale: CGFloat, center: CGPoint) -> CGRect { 123 | var zoomRect = CGRect.zero 124 | zoomRect.size.height = imgView.frame.size.height / scale 125 | zoomRect.size.width = imgView.frame.size.width / scale 126 | let newCenter = imgView.convert(center, from: scrollImg) 127 | zoomRect.origin.x = newCenter.x - (zoomRect.size.width / 2.0) 128 | zoomRect.origin.y = newCenter.y - (zoomRect.size.height / 2.0) 129 | return zoomRect 130 | } 131 | 132 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 133 | return self.imgView 134 | } 135 | 136 | override func layoutSubviews() { 137 | super.layoutSubviews() 138 | scrollImg.frame = self.bounds 139 | imgView.frame = self.bounds 140 | } 141 | 142 | override func prepareForReuse() { 143 | super.prepareForReuse() 144 | scrollImg.setZoomScale(1, animated: true) 145 | } 146 | 147 | required init?(coder aDecoder: NSCoder) { 148 | fatalError("init(coder:) has not been implemented") 149 | } 150 | } 151 | 152 | -------------------------------------------------------------------------------- /photosApp2/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // photosApp2 4 | // 5 | // Created by Muskan on 10/4/17. 6 | // Copyright © 2017 akhil. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UINavigationControllerDelegate { 13 | 14 | var myCollectionView: UICollectionView! 15 | var imageArray=[UIImage]() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | // Do any additional setup after loading the view, typically from a nib. 20 | 21 | self.title = "Photos" 22 | 23 | let layout = UICollectionViewFlowLayout() 24 | 25 | myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout) 26 | myCollectionView.delegate=self 27 | myCollectionView.dataSource=self 28 | myCollectionView.register(PhotoItemCell.self, forCellWithReuseIdentifier: "Cell") 29 | myCollectionView.backgroundColor=UIColor.white 30 | self.view.addSubview(myCollectionView) 31 | 32 | myCollectionView.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleHeight.rawValue))) 33 | 34 | grabPhotos() 35 | } 36 | 37 | //MARK: CollectionView 38 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 39 | return imageArray.count 40 | } 41 | 42 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 43 | let cell=collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! PhotoItemCell 44 | cell.img.image=imageArray[indexPath.item] 45 | return cell 46 | } 47 | 48 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 49 | print(indexPath) 50 | let vc=ImagePreviewVC() 51 | vc.imgArray = self.imageArray 52 | vc.passedContentOffset = indexPath 53 | self.navigationController?.pushViewController(vc, animated: true) 54 | } 55 | 56 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 57 | let width = collectionView.frame.width 58 | // if UIDevice.current.orientation.isPortrait { 59 | // return CGSize(width: width/4 - 1, height: width/4 - 1) 60 | // } else { 61 | // return CGSize(width: width/6 - 1, height: width/6 - 1) 62 | // } 63 | if DeviceInfo.Orientation.isPortrait { 64 | return CGSize(width: width/4 - 1, height: width/4 - 1) 65 | } else { 66 | return CGSize(width: width/6 - 1, height: width/6 - 1) 67 | } 68 | } 69 | 70 | override func viewWillLayoutSubviews() { 71 | super.viewWillLayoutSubviews() 72 | myCollectionView.collectionViewLayout.invalidateLayout() 73 | } 74 | 75 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 76 | return 1.0 77 | } 78 | 79 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { 80 | return 1.0 81 | } 82 | 83 | //MARK: grab photos 84 | func grabPhotos(){ 85 | imageArray = [] 86 | 87 | DispatchQueue.global(qos: .background).async { 88 | print("This is run on the background queue") 89 | let imgManager=PHImageManager.default() 90 | 91 | let requestOptions=PHImageRequestOptions() 92 | requestOptions.isSynchronous=true 93 | requestOptions.deliveryMode = .highQualityFormat 94 | 95 | let fetchOptions=PHFetchOptions() 96 | fetchOptions.sortDescriptors=[NSSortDescriptor(key:"creationDate", ascending: false)] 97 | 98 | let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions) 99 | print(fetchResult) 100 | print(fetchResult.count) 101 | if fetchResult.count > 0 { 102 | for i in 0..