├── .gitignore ├── Code ├── AnimatedFlowLayout.swift ├── AppDelegate.swift ├── Asset.swift ├── Author.swift ├── ContentfulDataManager.swift ├── GalleryHeaderView.swift ├── Image.swift ├── ImageCollectionViewCell.swift ├── ImageDetailsViewController.swift ├── ImagesByGalleryViewController.swift ├── ImagesPageViewController.swift ├── PhotoGallery.swift ├── SyncInfo.swift └── UIKit.swift ├── Gallery.xcdatamodeld ├── .xccurrentversion ├── Gallery.xcdatamodel │ └── contents └── Gallery2.xcdatamodel │ └── contents ├── Gallery.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Gallery.xcscheme ├── Gallery.xcworkspace └── contents.xcworkspacedata ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── Podfile ├── Podfile.lock ├── README.md ├── Resources ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon-Spotlight-40@2x.png │ │ └── Icon-Spotlight-40@3x.png └── gallery-app-icon.png ├── Supporting Files ├── BridgingHeader.h └── Info.plist └── Templates └── gallery.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## CocoaPods 2 | 3 | Pods 4 | 5 | ## Node 6 | 7 | node_modules 8 | 9 | ## OS X 10 | 11 | .DS_Store 12 | 13 | ## Other 14 | 15 | .gutter.json 16 | .pt 17 | 18 | ## Xcode 19 | 20 | *.xccheckout 21 | xcuserdata 22 | *.xcscmblueprint 23 | -------------------------------------------------------------------------------- /Code/AnimatedFlowLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatedFlowLayout.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 16/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AnimatedFlowLayout: UICollectionViewFlowLayout { 12 | var indexPathsToAnimate = [IndexPath]() 13 | var showsHeader: Bool = false { 14 | didSet { 15 | if showsHeader { 16 | if let collectionView = collectionView { 17 | headerReferenceSize = CGSize(width: collectionView.frame.size.width, height: 160.0) 18 | return 19 | } 20 | } 21 | 22 | headerReferenceSize = .zero 23 | } 24 | } 25 | 26 | func calculateItemSizeForBounds(bounds: CGRect) { 27 | let width = Int((bounds.width - 2.0) / 2) 28 | itemSize = CGSize(width: width, height: width) 29 | } 30 | 31 | override func finalizeCollectionViewUpdates() { 32 | super.finalizeCollectionViewUpdates() 33 | 34 | indexPathsToAnimate.removeAll() 35 | } 36 | 37 | override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 38 | let attr = layoutAttributesForItem(at: itemIndexPath) 39 | 40 | if let attr = attr, indexPathsToAnimate.contains(itemIndexPath) { 41 | attr.transform = CGAffineTransform(scaleX: 0.2, y: 0.2).rotated(by: CGFloat(Double.pi)) 42 | attr.center = CGPoint(x: collectionView!.bounds.midX, y: collectionView!.bounds.maxY) 43 | 44 | indexPathsToAnimate.remove(at: indexPathsToAnimate.index(of: itemIndexPath)!) 45 | } 46 | 47 | return attr 48 | } 49 | 50 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 51 | if (newBounds != collectionView?.bounds) { 52 | calculateItemSizeForBounds(bounds: newBounds) 53 | return true 54 | } 55 | 56 | return false 57 | } 58 | 59 | override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) { 60 | super.prepare(forCollectionViewUpdates: updateItems) 61 | 62 | indexPathsToAnimate += updateItems.map { (element) -> IndexPath? in 63 | return (element as UICollectionViewUpdateItem).indexPathAfterUpdate 64 | }.compactMap { $0 } 65 | } 66 | 67 | override func prepare() { 68 | if let collectionView = collectionView { 69 | calculateItemSizeForBounds(bounds: collectionView.bounds) 70 | 71 | minimumInteritemSpacing = 1.0 72 | minimumLineSpacing = 1.0 73 | } 74 | 75 | super.prepare() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Code/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 03/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Keys 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 18 | 19 | window?.backgroundColor = .white 20 | return true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Code/Asset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Asset.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 04/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import ContentfulPersistence 12 | 13 | class Asset: NSManagedObject, AssetPersistable { 14 | 15 | @NSManaged var id: String 16 | @NSManaged var localeCode: String? 17 | @NSManaged var title: String? 18 | @NSManaged var assetDescription: String? 19 | @NSManaged var urlString: String? 20 | @NSManaged var createdAt: Date? 21 | @NSManaged var updatedAt: Date? 22 | 23 | @NSManaged var size: NSNumber? 24 | @NSManaged var width: NSNumber? 25 | @NSManaged var height: NSNumber? 26 | @NSManaged var fileType: String? 27 | @NSManaged var fileName: String? 28 | 29 | @NSManaged var coverImage_79h5TZwqOWy0ygOKGs2Wky_Inverse: NSSet 30 | @NSManaged var images_79h5TZwqOWy0ygOKGs2Wky_Inverse: NSSet 31 | @NSManaged var photo_1xYw5JsIecuGE68mmGMg20_Inverse: NSSet 32 | @NSManaged var profilePhoto_38nK0gXXIccQ2IEosyAg6C_Inverse: NSSet 33 | } 34 | -------------------------------------------------------------------------------- /Code/Author.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Author.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 04/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import Contentful 12 | import ContentfulPersistence 13 | 14 | class Author: NSManagedObject, EntryPersistable { 15 | 16 | static let contentTypeId = "author" 17 | 18 | @NSManaged var id: String 19 | @NSManaged var localeCode: String? 20 | @NSManaged var createdAt: Date? 21 | @NSManaged var updatedAt: Date? 22 | 23 | @NSManaged var biography: String? 24 | @NSManaged var name: String 25 | @NSManaged var twitterHandle: String? 26 | @NSManaged var authorInverse: NSSet 27 | @NSManaged var createdEntries: NSOrderedSet 28 | @NSManaged var profilePhoto: Asset? 29 | 30 | static func fieldMapping() -> [FieldName: String] { 31 | return [ 32 | "name": "name", 33 | "biography": "biography", 34 | "twitterHandle": "twitterHandle", 35 | "createdEntries": "createdEntries", 36 | "profilePhoto": "profilePhoto" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Code/ContentfulDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentfulDataManager.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 04/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | import Contentful 11 | import ContentfulPersistence 12 | import Keys 13 | 14 | class ContentfulDataManager { 15 | 16 | let coreDataStore: CoreDataStore 17 | let managedObjectContext: NSManagedObjectContext 18 | let contentfulSynchronizer: SynchronizationManager 19 | 20 | static let storeURL = FileManager.default.urls(for: .documentDirectory, 21 | in: .userDomainMask).last?.appendingPathComponent("Gallery.sqlite") 22 | 23 | static func setupManagedObjectContext() -> NSManagedObjectContext { 24 | let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 25 | 26 | let modelURL = Bundle(for: ContentfulDataManager.self).url(forResource: "Gallery", withExtension: "momd")! 27 | 28 | let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)! 29 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) 30 | do { 31 | try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: ContentfulDataManager.storeURL!, options: nil) 32 | } catch { 33 | fatalError() 34 | } 35 | 36 | managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator 37 | return managedObjectContext 38 | } 39 | 40 | func fetchGalleries(predicate: String? = nil) -> [Photo_Gallery] { 41 | let fetchPredicate = predicate != nil ? NSPredicate(format: predicate!) : NSPredicate(value: true) 42 | return try! coreDataStore.fetchAll(type: Photo_Gallery.self, predicate: fetchPredicate) 43 | } 44 | 45 | func fetchImages(predicate: String? = nil) -> [Image] { 46 | 47 | let fetchPredicate = predicate != nil ? NSPredicate(format: predicate!) : NSPredicate(value: true) 48 | return try! coreDataStore.fetchAll(type: Image.self, predicate: fetchPredicate) 49 | } 50 | 51 | init() { 52 | let model = PersistenceModel(spaceType: SyncInfo.self, 53 | assetType: Asset.self, 54 | entryTypes: [Image.self, Photo_Gallery.self, Author.self]) 55 | 56 | let managedObjectContext = ContentfulDataManager.setupManagedObjectContext() 57 | let coreDataStore = CoreDataStore(context: managedObjectContext) 58 | self.managedObjectContext = managedObjectContext 59 | self.coreDataStore = coreDataStore 60 | let keys = GalleryKeys() 61 | let client = Client(spaceId: keys.gallerySpaceId, 62 | accessToken: keys.galleryAccessToken) 63 | let contentfulSynchronizer = SynchronizationManager(client: client, 64 | localizationScheme: .default, 65 | persistenceStore: coreDataStore, 66 | persistenceModel: model) 67 | self.contentfulSynchronizer = contentfulSynchronizer 68 | } 69 | 70 | func performSynchronization(completion: @escaping ResultsHandler) { 71 | contentfulSynchronizer.sync { result in 72 | completion(result) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Code/GalleryHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GalleryHeaderView.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 02/03/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class GalleryHeaderView: UICollectionReusableView { 12 | let backgroundImageView = UIImageView() 13 | let textLabel = UILabel() 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | 18 | let effect = UIBlurEffect(style: .dark) 19 | let backgroundView = UIVisualEffectView(effect: effect) 20 | backgroundView.frame.size = frame.size 21 | backgroundView.contentView.addSubview(backgroundImageView) 22 | backgroundView.contentView.addSubview(textLabel) 23 | addSubview(backgroundView) 24 | 25 | backgroundImageView.alpha = 0.5 26 | backgroundImageView.contentMode = .scaleAspectFill 27 | 28 | textLabel.backgroundColor = .clear 29 | // textLabel.font = UIFont.boldTitleFont() 30 | textLabel.numberOfLines = 0 31 | textLabel.shadowColor = .black 32 | textLabel.shadowOffset = CGSize(width: 1.0, height: 1.0) 33 | textLabel.textAlignment = .center 34 | textLabel.textColor = .white 35 | } 36 | 37 | required init?(coder aDecoder: NSCoder) { 38 | fatalError("init(coder:) has not been implemented") 39 | } 40 | 41 | override func layoutSubviews() { 42 | super.layoutSubviews() 43 | 44 | backgroundImageView.frame.size = frame.size 45 | textLabel.frame.size = frame.size 46 | subviews.first?.frame.size = frame.size 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Code/Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 04/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import Contentful 12 | import ContentfulPersistence 13 | 14 | class Image: NSManagedObject, EntryPersistable { 15 | 16 | static let contentTypeId = "image" 17 | 18 | @NSManaged var localeCode: String? 19 | @NSManaged var id: String 20 | @NSManaged var createdAt: Date? 21 | @NSManaged var updatedAt: Date? 22 | @NSManaged var title: String 23 | @NSManaged var imageCaption: String? 24 | @NSManaged var imageCredits: String? 25 | @NSManaged var photo: Asset? 26 | 27 | @NSManaged var createdEntriesInverse: NSSet 28 | @NSManaged var imagesInverse: NSSet 29 | 30 | static func fieldMapping() -> [FieldName: String] { 31 | return [ 32 | "title": "title", 33 | "photo": "photo", 34 | "imageCaption": "imageCaption", 35 | "imageCredits": "imageCredits" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Code/ImageCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCollectionViewCell.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 16/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGSize { 12 | func screenSize() -> CGSize { 13 | let scale = UIScreen.main.nativeScale 14 | return CGSize(width: width * scale, height: height * scale) 15 | } 16 | } 17 | 18 | class ImageCollectionViewCell: UICollectionViewCell { 19 | 20 | let imageView: UIImageView 21 | let shadowView: UIView 22 | 23 | override init(frame: CGRect) { 24 | imageView = UIImageView(frame: frame) 25 | imageView.alpha = 0.9 26 | imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 27 | imageView.clipsToBounds = true 28 | imageView.contentMode = .scaleAspectFill 29 | imageView.layer.cornerRadius = 2.0 30 | 31 | shadowView = UIView(frame: frame) 32 | shadowView.backgroundColor = .white 33 | shadowView.layer.shadowColor = UIColor.black.cgColor 34 | shadowView.layer.shadowOffset = CGSize(width: 2.0, height: 2.0) 35 | shadowView.layer.shadowOpacity = 0.5 36 | 37 | super.init(frame: frame) 38 | 39 | addSubview(shadowView) 40 | addSubview(imageView) 41 | } 42 | 43 | required init?(coder aDecoder: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | 47 | override func layoutSubviews() { 48 | imageView.frame = bounds.insetBy(dx: 5.0, dy: 5.0) 49 | shadowView.frame = bounds.insetBy(dx: 5.0, dy: 5.0) 50 | 51 | super.layoutSubviews() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Code/ImageDetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageDetailsViewController.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 03/03/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import markymark 11 | 12 | let metaInformationHeight: CGFloat = 100.0 13 | 14 | class ImageDetailsViewController: UIViewController, UIScrollViewDelegate { 15 | 16 | let imageView = UIImageView(frame: .zero) 17 | 18 | let metaInformationView = UITextView(frame: .zero) 19 | 20 | weak var pageViewController: UIPageViewController? 21 | 22 | let scrollView = UIScrollView(frame: .zero) 23 | 24 | init() { 25 | super.init(nibName: nil, bundle: nil) 26 | } 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | override func viewWillTransition(to size: CGSize, 33 | with coordinator: UIViewControllerTransitionCoordinator) { 34 | computeFrames() 35 | } 36 | 37 | func computeFrames() { 38 | // FIXME: 39 | 40 | switch UIDevice.current.orientation { 41 | case .portrait, .portraitUpsideDown: 42 | // TODO: leave room for metaInformationHeight 43 | scrollView.frame.size.height = view.frame.size.height - metaInformationHeight 44 | metaInformationView.frame.size.height = metaInformationHeight 45 | default: 46 | scrollView.frame.size.height = view.frame.size.height 47 | metaInformationView.frame.origin.y = view.frame.maxY 48 | 49 | } 50 | metaInformationView.frame.size.height = metaInformationHeight 51 | metaInformationView.frame.origin.y = scrollView.frame.maxY 52 | } 53 | 54 | func defaultZoom() { 55 | guard let image = imageView.image else { return } 56 | 57 | let xZoom = image.size.width / scrollView.frame.size.width 58 | let yZoom = image.size.height / scrollView.frame.size.height 59 | scrollView.zoomScale = max(xZoom, yZoom) 60 | scrollViewDidZoom(scrollView) 61 | } 62 | 63 | func statusBarStyleForBackgroundColor(color: UIColor?) -> UIBarStyle { 64 | guard let componentColors = color?.cgColor.components else { return .black } 65 | 66 | var darknessScore = componentColors[0] * 255 * 299 67 | darknessScore += componentColors[1] * 255 * 587 68 | darknessScore += componentColors[2] * 255 * 114 69 | darknessScore /= 1000.0 70 | 71 | return (darknessScore >= 125.0) ? .default : .black 72 | } 73 | 74 | func updateImage(image: UIImage?) { 75 | guard image != nil else { 76 | view.backgroundColor = .white 77 | imageView.backgroundColor = .white 78 | return 79 | } 80 | 81 | defaultZoom() 82 | 83 | DispatchQueue.global(qos: DispatchQoS.default.qosClass).async { 84 | let size = CGSize(width: 100.0, height: 100.0) 85 | 86 | UIGraphicsBeginImageContextWithOptions(size, true, 0.0) 87 | self.imageView.image?.draw(in: CGRect(origin: .zero, size: size)) 88 | 89 | DispatchQueue.main.async { 90 | self.updateNavigationBar(force: false) 91 | } 92 | } 93 | } 94 | 95 | func updateNavigationBar(force: Bool) { 96 | if let viewController = pageViewController, let firstVC = viewController.viewControllers?.first { 97 | if !force && firstVC !== self { 98 | return 99 | } 100 | 101 | if let navBar = viewController.navigationController?.navigationBar { 102 | navBar.barStyle = statusBarStyleForBackgroundColor(color: view.backgroundColor) 103 | navBar.barTintColor = view.backgroundColor 104 | } 105 | } 106 | } 107 | 108 | // TODO: Move. 109 | static func attributedMarkdownText(text: String, font: UIFont) -> NSAttributedString { 110 | let markyMark = MarkyMark() { $0.setFlavor(ContentfulFlavor()) } 111 | let markdownItems = markyMark.parseMarkDown(text) 112 | let styling = DefaultStyling() 113 | let config = MarkDownToAttributedStringConverterConfiguration(styling: styling) 114 | // Configure markymark to leverage the Contentful images API when encountering inline SVGs. 115 | 116 | let converter = MarkDownConverter(configuration: config) 117 | let attributedText = converter.convert(markdownItems) 118 | 119 | let range = NSRange(location: 0, length: attributedText.length) 120 | attributedText.addAttributes([.font: font], range: range) 121 | return attributedText 122 | } 123 | 124 | func updateText(_ text: String) { 125 | metaInformationView.attributedText = ImageDetailsViewController.attributedMarkdownText(text: text, font: UIFont.systemFont(ofSize: 14.0, weight: .regular)) 126 | metaInformationView.textColor = .white 127 | } 128 | 129 | override func viewDidLoad() { 130 | super.viewDidLoad() 131 | 132 | for view in [metaInformationView, imageView, scrollView] { 133 | view.autoresizingMask = .flexibleWidth 134 | view.frame.size.width = self.view.frame.size.width 135 | } 136 | 137 | metaInformationView.backgroundColor = .clear 138 | metaInformationView.isEditable = false 139 | metaInformationView.textContainerInset = UIEdgeInsets(top: 0.0, left: 20.0, bottom: 0.0, right: 20.0) 140 | 141 | imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 142 | imageView.clipsToBounds = true 143 | imageView.contentMode = .scaleAspectFit 144 | 145 | scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 146 | scrollView.delegate = self 147 | scrollView.maximumZoomScale = 2.0 148 | scrollView.addSubview(imageView) 149 | 150 | let recognizer = UITapGestureRecognizer(target: self, action: #selector(doubleTapped)) 151 | recognizer.numberOfTapsRequired = 2 152 | scrollView.addGestureRecognizer(recognizer) 153 | 154 | view.addSubview(metaInformationView) 155 | view.addSubview(scrollView) 156 | } 157 | 158 | override func viewWillAppear(_ animated: Bool) { 159 | super.viewWillAppear(animated) 160 | 161 | computeFrames() 162 | defaultZoom() 163 | updateNavigationBar(force: true) 164 | } 165 | 166 | // MARK: Actions 167 | 168 | @objc func doubleTapped() { 169 | UIView.animate(withDuration: 0.1) { 170 | self.defaultZoom() 171 | } 172 | } 173 | 174 | // MARK: UIScrollViewDelegate 175 | 176 | func scrollViewDidZoom(_ scrollView: UIScrollView) { 177 | let innerFrame = imageView.frame 178 | let scrollerBounds = scrollView.bounds 179 | 180 | if (innerFrame.size.width < scrollerBounds.size.width) || (innerFrame.size.height < scrollerBounds.size.height) { 181 | scrollView.contentOffset = CGPoint(x: imageView.center.x - (scrollerBounds.size.width / 2), y: imageView.center.y - (scrollerBounds.size.height / 2)) 182 | } 183 | 184 | var insets = UIEdgeInsets.zero 185 | 186 | if (scrollerBounds.size.width > innerFrame.size.width) { 187 | insets.left = (scrollerBounds.size.width - innerFrame.size.width) / 2 188 | insets.right = -insets.left 189 | } 190 | 191 | if (scrollerBounds.size.height > innerFrame.size.height) { 192 | insets.top = (scrollerBounds.size.height - innerFrame.size.height) / 2 193 | insets.bottom = -insets.top 194 | } 195 | 196 | scrollView.contentInset = insets 197 | } 198 | 199 | func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { 200 | return imageView 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Code/ImagesByGalleryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagesByGalleryViewController.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 26/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AlamofireImage 11 | import Contentful 12 | 13 | class ImagesByGalleryViewController: UICollectionViewController, 14 | UINavigationControllerDelegate, 15 | SingleImageViewControllerDelegate { 16 | 17 | lazy var dataManager = ContentfulDataManager() 18 | 19 | var selectedIndexPath: IndexPath? 20 | 21 | var galleries: [Photo_Gallery] = [Photo_Gallery]() { 22 | didSet { 23 | if let layout = collectionView?.collectionViewLayout as? AnimatedFlowLayout { 24 | layout.showsHeader = true 25 | } 26 | 27 | collectionView?.reloadData() 28 | } 29 | } 30 | 31 | 32 | func refresh() { 33 | dataManager.performSynchronization() { [weak self] result in 34 | guard let strongSelf = self else { return } 35 | 36 | switch result { 37 | case .success: 38 | strongSelf.galleries = strongSelf.dataManager.fetchGalleries().sorted() { $0.title! < $1.title! } 39 | strongSelf.collectionView?.reloadData() 40 | 41 | case .error(let error as NSError) where error.code != NSURLErrorNotConnectedToInternet: 42 | strongSelf.galleries = strongSelf.dataManager.fetchGalleries().sorted() { $0.title! < $1.title! } 43 | strongSelf.collectionView?.reloadData() 44 | 45 | strongSelf.showAlertController(for: error) 46 | case .error(let error): 47 | strongSelf.galleries = strongSelf.dataManager.fetchGalleries().sorted() { $0.title! < $1.title! } 48 | strongSelf.collectionView?.reloadData() 49 | 50 | strongSelf.showAlertController(for: error) 51 | } 52 | } 53 | } 54 | 55 | override func viewDidLoad() { 56 | super.viewDidLoad() 57 | 58 | addInfoButton() 59 | 60 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 61 | 62 | collectionView?.register(ImageCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ImageCollectionViewCell.self)) 63 | collectionView?.register(GalleryHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: String(describing: GalleryHeaderView.self)) 64 | 65 | refresh() 66 | } 67 | 68 | // MARK: Private 69 | 70 | func showAlertController(for error: Error) { 71 | let alertController = UIAlertController(title: NSLocalizedString("Error", comment: ""), message: error.localizedDescription, preferredStyle: UIAlertController.Style.alert) 72 | let cancelAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel) { _ in 73 | alertController.dismiss(animated: true, completion: nil) 74 | } 75 | alertController.addAction(cancelAction) 76 | present(alertController, animated: true, completion: nil) 77 | } 78 | 79 | // MARK: SingleImageViewControllerDelegate 80 | 81 | func updateCurrentIndex(_ index: Int) { 82 | if let selectedIndexPath = selectedIndexPath { 83 | self.selectedIndexPath = IndexPath(item: index, section: selectedIndexPath.section) 84 | } else { 85 | self.selectedIndexPath = IndexPath(item: index, section: 0) 86 | } 87 | } 88 | 89 | // MARK: UICollectionViewDataSource 90 | 91 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 92 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ImageCollectionViewCell.self), for: indexPath as IndexPath) as! ImageCollectionViewCell 93 | 94 | let image = galleries[indexPath.section].images[indexPath.item] as? Image 95 | 96 | if let asset = image?.photo, let urlString = asset.urlString { 97 | let size = UIScreen.main.bounds.size 98 | let imageOptions = [ImageOption.width(UInt(size.width)), ImageOption.height(UInt(size.height))] 99 | let url = try! urlString.url(with: imageOptions) 100 | 101 | cell.imageView.image = nil 102 | cell.imageView.af_setImage(withURL: url, 103 | imageTransition: .crossDissolve(0.2)) 104 | } 105 | 106 | return cell 107 | } 108 | 109 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 110 | return galleries[section].images.count 111 | } 112 | 113 | override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 114 | if kind == UICollectionView.elementKindSectionHeader { 115 | let gallery = galleries[indexPath.section] 116 | let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: GalleryHeaderView.self), for: indexPath) as! GalleryHeaderView 117 | 118 | if let asset = gallery.coverImage, let urlString = asset.urlString, let url = URL(string: urlString) { 119 | view.backgroundImageView.af_setImage(withURL: url, 120 | imageTransition: .crossDissolve(0.2)) 121 | } 122 | 123 | view.textLabel.text = gallery.title 124 | 125 | return view 126 | } 127 | 128 | return UICollectionReusableView() 129 | } 130 | 131 | override func numberOfSections(in collectionView: UICollectionView) -> Int { 132 | return galleries.count 133 | } 134 | 135 | // MARK: UICollectionViewDelegate 136 | 137 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 138 | selectedIndexPath = indexPath 139 | 140 | let imagesPageViewController = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ImagesPageViewController") as! ImagesPageViewController 141 | imagesPageViewController.gallery = galleries[indexPath.section] 142 | imagesPageViewController.initialIndex = indexPath.row 143 | navigationController?.pushViewController(imagesPageViewController, animated: true) 144 | } 145 | 146 | // MARK: UINavigationControllerDelegate 147 | 148 | public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { 149 | guard viewController == self else { return } 150 | let navBar = navigationController.navigationBar 151 | navBar.barStyle = .default 152 | navBar.barTintColor = nil 153 | navBar.tintColor = UIView().tintColor 154 | navBar.titleTextAttributes = [.foregroundColor: UIColor.black] 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Code/ImagesPageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagesPageViewController.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 17/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Contentful 11 | 12 | protocol SingleImageViewControllerDelegate: class { 13 | func updateCurrentIndex(_ index: Int) 14 | } 15 | 16 | class ImagesPageViewController: UIPageViewController, 17 | UIPageViewControllerDataSource, 18 | UIPageViewControllerDelegate { 19 | 20 | var gallery: Photo_Gallery? 21 | 22 | lazy var images: [Image] = { 23 | if let gallery = gallery { 24 | return gallery.images.array as! [Image] 25 | } 26 | return [Image]() 27 | }() 28 | 29 | var initialIndex = 0 30 | 31 | weak var singleImageDelegate: SingleImageViewControllerDelegate? 32 | 33 | func updateCurrentIndex(_ index: Int) { 34 | if index < 0 || index > images.count { 35 | return 36 | } 37 | 38 | if (index == 0) { 39 | title = gallery?.title 40 | } else { 41 | let image = images[index - 1] 42 | title = image.title 43 | } 44 | 45 | if let delegate = singleImageDelegate { 46 | delegate.updateCurrentIndex(index - 1) 47 | } 48 | } 49 | 50 | func viewControllerWithIndex(index: Int) -> ImageDetailsViewController? { 51 | if index < 0 || index > images.count { 52 | return nil 53 | } 54 | 55 | let asset = index == 0 ? gallery?.coverImage : images[index - 1].photo 56 | let vc = ImageDetailsViewController() 57 | vc.pageViewController = self 58 | vc.view.tag = index 59 | 60 | var title = NSLocalizedString("Untitled", comment: "") 61 | var description = "" 62 | 63 | if (index == 0) { 64 | title = gallery?.title ?? title 65 | description = gallery?.galleryDescription ?? description 66 | } else { 67 | title = images[index - 1].imageCaption ?? title 68 | description = images[index - 1].imageCredits ?? description 69 | } 70 | 71 | vc.updateText("# \(title)\n\n\(description)") 72 | 73 | if let asset = asset, let urlString = asset.urlString { 74 | let size = UIScreen.main.bounds.size 75 | let imageOptions = [ImageOption.width(UInt(size.width)), ImageOption.height(UInt(size.height))] 76 | let url = try! urlString.url(with: imageOptions) 77 | 78 | vc.imageView.image = nil 79 | vc.imageView.af_setImage(withURL: url) 80 | } 81 | 82 | let _ = view.subviews.first?.gestureRecognizers?.map { (recognizer) -> Void in vc.scrollView.panGestureRecognizer.require(toFail: recognizer as UIGestureRecognizer) } 83 | 84 | return vc 85 | } 86 | 87 | override func viewDidLoad() { 88 | super.viewDidLoad() 89 | 90 | delegate = self 91 | dataSource = self 92 | } 93 | 94 | override func viewWillAppear(_ animated: Bool) { 95 | super.viewWillAppear(animated) 96 | view.backgroundColor = .black 97 | 98 | if let imageVC = viewControllerWithIndex(index: initialIndex + 1) { 99 | setViewControllers([imageVC], direction: .forward, animated: false) { (finished) in 100 | if finished { 101 | self.updateCurrentIndex(self.initialIndex + 1) 102 | } 103 | } 104 | } 105 | } 106 | 107 | // MARK: UIPageViewControllerDataSource 108 | 109 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { 110 | let currentIndex = viewController.view.tag 111 | return viewControllerWithIndex(index: currentIndex - 1) 112 | } 113 | 114 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { 115 | let currentIndex = viewController.view.tag 116 | return viewControllerWithIndex(index: currentIndex + 1) 117 | } 118 | 119 | // MARK: UIPageViewControllerDelegate 120 | 121 | func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { 122 | if let firstVC = viewControllers?.first, completed { 123 | let currentIndex = firstVC.view.tag 124 | updateCurrentIndex(currentIndex) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Code/PhotoGallery.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoGallery.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 04/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import Contentful 12 | import ContentfulPersistence 13 | 14 | class Photo_Gallery: NSManagedObject, EntryPersistable { 15 | 16 | static let contentTypeId = "photoGallery" 17 | 18 | @NSManaged var id: String 19 | @NSManaged var localeCode: String? 20 | @NSManaged var createdAt: Date? 21 | @NSManaged var updatedAt: Date? 22 | 23 | @NSManaged var title: String? 24 | @NSManaged var date: Date? 25 | @NSManaged var galleryDescription: String? 26 | @NSManaged var slug: String? 27 | @NSManaged var author: Author? 28 | @NSManaged var coverImage: Asset? 29 | @NSManaged var images: NSOrderedSet 30 | @NSManaged var authors: NSOrderedSet? 31 | 32 | static func fieldMapping() -> [FieldName: String] { 33 | return [ 34 | "title": "title", 35 | "date": "date", 36 | "description": "galleryDescription", 37 | "coverImage": "coverImage", 38 | "slug": "slug", 39 | "author": "author", 40 | "images": "images", 41 | "authors": "authors" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Code/SyncInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncInfo.swift 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 04/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import ContentfulPersistence 12 | 13 | class SyncInfo: NSManagedObject, SyncSpacePersistable { 14 | 15 | @NSManaged var syncToken: String? 16 | } 17 | -------------------------------------------------------------------------------- /Code/UIKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKit.swift 3 | // Blog 4 | // 5 | // Created by Boris Bügling on 28/01/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ContentfulDialogs 11 | 12 | extension UIViewController { 13 | func addInfoButton() { 14 | let infoButton = UIButton(type: .infoLight) as UIButton 15 | infoButton.addTarget(self, action: #selector(infoTapped), for: .touchUpInside) 16 | navigationItem.rightBarButtonItem = UIBarButtonItem(customView: infoButton) 17 | } 18 | 19 | @objc func infoTapped() { 20 | let aboutUsViewController = AboutUsViewController() 21 | let navigationController = UINavigationController(rootViewController: aboutUsViewController) 22 | 23 | navigationController.topViewController?.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target:navigationController, action: #selector(dismissAnimated)) 24 | present(navigationController, animated: true, completion: nil) 25 | } 26 | 27 | @objc func dismissAnimated() { 28 | dismiss(animated: true, completion: nil) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Gallery.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | Gallery2.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Gallery.xcdatamodeld/Gallery.xcdatamodel/contents: -------------------------------------------------------------------------------- 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 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Gallery.xcdatamodeld/Gallery2.xcdatamodel/contents: -------------------------------------------------------------------------------- 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 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Gallery.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 64C9EF64CF164F78801B9A5C /* Pods_Gallery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F2B08311637F24B087374EB /* Pods_Gallery.framework */; }; 11 | A127574D1A81091400A0CA79 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A127574C1A81091400A0CA79 /* AppDelegate.swift */; }; 12 | A12757521A81091400A0CA79 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A12757501A81091400A0CA79 /* Main.storyboard */; }; 13 | A12757541A81091400A0CA79 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A12757531A81091400A0CA79 /* Images.xcassets */; }; 14 | A12757571A81091400A0CA79 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = A12757551A81091400A0CA79 /* LaunchScreen.xib */; }; 15 | A13B63DE1A822E8F00EF5EED /* Gallery.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = A13B63DC1A822E8F00EF5EED /* Gallery.xcdatamodeld */; }; 16 | A13B63E01A822EB700EF5EED /* Asset.swift in Sources */ = {isa = PBXBuildFile; fileRef = A13B63DF1A822EB700EF5EED /* Asset.swift */; }; 17 | A13B63E21A822EB700EF5EED /* PhotoGallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = A13B63E11A822EB700EF5EED /* PhotoGallery.swift */; }; 18 | A13B63E41A822EB700EF5EED /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A13B63E31A822EB700EF5EED /* Image.swift */; }; 19 | A13B63E61A822EB700EF5EED /* SyncInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A13B63E51A822EB700EF5EED /* SyncInfo.swift */; }; 20 | A13B63E81A822EB700EF5EED /* Author.swift in Sources */ = {isa = PBXBuildFile; fileRef = A13B63E71A822EB700EF5EED /* Author.swift */; }; 21 | A18A5BCA1AA9E8DE00486BF0 /* gallery-app-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = A18A5BC91AA9E8DE00486BF0 /* gallery-app-icon.png */; }; 22 | A1C332351A924553008EC0B7 /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C332341A924553008EC0B7 /* ImageCollectionViewCell.swift */; }; 23 | A1C3323A1A925A3F008EC0B7 /* AnimatedFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C332391A925A3F008EC0B7 /* AnimatedFlowLayout.swift */; }; 24 | A1C3323C1A937096008EC0B7 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C3323B1A937096008EC0B7 /* UIKit.swift */; }; 25 | A1C3323E1A937B59008EC0B7 /* ImagesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C3323D1A937B59008EC0B7 /* ImagesPageViewController.swift */; }; 26 | A1C6E2FD1A9F692D009845B7 /* ImagesByGalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C6E2FC1A9F692D009845B7 /* ImagesByGalleryViewController.swift */; }; 27 | A1CEEE421AA608B3000AA8E6 /* ImageDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1CEEE411AA608B3000AA8E6 /* ImageDetailsViewController.swift */; }; 28 | A1D156DB1A822BC200250215 /* ContentfulDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D156DA1A822BC200250215 /* ContentfulDataManager.swift */; }; 29 | A1E3C56D1AA48E1E0069C998 /* GalleryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E3C56C1AA48E1E0069C998 /* GalleryHeaderView.swift */; }; 30 | EDB2D4D220EE82F2001021D7 /* Pods-Gallery-acknowledgements.markdown in Resources */ = {isa = PBXBuildFile; fileRef = EDB2D4D120EE82F2001021D7 /* Pods-Gallery-acknowledgements.markdown */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXCopyFilesBuildPhase section */ 34 | ED3491231DFADBAE003F14BD /* Embed Frameworks */ = { 35 | isa = PBXCopyFilesBuildPhase; 36 | buildActionMask = 2147483647; 37 | dstPath = ""; 38 | dstSubfolderSpec = 10; 39 | files = ( 40 | ); 41 | name = "Embed Frameworks"; 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXCopyFilesBuildPhase section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 0F2B08311637F24B087374EB /* Pods_Gallery.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Gallery.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 873C47EE5F14DDAE7B2AB283 /* Pods-Gallery.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Gallery.debug.xcconfig"; path = "Target Support Files/Pods-Gallery/Pods-Gallery.debug.xcconfig"; sourceTree = ""; }; 49 | A12757471A81091400A0CA79 /* Gallery.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gallery.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | A127574B1A81091400A0CA79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | A127574C1A81091400A0CA79 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52 | A12757511A81091400A0CA79 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | A12757531A81091400A0CA79 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 54 | A12757561A81091400A0CA79 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 55 | A13B63DD1A822E8F00EF5EED /* Gallery.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Gallery.xcdatamodel; sourceTree = ""; }; 56 | A13B63DF1A822EB700EF5EED /* Asset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Asset.swift; sourceTree = ""; }; 57 | A13B63E11A822EB700EF5EED /* PhotoGallery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoGallery.swift; sourceTree = ""; }; 58 | A13B63E31A822EB700EF5EED /* Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; 59 | A13B63E51A822EB700EF5EED /* SyncInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncInfo.swift; sourceTree = ""; }; 60 | A13B63E71A822EB700EF5EED /* Author.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Author.swift; sourceTree = ""; }; 61 | A13B63E91A824F4100EF5EED /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; 62 | A18A5BC91AA9E8DE00486BF0 /* gallery-app-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gallery-app-icon.png"; sourceTree = ""; }; 63 | A1C332341A924553008EC0B7 /* ImageCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCollectionViewCell.swift; sourceTree = ""; }; 64 | A1C332391A925A3F008EC0B7 /* AnimatedFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatedFlowLayout.swift; sourceTree = ""; }; 65 | A1C3323B1A937096008EC0B7 /* UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = ""; }; 66 | A1C3323D1A937B59008EC0B7 /* ImagesPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesPageViewController.swift; sourceTree = ""; }; 67 | A1C6E2FC1A9F692D009845B7 /* ImagesByGalleryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesByGalleryViewController.swift; sourceTree = ""; }; 68 | A1CEEE411AA608B3000AA8E6 /* ImageDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDetailsViewController.swift; sourceTree = ""; }; 69 | A1D156DA1A822BC200250215 /* ContentfulDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentfulDataManager.swift; sourceTree = ""; }; 70 | A1E3C56C1AA48E1E0069C998 /* GalleryHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryHeaderView.swift; sourceTree = ""; }; 71 | DDBBF01A4ABC44347D965A63 /* Pods-Gallery.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Gallery.release.xcconfig"; path = "Target Support Files/Pods-Gallery/Pods-Gallery.release.xcconfig"; sourceTree = ""; }; 72 | ED4D63441EEFDE3D0012E0A7 /* Gallery2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Gallery2.xcdatamodel; sourceTree = ""; }; 73 | EDB2D4D120EE82F2001021D7 /* Pods-Gallery-acknowledgements.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = "Pods-Gallery-acknowledgements.markdown"; path = "Pods/Target Support Files/Pods-Gallery/Pods-Gallery-acknowledgements.markdown"; sourceTree = ""; }; 74 | /* End PBXFileReference section */ 75 | 76 | /* Begin PBXFrameworksBuildPhase section */ 77 | A12757441A81091400A0CA79 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | 64C9EF64CF164F78801B9A5C /* Pods_Gallery.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 18D8030A3D9B9B1D0790ADBD /* Frameworks */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 0F2B08311637F24B087374EB /* Pods_Gallery.framework */, 92 | ); 93 | name = Frameworks; 94 | sourceTree = ""; 95 | }; 96 | A127573E1A81091400A0CA79 = { 97 | isa = PBXGroup; 98 | children = ( 99 | ED2452E920EE82A60077FE13 /* Podspec metadata */, 100 | A12757491A81091400A0CA79 /* Code */, 101 | A12757481A81091400A0CA79 /* Products */, 102 | A127576C1A81096800A0CA79 /* Resources */, 103 | A127574A1A81091400A0CA79 /* Supporting Files */, 104 | DA7B6EC94B6A198977EF9C8A /* Pods */, 105 | 18D8030A3D9B9B1D0790ADBD /* Frameworks */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | A12757481A81091400A0CA79 /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | A12757471A81091400A0CA79 /* Gallery.app */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | A12757491A81091400A0CA79 /* Code */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | A1D156DC1A822BF000250215 /* Model */, 121 | A1C332391A925A3F008EC0B7 /* AnimatedFlowLayout.swift */, 122 | A127574C1A81091400A0CA79 /* AppDelegate.swift */, 123 | A1D156DA1A822BC200250215 /* ContentfulDataManager.swift */, 124 | A1E3C56C1AA48E1E0069C998 /* GalleryHeaderView.swift */, 125 | A1C332341A924553008EC0B7 /* ImageCollectionViewCell.swift */, 126 | A1CEEE411AA608B3000AA8E6 /* ImageDetailsViewController.swift */, 127 | A1C6E2FC1A9F692D009845B7 /* ImagesByGalleryViewController.swift */, 128 | A1C3323D1A937B59008EC0B7 /* ImagesPageViewController.swift */, 129 | A1C3323B1A937096008EC0B7 /* UIKit.swift */, 130 | ); 131 | path = Code; 132 | sourceTree = ""; 133 | }; 134 | A127574A1A81091400A0CA79 /* Supporting Files */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | A13B63E91A824F4100EF5EED /* BridgingHeader.h */, 138 | A127574B1A81091400A0CA79 /* Info.plist */, 139 | ); 140 | path = "Supporting Files"; 141 | sourceTree = ""; 142 | }; 143 | A127576C1A81096800A0CA79 /* Resources */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | A18A5BC91AA9E8DE00486BF0 /* gallery-app-icon.png */, 147 | A12757531A81091400A0CA79 /* Images.xcassets */, 148 | A12757551A81091400A0CA79 /* LaunchScreen.xib */, 149 | A12757501A81091400A0CA79 /* Main.storyboard */, 150 | ); 151 | path = Resources; 152 | sourceTree = ""; 153 | }; 154 | A1D156DC1A822BF000250215 /* Model */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | A13B63DC1A822E8F00EF5EED /* Gallery.xcdatamodeld */, 158 | A13B63DF1A822EB700EF5EED /* Asset.swift */, 159 | A13B63E71A822EB700EF5EED /* Author.swift */, 160 | A13B63E31A822EB700EF5EED /* Image.swift */, 161 | A13B63E11A822EB700EF5EED /* PhotoGallery.swift */, 162 | A13B63E51A822EB700EF5EED /* SyncInfo.swift */, 163 | ); 164 | name = Model; 165 | sourceTree = ""; 166 | }; 167 | DA7B6EC94B6A198977EF9C8A /* Pods */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 873C47EE5F14DDAE7B2AB283 /* Pods-Gallery.debug.xcconfig */, 171 | DDBBF01A4ABC44347D965A63 /* Pods-Gallery.release.xcconfig */, 172 | ); 173 | name = Pods; 174 | path = Pods; 175 | sourceTree = ""; 176 | }; 177 | ED2452E920EE82A60077FE13 /* Podspec metadata */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | EDB2D4D120EE82F2001021D7 /* Pods-Gallery-acknowledgements.markdown */, 181 | ); 182 | name = "Podspec metadata"; 183 | sourceTree = ""; 184 | }; 185 | /* End PBXGroup section */ 186 | 187 | /* Begin PBXNativeTarget section */ 188 | A12757461A81091400A0CA79 /* Gallery */ = { 189 | isa = PBXNativeTarget; 190 | buildConfigurationList = A12757661A81091400A0CA79 /* Build configuration list for PBXNativeTarget "Gallery" */; 191 | buildPhases = ( 192 | 1FFBCF2E51E734E3FD0DA36C /* [CP] Check Pods Manifest.lock */, 193 | A12757431A81091400A0CA79 /* Sources */, 194 | A12757441A81091400A0CA79 /* Frameworks */, 195 | A12757451A81091400A0CA79 /* Resources */, 196 | ED3491231DFADBAE003F14BD /* Embed Frameworks */, 197 | BF0406E70BC8575527028B8D /* [CP] Embed Pods Frameworks */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | ); 203 | name = Gallery; 204 | productName = Gallery; 205 | productReference = A12757471A81091400A0CA79 /* Gallery.app */; 206 | productType = "com.apple.product-type.application"; 207 | }; 208 | /* End PBXNativeTarget section */ 209 | 210 | /* Begin PBXProject section */ 211 | A127573F1A81091400A0CA79 /* Project object */ = { 212 | isa = PBXProject; 213 | attributes = { 214 | LastSwiftMigration = 0710; 215 | LastSwiftUpdateCheck = 0710; 216 | LastUpgradeCheck = 0940; 217 | ORGANIZATIONNAME = "Contentful GmbH"; 218 | TargetAttributes = { 219 | A12757461A81091400A0CA79 = { 220 | CreatedOnToolsVersion = 6.1.1; 221 | DevelopmentTeam = RWJ5E97L7R; 222 | LastSwiftMigration = 0820; 223 | }; 224 | }; 225 | }; 226 | buildConfigurationList = A12757421A81091400A0CA79 /* Build configuration list for PBXProject "Gallery" */; 227 | compatibilityVersion = "Xcode 3.2"; 228 | developmentRegion = English; 229 | hasScannedForEncodings = 0; 230 | knownRegions = ( 231 | en, 232 | Base, 233 | ); 234 | mainGroup = A127573E1A81091400A0CA79; 235 | productRefGroup = A12757481A81091400A0CA79 /* Products */; 236 | projectDirPath = ""; 237 | projectRoot = ""; 238 | targets = ( 239 | A12757461A81091400A0CA79 /* Gallery */, 240 | ); 241 | }; 242 | /* End PBXProject section */ 243 | 244 | /* Begin PBXResourcesBuildPhase section */ 245 | A12757451A81091400A0CA79 /* Resources */ = { 246 | isa = PBXResourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | A12757521A81091400A0CA79 /* Main.storyboard in Resources */, 250 | A12757571A81091400A0CA79 /* LaunchScreen.xib in Resources */, 251 | EDB2D4D220EE82F2001021D7 /* Pods-Gallery-acknowledgements.markdown in Resources */, 252 | A18A5BCA1AA9E8DE00486BF0 /* gallery-app-icon.png in Resources */, 253 | A12757541A81091400A0CA79 /* Images.xcassets in Resources */, 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | /* End PBXResourcesBuildPhase section */ 258 | 259 | /* Begin PBXShellScriptBuildPhase section */ 260 | 1FFBCF2E51E734E3FD0DA36C /* [CP] Check Pods Manifest.lock */ = { 261 | isa = PBXShellScriptBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | ); 265 | inputFileListPaths = ( 266 | ); 267 | inputPaths = ( 268 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 269 | "${PODS_ROOT}/Manifest.lock", 270 | ); 271 | name = "[CP] Check Pods Manifest.lock"; 272 | outputFileListPaths = ( 273 | ); 274 | outputPaths = ( 275 | "$(DERIVED_FILE_DIR)/Pods-Gallery-checkManifestLockResult.txt", 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | shellPath = /bin/sh; 279 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 280 | showEnvVarsInLog = 0; 281 | }; 282 | BF0406E70BC8575527028B8D /* [CP] Embed Pods Frameworks */ = { 283 | isa = PBXShellScriptBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | inputFileListPaths = ( 288 | ); 289 | inputPaths = ( 290 | "${PODS_ROOT}/Target Support Files/Pods-Gallery/Pods-Gallery-frameworks.sh", 291 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 292 | "${BUILT_PRODUCTS_DIR}/AlamofireImage/AlamofireImage.framework", 293 | "${BUILT_PRODUCTS_DIR}/Contentful/Contentful.framework", 294 | "${BUILT_PRODUCTS_DIR}/ContentfulDialogs/ContentfulDialogs.framework", 295 | "${BUILT_PRODUCTS_DIR}/ContentfulPersistenceSwift/ContentfulPersistence.framework", 296 | "${BUILT_PRODUCTS_DIR}/Keys/Keys.framework", 297 | "${BUILT_PRODUCTS_DIR}/markymark/markymark.framework", 298 | ); 299 | name = "[CP] Embed Pods Frameworks"; 300 | outputFileListPaths = ( 301 | ); 302 | outputPaths = ( 303 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 304 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlamofireImage.framework", 305 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Contentful.framework", 306 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ContentfulDialogs.framework", 307 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ContentfulPersistence.framework", 308 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Keys.framework", 309 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/markymark.framework", 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | shellPath = /bin/sh; 313 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Gallery/Pods-Gallery-frameworks.sh\"\n"; 314 | showEnvVarsInLog = 0; 315 | }; 316 | /* End PBXShellScriptBuildPhase section */ 317 | 318 | /* Begin PBXSourcesBuildPhase section */ 319 | A12757431A81091400A0CA79 /* Sources */ = { 320 | isa = PBXSourcesBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | A1C3323A1A925A3F008EC0B7 /* AnimatedFlowLayout.swift in Sources */, 324 | A1C3323C1A937096008EC0B7 /* UIKit.swift in Sources */, 325 | A1E3C56D1AA48E1E0069C998 /* GalleryHeaderView.swift in Sources */, 326 | A13B63E41A822EB700EF5EED /* Image.swift in Sources */, 327 | A13B63E61A822EB700EF5EED /* SyncInfo.swift in Sources */, 328 | A1C3323E1A937B59008EC0B7 /* ImagesPageViewController.swift in Sources */, 329 | A13B63E81A822EB700EF5EED /* Author.swift in Sources */, 330 | A1C6E2FD1A9F692D009845B7 /* ImagesByGalleryViewController.swift in Sources */, 331 | A1D156DB1A822BC200250215 /* ContentfulDataManager.swift in Sources */, 332 | A13B63E01A822EB700EF5EED /* Asset.swift in Sources */, 333 | A127574D1A81091400A0CA79 /* AppDelegate.swift in Sources */, 334 | A13B63E21A822EB700EF5EED /* PhotoGallery.swift in Sources */, 335 | A13B63DE1A822E8F00EF5EED /* Gallery.xcdatamodeld in Sources */, 336 | A1C332351A924553008EC0B7 /* ImageCollectionViewCell.swift in Sources */, 337 | A1CEEE421AA608B3000AA8E6 /* ImageDetailsViewController.swift in Sources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | /* End PBXSourcesBuildPhase section */ 342 | 343 | /* Begin PBXVariantGroup section */ 344 | A12757501A81091400A0CA79 /* Main.storyboard */ = { 345 | isa = PBXVariantGroup; 346 | children = ( 347 | A12757511A81091400A0CA79 /* Base */, 348 | ); 349 | name = Main.storyboard; 350 | path = .; 351 | sourceTree = ""; 352 | }; 353 | A12757551A81091400A0CA79 /* LaunchScreen.xib */ = { 354 | isa = PBXVariantGroup; 355 | children = ( 356 | A12757561A81091400A0CA79 /* Base */, 357 | ); 358 | name = LaunchScreen.xib; 359 | path = .; 360 | sourceTree = ""; 361 | }; 362 | /* End PBXVariantGroup section */ 363 | 364 | /* Begin XCBuildConfiguration section */ 365 | A12757641A81091400A0CA79 /* Debug */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ALWAYS_SEARCH_USER_PATHS = NO; 369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 370 | CLANG_CXX_LIBRARY = "libc++"; 371 | CLANG_ENABLE_MODULES = YES; 372 | CLANG_ENABLE_OBJC_ARC = YES; 373 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 374 | CLANG_WARN_BOOL_CONVERSION = YES; 375 | CLANG_WARN_COMMA = YES; 376 | CLANG_WARN_CONSTANT_CONVERSION = YES; 377 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 378 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 379 | CLANG_WARN_EMPTY_BODY = YES; 380 | CLANG_WARN_ENUM_CONVERSION = YES; 381 | CLANG_WARN_INFINITE_RECURSION = YES; 382 | CLANG_WARN_INT_CONVERSION = YES; 383 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 385 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 387 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 388 | CLANG_WARN_STRICT_PROTOTYPES = YES; 389 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 390 | CLANG_WARN_UNREACHABLE_CODE = YES; 391 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 392 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 393 | COPY_PHASE_STRIP = NO; 394 | ENABLE_STRICT_OBJC_MSGSEND = YES; 395 | ENABLE_TESTABILITY = YES; 396 | GCC_C_LANGUAGE_STANDARD = gnu99; 397 | GCC_DYNAMIC_NO_PIC = NO; 398 | GCC_NO_COMMON_BLOCKS = YES; 399 | GCC_OPTIMIZATION_LEVEL = 0; 400 | GCC_PREPROCESSOR_DEFINITIONS = ( 401 | "DEBUG=1", 402 | "$(inherited)", 403 | ); 404 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 405 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 406 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 407 | GCC_WARN_UNDECLARED_SELECTOR = YES; 408 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 409 | GCC_WARN_UNUSED_FUNCTION = YES; 410 | GCC_WARN_UNUSED_VARIABLE = YES; 411 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 412 | MTL_ENABLE_DEBUG_INFO = YES; 413 | ONLY_ACTIVE_ARCH = YES; 414 | SDKROOT = iphoneos; 415 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 416 | SWIFT_VERSION = 4.2; 417 | USER_HEADER_SEARCH_PATHS = "Pods/**"; 418 | }; 419 | name = Debug; 420 | }; 421 | A12757651A81091400A0CA79 /* Release */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | ALWAYS_SEARCH_USER_PATHS = NO; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_ENABLE_OBJC_ARC = YES; 429 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 430 | CLANG_WARN_BOOL_CONVERSION = YES; 431 | CLANG_WARN_COMMA = YES; 432 | CLANG_WARN_CONSTANT_CONVERSION = YES; 433 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 434 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 435 | CLANG_WARN_EMPTY_BODY = YES; 436 | CLANG_WARN_ENUM_CONVERSION = YES; 437 | CLANG_WARN_INFINITE_RECURSION = YES; 438 | CLANG_WARN_INT_CONVERSION = YES; 439 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 440 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 441 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 442 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 443 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 444 | CLANG_WARN_STRICT_PROTOTYPES = YES; 445 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 446 | CLANG_WARN_UNREACHABLE_CODE = YES; 447 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 448 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 449 | COPY_PHASE_STRIP = YES; 450 | ENABLE_NS_ASSERTIONS = NO; 451 | ENABLE_STRICT_OBJC_MSGSEND = YES; 452 | GCC_C_LANGUAGE_STANDARD = gnu99; 453 | GCC_NO_COMMON_BLOCKS = YES; 454 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 455 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 456 | GCC_WARN_UNDECLARED_SELECTOR = YES; 457 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 458 | GCC_WARN_UNUSED_FUNCTION = YES; 459 | GCC_WARN_UNUSED_VARIABLE = YES; 460 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 461 | MTL_ENABLE_DEBUG_INFO = NO; 462 | SDKROOT = iphoneos; 463 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 464 | SWIFT_VERSION = 4.2; 465 | USER_HEADER_SEARCH_PATHS = "Pods/**"; 466 | VALIDATE_PRODUCT = YES; 467 | }; 468 | name = Release; 469 | }; 470 | A12757671A81091400A0CA79 /* Debug */ = { 471 | isa = XCBuildConfiguration; 472 | baseConfigurationReference = 873C47EE5F14DDAE7B2AB283 /* Pods-Gallery.debug.xcconfig */; 473 | buildSettings = { 474 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 475 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 476 | DEVELOPMENT_TEAM = RWJ5E97L7R; 477 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 478 | INFOPLIST_FILE = "Supporting Files/Info.plist"; 479 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 480 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 481 | PRODUCT_BUNDLE_IDENTIFIER = "com.contentful.$(PRODUCT_NAME:rfc1034identifier)"; 482 | PRODUCT_NAME = "$(TARGET_NAME)"; 483 | SWIFT_OBJC_BRIDGING_HEADER = "Supporting Files/BridgingHeader.h"; 484 | SWIFT_VERSION = 4.2; 485 | }; 486 | name = Debug; 487 | }; 488 | A12757681A81091400A0CA79 /* Release */ = { 489 | isa = XCBuildConfiguration; 490 | baseConfigurationReference = DDBBF01A4ABC44347D965A63 /* Pods-Gallery.release.xcconfig */; 491 | buildSettings = { 492 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 493 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 494 | DEVELOPMENT_TEAM = RWJ5E97L7R; 495 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 496 | INFOPLIST_FILE = "Supporting Files/Info.plist"; 497 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 498 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 499 | PRODUCT_BUNDLE_IDENTIFIER = "com.contentful.$(PRODUCT_NAME:rfc1034identifier)"; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | SWIFT_OBJC_BRIDGING_HEADER = "Supporting Files/BridgingHeader.h"; 502 | SWIFT_VERSION = 4.2; 503 | }; 504 | name = Release; 505 | }; 506 | /* End XCBuildConfiguration section */ 507 | 508 | /* Begin XCConfigurationList section */ 509 | A12757421A81091400A0CA79 /* Build configuration list for PBXProject "Gallery" */ = { 510 | isa = XCConfigurationList; 511 | buildConfigurations = ( 512 | A12757641A81091400A0CA79 /* Debug */, 513 | A12757651A81091400A0CA79 /* Release */, 514 | ); 515 | defaultConfigurationIsVisible = 0; 516 | defaultConfigurationName = Release; 517 | }; 518 | A12757661A81091400A0CA79 /* Build configuration list for PBXNativeTarget "Gallery" */ = { 519 | isa = XCConfigurationList; 520 | buildConfigurations = ( 521 | A12757671A81091400A0CA79 /* Debug */, 522 | A12757681A81091400A0CA79 /* Release */, 523 | ); 524 | defaultConfigurationIsVisible = 0; 525 | defaultConfigurationName = Release; 526 | }; 527 | /* End XCConfigurationList section */ 528 | 529 | /* Begin XCVersionGroup section */ 530 | A13B63DC1A822E8F00EF5EED /* Gallery.xcdatamodeld */ = { 531 | isa = XCVersionGroup; 532 | children = ( 533 | ED4D63441EEFDE3D0012E0A7 /* Gallery2.xcdatamodel */, 534 | A13B63DD1A822E8F00EF5EED /* Gallery.xcdatamodel */, 535 | ); 536 | currentVersion = ED4D63441EEFDE3D0012E0A7 /* Gallery2.xcdatamodel */; 537 | name = Gallery.xcdatamodeld; 538 | path = ../Gallery.xcdatamodeld; 539 | sourceTree = ""; 540 | versionGroupType = wrapper.xcdatamodel; 541 | }; 542 | /* End XCVersionGroup section */ 543 | }; 544 | rootObject = A127573F1A81091400A0CA79 /* Project object */; 545 | } 546 | -------------------------------------------------------------------------------- /Gallery.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Gallery.xcodeproj/xcshareddata/xcschemes/Gallery.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Gallery.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 1.6.0.beta.2' 4 | gem 'cocoapods-keys' 5 | gem 'cocoapods-deintegrate' 6 | gem 'cocoapods-clean' 7 | gem 'sbconstants' 8 | gem 'xcpretty' 9 | gem 'contentful_bootstrap', '~> 3.0.0' 10 | 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | RubyInline (3.12.4) 6 | ZenTest (~> 4.3) 7 | ZenTest (4.11.1) 8 | activesupport (4.2.10) 9 | i18n (~> 0.7) 10 | minitest (~> 5.1) 11 | thread_safe (~> 0.3, >= 0.3.4) 12 | tzinfo (~> 1.1) 13 | addressable (2.5.2) 14 | public_suffix (>= 2.0.2, < 4.0) 15 | atomos (0.1.3) 16 | claide (1.0.2) 17 | cocoapods (1.6.0.beta.2) 18 | activesupport (>= 4.0.2, < 5) 19 | claide (>= 1.0.2, < 2.0) 20 | cocoapods-core (= 1.6.0.beta.2) 21 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 22 | cocoapods-downloader (>= 1.2.2, < 2.0) 23 | cocoapods-plugins (>= 1.0.0, < 2.0) 24 | cocoapods-search (>= 1.0.0, < 2.0) 25 | cocoapods-stats (>= 1.0.0, < 2.0) 26 | cocoapods-trunk (>= 1.3.1, < 2.0) 27 | cocoapods-try (>= 1.1.0, < 2.0) 28 | colored2 (~> 3.1) 29 | escape (~> 0.0.4) 30 | fourflusher (~> 2.0.1) 31 | gh_inspector (~> 1.0) 32 | molinillo (~> 0.6.6) 33 | nap (~> 1.0) 34 | ruby-macho (~> 1.3, >= 1.3.1) 35 | xcodeproj (>= 1.7.0, < 2.0) 36 | cocoapods-clean (0.0.1) 37 | cocoapods-core (1.6.0.beta.2) 38 | activesupport (>= 4.0.2, < 6) 39 | fuzzy_match (~> 2.0.4) 40 | nap (~> 1.0) 41 | cocoapods-deintegrate (1.0.2) 42 | cocoapods-downloader (1.2.2) 43 | cocoapods-keys (2.0.6) 44 | dotenv 45 | osx_keychain 46 | cocoapods-plugins (1.0.0) 47 | nap 48 | cocoapods-search (1.0.0) 49 | cocoapods-stats (1.0.0) 50 | cocoapods-trunk (1.3.1) 51 | nap (>= 0.8, < 2.0) 52 | netrc (~> 0.11) 53 | cocoapods-try (1.1.0) 54 | colored2 (3.1.2) 55 | concurrent-ruby (1.1.3) 56 | contentful (0.12.0) 57 | http (~> 1.0) 58 | json (~> 1.8) 59 | multi_json (~> 1) 60 | contentful-management (2.6.0) 61 | http (> 1.0, < 3.0) 62 | json (~> 1.8) 63 | multi_json (~> 1) 64 | contentful_bootstrap (3.0.0) 65 | contentful (~> 0.7) 66 | contentful-management 67 | inifile 68 | launchy 69 | domain_name (0.5.20180417) 70 | unf (>= 0.0.5, < 1.0.0) 71 | dotenv (2.5.0) 72 | escape (0.0.4) 73 | fourflusher (2.0.1) 74 | fuzzy_match (2.0.4) 75 | gh_inspector (1.1.3) 76 | http (1.0.4) 77 | addressable (~> 2.3) 78 | http-cookie (~> 1.0) 79 | http-form_data (~> 1.0.1) 80 | http_parser.rb (~> 0.6.0) 81 | http-cookie (1.0.3) 82 | domain_name (~> 0.5) 83 | http-form_data (1.0.3) 84 | http_parser.rb (0.6.0) 85 | i18n (0.9.5) 86 | concurrent-ruby (~> 1.0) 87 | inifile (3.0.0) 88 | json (1.8.6) 89 | launchy (2.4.3) 90 | addressable (~> 2.3) 91 | minitest (5.11.3) 92 | molinillo (0.6.6) 93 | multi_json (1.13.1) 94 | nanaimo (0.2.6) 95 | nap (1.1.0) 96 | netrc (0.11.0) 97 | osx_keychain (1.0.2) 98 | RubyInline (~> 3) 99 | public_suffix (3.0.3) 100 | rouge (2.0.7) 101 | ruby-macho (1.3.1) 102 | sbconstants (1.2.0) 103 | thread_safe (0.3.6) 104 | tzinfo (1.2.5) 105 | thread_safe (~> 0.1) 106 | unf (0.1.4) 107 | unf_ext 108 | unf_ext (0.0.7.5) 109 | xcodeproj (1.7.0) 110 | CFPropertyList (>= 2.3.3, < 4.0) 111 | atomos (~> 0.1.3) 112 | claide (>= 1.0.2, < 2.0) 113 | colored2 (~> 3.1) 114 | nanaimo (~> 0.2.6) 115 | xcpretty (0.3.0) 116 | rouge (~> 2.0.7) 117 | 118 | PLATFORMS 119 | ruby 120 | 121 | DEPENDENCIES 122 | cocoapods (~> 1.6.0.beta.2) 123 | cocoapods-clean 124 | cocoapods-deintegrate 125 | cocoapods-keys 126 | contentful_bootstrap (~> 3.0.0) 127 | sbconstants 128 | xcpretty 129 | 130 | BUNDLED WITH 131 | 1.17.1 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Contentful GmbH - https://www.contentful.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # read value from Contentful configuration 2 | get_config = $(shell grep -A 2 $1 ~/.contentfulrc|grep $2|cut -d' ' -f3) 3 | 4 | .PHONY: all bootstrap setup storyboard_ids 5 | 6 | SPACE_NAME=my_gallery 7 | 8 | all: 9 | xcodebuild -workspace 'Gallery.xcworkspace' -scheme 'Gallery'|xcpretty 10 | 11 | bootstrap: 12 | bundle install 13 | bundle exec contentful_bootstrap create_space $(SPACE_NAME) -j Templates/gallery.json 14 | 15 | clean: 16 | rm -rf $(HOME)/Library/Developer/Xcode/DerivedData/* 17 | 18 | clean_simulators: kill_simulator 19 | xcrun simctl erase all 20 | 21 | kill_simulator: 22 | killall "Simulator" || true 23 | 24 | setup: bootstrap 25 | pod keys set GallerySpaceId $(call get_config,$(SPACE_NAME),SPACE_ID) 26 | pod keys set GalleryAccessToken $(call get_config,$(SPACE_NAME),CONTENTFUL_DELIVERY_ACCESS_TOKEN) 27 | pod install #--no-repo-update 28 | 29 | storyboard_ids: 30 | bundle exec sbconstants --swift Code/StoryboardIdentifiers.swift 31 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | =begin 4 | Use cocoapod-keys to load application keys (variables). 5 | See https://github.com/orta/cocoapods-keys. 6 | =end 7 | 8 | plugin 'cocoapods-keys', { 9 | :project => 'Gallery', 10 | :keys => [ 11 | 'GallerySpaceId', 12 | 'GalleryAccessToken' 13 | ]} 14 | 15 | source 'https://github.com/CocoaPods/Specs' 16 | #source 'https://github.com/contentful/CocoaPodsSpecs' 17 | 18 | use_frameworks! 19 | platform :ios, '10.0' 20 | inhibit_all_warnings! 21 | 22 | target 'Gallery' do 23 | 24 | pod 'ContentfulPersistenceSwift', '~> 0.13.0' 25 | pod 'AlamofireImage', '~> 3' 26 | pod 'markymark' 27 | pod 'ContentfulDialogs', :git => 'https://github.com/contentful/contentful-ios-dialogs.git' 28 | end 29 | 30 | 31 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.7.3) 3 | - AlamofireImage (3.4.1): 4 | - Alamofire (~> 4.7) 5 | - Contentful (4.1.3): 6 | - Contentful/ImageOptions (= 4.1.3) 7 | - Contentful/ImageOptions (4.1.3) 8 | - ContentfulDialogs (2.0.0): 9 | - markymark 10 | - ContentfulPersistenceSwift (0.13.0): 11 | - Contentful (~> 4.1.0) 12 | - Keys (1.0.1) 13 | - markymark (8.0.0) 14 | 15 | DEPENDENCIES: 16 | - AlamofireImage (~> 3) 17 | - ContentfulDialogs (from `https://github.com/contentful/contentful-ios-dialogs.git`) 18 | - ContentfulPersistenceSwift (~> 0.13.0) 19 | - Keys (from `Pods/CocoaPodsKeys`) 20 | - markymark 21 | 22 | SPEC REPOS: 23 | https://github.com/cocoapods/specs.git: 24 | - Alamofire 25 | - AlamofireImage 26 | - Contentful 27 | - ContentfulPersistenceSwift 28 | - markymark 29 | 30 | EXTERNAL SOURCES: 31 | ContentfulDialogs: 32 | :git: https://github.com/contentful/contentful-ios-dialogs.git 33 | Keys: 34 | :path: Pods/CocoaPodsKeys 35 | 36 | CHECKOUT OPTIONS: 37 | ContentfulDialogs: 38 | :commit: 7d47122013b304989bcda99b2ad179f9704f3d51 39 | :git: https://github.com/contentful/contentful-ios-dialogs.git 40 | 41 | SPEC CHECKSUMS: 42 | Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568 43 | AlamofireImage: 78d67ccbb763d87ba44b21583d2153500a195630 44 | Contentful: adb1e999e7c223615d84c00ad9556941b40df563 45 | ContentfulDialogs: 677a8406352443f0671205f6f2b18624b05115af 46 | ContentfulPersistenceSwift: 34dcc378e015a732b2595e9f6a4e5dcd47e4fe21 47 | Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9 48 | markymark: 169ed4b8ce17cfa93ffb667884d2dd896f1a6d37 49 | 50 | PODFILE CHECKSUM: 671ec04c8c6fa9054705181d90daabe7dfac2538 51 | 52 | COCOAPODS: 1.6.0.beta.2 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gallery 2 | 3 | [![](https://assets.contentful.com/7clmb9ye18e7/9prTbbpxsWgQ0K6qAEyY6/cd3d2a09a6110ce61d06cea59d4cf62a/download-store.svg)](https://itunes.apple.com/app/id975142754) 4 | 5 | This is an iOS application example for the [Contentful][1] gallery space template. 6 | 7 | [Contentful][1] is a content management platform for web applications, mobile apps and connected devices. It allows you to create, edit & manage content in the cloud and publish it anywhere via powerful API. Contentful offers tools for managing editorial teams and enabling cooperation between organizations. 8 | 9 | ## Usage 10 | 11 | - Create a space with the "Gallery" space template on [Contentful][1] 12 | - Clone this repo and setup [CocoaPods][2] for it: 13 | 14 | 15 | First install (cocoapods-keys)[https://github.com/orta/cocoapods-keys] if is not already installed. (NOTE: If you are not using the default system Ruby, you must make sure that you `chown` your `~/.gems` directory, in order to install gems without the `sudo` keyword. 16 | 17 | ``` 18 | pod plugins install cocoapods-keys 19 | ``` 20 | 21 | ``` 22 | $ make setup 23 | ``` 24 | 25 | - Use cocoapods-keys to setup your space credentials for it: 26 | 27 | ``` 28 | $ bundle exec pod keys set GallerySpaceId $YOUR_SPACE_ID 29 | $ bundle exec pod keys set GalleryAccessToken $YOUR_ACCESS_TOKEN 30 | ``` 31 | 32 | - Now you're ready to use it! 33 | 34 | ## Customizing 35 | 36 | - You can easily drop the [Contentful][1] related branding by removing 'ContentfulDialogs' and 'ContentfulStyle' from the Podfile. You will need to remove the `addInfoButton()` function from the `UIViewController` extension, as well as replace usages of our fonts and colors. In addition to that, update the bridging header as there are now some header files missing. 37 | 38 | - The app has support for the 'contentful-gallery://' custom URL scheme, you should also remove that if you are using the template for your own application. 39 | 40 | ## License 41 | 42 | Copyright (c) 2015 Contentful GmbH. See LICENSE for further details. 43 | 44 | 45 | [1]: https://www.contentful.com 46 | [2]: http://cocoapods.org 47 | -------------------------------------------------------------------------------- /Resources/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Resources/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 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Resources/Images.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 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-Small@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-Small@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-Spotlight-40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-Spotlight-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-60@3x.png", 47 | "scale" : "3x" 48 | } 49 | ], 50 | "info" : { 51 | "version" : 1, 52 | "author" : "xcode" 53 | } 54 | } -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-ios/5a9830f2d4ecdf35c9d706b8bf3d71b6f76f40ef/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-ios/5a9830f2d4ecdf35c9d706b8bf3d71b6f76f40ef/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-ios/5a9830f2d4ecdf35c9d706b8bf3d71b6f76f40ef/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-ios/5a9830f2d4ecdf35c9d706b8bf3d71b6f76f40ef/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-ios/5a9830f2d4ecdf35c9d706b8bf3d71b6f76f40ef/Resources/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-ios/5a9830f2d4ecdf35c9d706b8bf3d71b6f76f40ef/Resources/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png -------------------------------------------------------------------------------- /Resources/gallery-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-ios/5a9830f2d4ecdf35c9d706b8bf3d71b6f76f40ef/Resources/gallery-app-icon.png -------------------------------------------------------------------------------- /Supporting Files/BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // BridgingHeader.h 3 | // Gallery 4 | // 5 | // Created by Boris Bügling on 04/02/15. 6 | // Copyright (c) 2015 Contentful GmbH. All rights reserved. 7 | // 8 | 9 | #import 10 | -------------------------------------------------------------------------------- /Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Photo Gallery 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Photo_Gallery 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.1 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Viewer 28 | CFBundleURLIconFile 29 | 512_black.png 30 | CFBundleURLName 31 | com.contentful.gallery 32 | CFBundleURLSchemes 33 | 34 | contentful-gallery 35 | 36 | 37 | 38 | CFBundleVersion 39 | 1 40 | LSRequiresIPhoneOS 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UIViewControllerBasedStatusBarAppearance 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Templates/gallery.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "1xYw5JsIecuGE68mmGMg20", 6 | "name": "Image", 7 | "displayField": "title", 8 | "fields": [ 9 | { 10 | "id": "title", 11 | "name": "Title", 12 | "type": "Symbol" 13 | }, 14 | { 15 | "id": "photo", 16 | "name": "Photo", 17 | "type": "Link", 18 | "linkType": "Asset" 19 | }, 20 | { 21 | "id": "imageCaption", 22 | "name": "Image caption", 23 | "type": "Text" 24 | }, 25 | { 26 | "id": "imageCredits", 27 | "name": "Image credits", 28 | "type": "Text" 29 | } 30 | ] 31 | }, 32 | { 33 | "id": "38nK0gXXIccQ2IEosyAg6C", 34 | "name": "Author", 35 | "displayField": "name", 36 | "fields": [ 37 | { 38 | "id": "name", 39 | "name": "Name", 40 | "type": "Symbol" 41 | }, 42 | { 43 | "id": "twitterHandle", 44 | "name": "Twitter handle", 45 | "type": "Symbol" 46 | }, 47 | { 48 | "id": "profilePhoto", 49 | "name": "Profile photo", 50 | "type": "Link", 51 | "linkType": "Asset" 52 | }, 53 | { 54 | "id": "biography", 55 | "name": "Biography", 56 | "type": "Text" 57 | }, 58 | { 59 | "id": "createdEntries", 60 | "name": "Created entries", 61 | "type": "Array", 62 | "items": { 63 | "type": "Link", 64 | "linkType": "Entry" 65 | } 66 | } 67 | ] 68 | }, 69 | { 70 | "id": "7leLzv8hW06amGmke86y8G", 71 | "name": "Photo Gallery", 72 | "displayField": "title", 73 | "fields": [ 74 | { 75 | "id": "title", 76 | "name": "Title", 77 | "type": "Text" 78 | }, 79 | { 80 | "id": "slug", 81 | "name": "Slug", 82 | "type": "Symbol" 83 | }, 84 | { 85 | "id": "author", 86 | "name": "Author", 87 | "type": "Link", 88 | "linkType": "Entry" 89 | }, 90 | { 91 | "id": "coverImage", 92 | "name": "Cover Image", 93 | "type": "Link", 94 | "linkType": "Asset" 95 | }, 96 | { 97 | "id": "description", 98 | "name": "Description", 99 | "type": "Text" 100 | }, 101 | { 102 | "id": "images", 103 | "name": "Images", 104 | "type": "Array", 105 | "items": { 106 | "type": "Link", 107 | "linkType": "Entry" 108 | } 109 | }, 110 | { 111 | "id": "tags", 112 | "name": "Tags", 113 | "type": "Array", 114 | "items": { 115 | "type": "Symbol" 116 | } 117 | }, 118 | { 119 | "id": "date", 120 | "name": "Date", 121 | "type": "Date" 122 | } 123 | ] 124 | } 125 | ], 126 | "assets": [ 127 | { 128 | "id": "149UafeyfcGSoOmESmmYSA", 129 | "title": "Janine", 130 | "file": { 131 | "filename": "FI01", 132 | "url": "https://images.contentful.com/fbr4i5aajb0w/149UafeyfcGSoOmESmmYSA/3fa4e999df6e6bc803159316c04efd76/FI01.png" 133 | } 134 | }, 135 | { 136 | "id": "1zY02V76cUsM6yWycWqyk6", 137 | "title": "Air Baloon", 138 | "file": { 139 | "filename": "bXoAlw8gT66vBo1wcFoO_IMG_9181", 140 | "url": "https://images.contentful.com/fbr4i5aajb0w/1zY02V76cUsM6yWycWqyk6/ef2a73cd2ff8ec9bf38dca716361bd98/bXoAlw8gT66vBo1wcFoO_IMG_9181.jpg" 141 | } 142 | }, 143 | { 144 | "id": "26PCF5jzziyeiessOOY0o0", 145 | "title": "The Flower", 146 | "file": { 147 | "filename": "2MwGKhLETRSQoHP9UWE4_IMG_1348-3", 148 | "url": "https://images.contentful.com/fbr4i5aajb0w/26PCF5jzziyeiessOOY0o0/eb7c2a18dfb6dfb2d6a591306e15a9b5/2MwGKhLETRSQoHP9UWE4_IMG_1348-3.jpg" 149 | } 150 | }, 151 | { 152 | "id": "2czUZ3VWIc68cyOoyGY4MW", 153 | "title": "Golden Gate Bridge", 154 | "file": { 155 | "filename": "lUUnN7VGSoWZ3noefeH7_Baker Beach-12", 156 | "url": "https://images.contentful.com/fbr4i5aajb0w/2czUZ3VWIc68cyOoyGY4MW/6a000dad7ec5564c9252b750521e8c07/lUUnN7VGSoWZ3noefeH7_Baker_Beach-12.jpg" 157 | } 158 | }, 159 | { 160 | "id": "3nCIlsuVKwQysuMcKGuUA4", 161 | "title": "The world on a digital screen", 162 | "file": { 163 | "filename": "tU3ptNgGSP6U2fE67Gvy_SYDNEY-162", 164 | "url": "https://images.contentful.com/fbr4i5aajb0w/3nCIlsuVKwQysuMcKGuUA4/9a8a4ebb33072a4bbdf5adcac6359f8d/tU3ptNgGSP6U2fE67Gvy_SYDNEY-162.jpg" 165 | } 166 | }, 167 | { 168 | "id": "4cDNp7QWHuYUaCEIkWC4wQ", 169 | "title": "tumblr njljopUHgc1tyx950o1 1280", 170 | "file": { 171 | "filename": "tumblr_njljopUHgc1tyx950o1_1280", 172 | "url": "https://images.contentful.com/fbr4i5aajb0w/4cDNp7QWHuYUaCEIkWC4wQ/a047577f04da2483ed4dac39e094a849/tumblr_njljopUHgc1tyx950o1_1280.png" 173 | } 174 | }, 175 | { 176 | "id": "4hbW28y6884swoYKiq6oSs", 177 | "title": "tumblr nblcumX8SZ1tyx950o1 1280", 178 | "file": { 179 | "filename": "tumblr_nblcumX8SZ1tyx950o1_1280", 180 | "url": "https://images.contentful.com/fbr4i5aajb0w/4hbW28y6884swoYKiq6oSs/2e01fe388e132125eb888114170bfbad/tumblr_nblcumX8SZ1tyx950o1_1280.jpg" 181 | } 182 | }, 183 | { 184 | "id": "5DGRkcoj9mAgwkgGyKMuwe", 185 | "title": "Pie in the sky", 186 | "file": { 187 | "filename": "EOZpjI3oSqKPNnF2S4Tp_Untitled", 188 | "url": "https://images.contentful.com/fbr4i5aajb0w/5DGRkcoj9mAgwkgGyKMuwe/8eeec539127f4bc70427ac5cdf2adba5/EOZpjI3oSqKPNnF2S4Tp_Untitled.jpg" 189 | } 190 | }, 191 | { 192 | "id": "6y0psij2o02YIwGScEo4kS", 193 | "title": "Celebration", 194 | "file": { 195 | "filename": "photo-1421986527537-888d998adb74", 196 | "url": "https://images.contentful.com/fbr4i5aajb0w/6y0psij2o02YIwGScEo4kS/1b3f09b8fcedece1d17ea58417b55eb4/photo-1421986527537-888d998adb74.jpeg" 197 | } 198 | }, 199 | { 200 | "id": "geptFDHuzQa8oA2Ywmiew", 201 | "title": "City Street", 202 | "file": { 203 | "filename": "3CoEETpvQYO8x60lnZSA_rue", 204 | "url": "https://images.contentful.com/fbr4i5aajb0w/geptFDHuzQa8oA2Ywmiew/c73149d067f432f5172601990997fa41/3CoEETpvQYO8x60lnZSA_rue.jpg" 205 | } 206 | } 207 | ], 208 | "entries": { 209 | "7leLzv8hW06amGmke86y8G": [ 210 | { 211 | "sys": { 212 | "id": "54O5P32wmcY0wiAY0ewA2e" 213 | }, 214 | "fields": { 215 | "title": "The World Around Me: The Best of Young Photography", 216 | "slug": "the-world-around-me-the-best-of-young-photography", 217 | "author": { 218 | "linkType": "Entry", 219 | "id": "4DyrC6MPp6Ws8UmQEQIGUc" 220 | }, 221 | "coverImage": { 222 | "linkType": "Asset", 223 | "id": "6y0psij2o02YIwGScEo4kS" 224 | }, 225 | "description": "All the images in the gallery taken from “[Unsplash](https://unsplash.com/)” photo project.", 226 | "images": [ 227 | { 228 | "linkType": "Entry", 229 | "id": "5dT4Tgc7gQCuyGSGiakqYC" 230 | }, 231 | { 232 | "linkType": "Entry", 233 | "id": "67rvVwLG3mMsucCsaKgaMi" 234 | }, 235 | { 236 | "linkType": "Entry", 237 | "id": "2MtVnLk3wIiAgOC6OG6qS4" 238 | }, 239 | { 240 | "linkType": "Entry", 241 | "id": "3rzf72XFm8aQU4oMOci8WY" 242 | }, 243 | { 244 | "linkType": "Entry", 245 | "id": "4MA2xHUeLeKaAs2K4Kqiog" 246 | }, 247 | { 248 | "linkType": "Entry", 249 | "id": "3eL4DUKKdyqGIQm2M6SaCe" 250 | }, 251 | { 252 | "linkType": "Entry", 253 | "id": "2YhtjbebgscIwO2keYEa4O" 254 | } 255 | ], 256 | "tags": [ 257 | "city", 258 | "lens", 259 | "bridge", 260 | "bar", 261 | "people", 262 | "perspective", 263 | "macro", 264 | "close up", 265 | "baloon" 266 | ], 267 | "date": "2015-01-30" 268 | } 269 | }, 270 | { 271 | "sys": { 272 | "id": "3ieluksjY4G8uoiu8sGUuo" 273 | }, 274 | "fields": { 275 | "title": "Photos by Danilo", 276 | "slug": "photos-by-danilo", 277 | "author": { 278 | "linkType": "Entry", 279 | "id": "5TGeaqWqjeMoieimSWMIO6" 280 | }, 281 | "coverImage": { 282 | "linkType": "Asset", 283 | "id": "4cDNp7QWHuYUaCEIkWC4wQ" 284 | }, 285 | "images": [ 286 | { 287 | "linkType": "Entry", 288 | "id": "4zWAVrLzwA8MMY8qOacImq" 289 | }, 290 | { 291 | "linkType": "Entry", 292 | "id": "2fbvdUtlByaCW0ceoqC0UM" 293 | } 294 | ] 295 | } 296 | } 297 | ], 298 | "38nK0gXXIccQ2IEosyAg6C": [ 299 | { 300 | "sys": { 301 | "id": "4DyrC6MPp6Ws8UmQEQIGUc" 302 | }, 303 | "fields": { 304 | "name": "Janine McKay", 305 | "twitterHandle": "@contentful", 306 | "profilePhoto": { 307 | "linkType": "Asset", 308 | "id": "149UafeyfcGSoOmESmmYSA" 309 | }, 310 | "biography": "Janine is our communications manager. Coming from the South of Germany via London, she enjoys the newest electronica, Japanese candy and literary discussions about the choices of tragic heroes." 311 | } 312 | }, 313 | { 314 | "sys": { 315 | "id": "5TGeaqWqjeMoieimSWMIO6" 316 | }, 317 | "fields": { 318 | "name": "Danilo Sierra" 319 | } 320 | } 321 | ], 322 | "1xYw5JsIecuGE68mmGMg20": [ 323 | { 324 | "sys": { 325 | "id": "2MtVnLk3wIiAgOC6OG6qS4" 326 | }, 327 | "fields": { 328 | "title": "The Golden Gate Bridge", 329 | "imageCaption": "The bridge, as seen on a cloudy day", 330 | "imageCredits": "Chris Brignola // http://www.avelamedia.com/", 331 | "photo": { 332 | "linkType": "Asset", 333 | "id": "2czUZ3VWIc68cyOoyGY4MW" 334 | } 335 | } 336 | }, 337 | { 338 | "sys": { 339 | "id": "3rzf72XFm8aQU4oMOci8WY" 340 | }, 341 | "fields": { 342 | "title": "Pie in the Sky", 343 | "imageCaption": "Taken at Woods Hole", 344 | "imageCredits": "Dogancan Ozturan // http://dogancan.org/", 345 | "photo": { 346 | "linkType": "Asset", 347 | "id": "5DGRkcoj9mAgwkgGyKMuwe" 348 | } 349 | } 350 | }, 351 | { 352 | "sys": { 353 | "id": "3eL4DUKKdyqGIQm2M6SaCe" 354 | }, 355 | "fields": { 356 | "title": "The Flower", 357 | "imageCaption": "Right in your backyard", 358 | "imageCredits": "John French // http://johnmfrench.tumblr.com/", 359 | "photo": { 360 | "linkType": "Asset", 361 | "id": "26PCF5jzziyeiessOOY0o0" 362 | } 363 | } 364 | }, 365 | { 366 | "sys": { 367 | "id": "2YhtjbebgscIwO2keYEa4O" 368 | }, 369 | "fields": { 370 | "title": "Air Baloon", 371 | "imageCaption": "Up in the air", 372 | "imageCredits": "Austin Ban // http://austinban.com/", 373 | "photo": { 374 | "linkType": "Asset", 375 | "id": "1zY02V76cUsM6yWycWqyk6" 376 | } 377 | } 378 | }, 379 | { 380 | "sys": { 381 | "id": "67rvVwLG3mMsucCsaKgaMi" 382 | }, 383 | "fields": { 384 | "title": "The world on a digital screen", 385 | "imageCaption": "From the personal archive", 386 | "imageCredits": "Jay Wennington // http://jaywennington.tumblr.com/", 387 | "photo": { 388 | "linkType": "Asset", 389 | "id": "3nCIlsuVKwQysuMcKGuUA4" 390 | } 391 | } 392 | }, 393 | { 394 | "sys": { 395 | "id": "4MA2xHUeLeKaAs2K4Kqiog" 396 | }, 397 | "fields": { 398 | "title": "City Street", 399 | "imageCredits": "Oscar Nillson // http://oscr.se/", 400 | "imageCaption": "Taken in San Sebastian", 401 | "photo": { 402 | "linkType": "Asset", 403 | "id": "geptFDHuzQa8oA2Ywmiew" 404 | } 405 | } 406 | }, 407 | { 408 | "sys": { 409 | "id": "2fbvdUtlByaCW0ceoqC0UM" 410 | }, 411 | "fields": { 412 | "photo": { 413 | "linkType": "Asset", 414 | "id": "4cDNp7QWHuYUaCEIkWC4wQ" 415 | }, 416 | "title": "Moscow" 417 | } 418 | }, 419 | { 420 | "sys": { 421 | "id": "4zWAVrLzwA8MMY8qOacImq" 422 | }, 423 | "fields": { 424 | "photo": { 425 | "linkType": "Asset", 426 | "id": "4hbW28y6884swoYKiq6oSs" 427 | }, 428 | "title": "Berlin" 429 | } 430 | }, 431 | { 432 | "sys": { 433 | "id": "5dT4Tgc7gQCuyGSGiakqYC" 434 | }, 435 | "fields": { 436 | "title": "It's a Celebration", 437 | "imageCaption": "From the personal archive", 438 | "imageCredits": "James Tarbotton // http://www.jamestarbotton.com/", 439 | "photo": { 440 | "linkType": "Asset", 441 | "id": "6y0psij2o02YIwGScEo4kS" 442 | } 443 | } 444 | } 445 | ] 446 | } 447 | } --------------------------------------------------------------------------------