Skip to content

Commit

Permalink
Merge pull request nytimes#251 from NYTimes/feature/merge-2.0-develop
Browse files Browse the repository at this point in the history
Merge Develop into Master (v2.0.0)
  • Loading branch information
alexbrashear committed Dec 11, 2017
2 parents f5c4d49 + 594f187 commit 533a1fa
Show file tree
Hide file tree
Showing 37 changed files with 911 additions and 489 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

Changes for users of the library currently on `develop`:

_This space intentionally left blank._
- Expose a data-source-oriented API for PhotosViewController ([#226](https://github.com/NYTimes/NYTPhotoViewer/pull/226))
- A data source no longer has to handle out-of-bounds indexes ([#227](https://github.com/NYTimes/NYTPhotoViewer/pull/227))
- The data source is not retained ([#227](https://github.com/NYTimes/NYTPhotoViewer/pull/227))
- Respect safe areas for iOS 11 support

## [1.2.0](https://github.com/NYTimes/NYTPhotoViewer/releases/tag/1.2.0)

Expand Down
5 changes: 2 additions & 3 deletions Example-Swift/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
final class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
return true
}
}

28 changes: 0 additions & 28 deletions Example-Swift/ExamplePhoto.swift

This file was deleted.

19 changes: 19 additions & 0 deletions Example-Swift/Photo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Photo.swift
// NYTPhotoViewer
//
// Created by Chris Dzombak on 2/2/17.
// Copyright © 2017 NYTimes. All rights reserved.
//

import UIKit

/// A photo may often be represented in a Swift application as a value type.
struct Photo {
// This would usually be a URL, but for this demo we load images from the bundle.
let name: String
let summary: String
let credit: String

let identifier: Int
}
50 changes: 50 additions & 0 deletions Example-Swift/PhotoBox.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// PhotoBox.swift
// NYTPhotoViewer
//
// Created by Chris Dzombak on 2/2/17.
// Copyright © 2017 NYTimes. All rights reserved.
//

import UIKit
import NYTPhotoViewer

/// A box allowing NYTPhotoViewer to consume Swift value types from our codebase.
final class NYTPhotoBox: NSObject, NYTPhoto {

let value: Photo

init(_ photo: Photo) {
value = photo
}

// MARK: NYTPhoto

var image: UIImage?
var imageData: Data?
var placeholderImage: UIImage?

var attributedCaptionTitle: NSAttributedString?

var attributedCaptionSummary: NSAttributedString? {
let attributes = [NSForegroundColorAttributeName: UIColor.white,
NSFontAttributeName: UIFont.preferredFont(forTextStyle: .body)]
return NSAttributedString(string: value.summary, attributes: attributes)
}

var attributedCaptionCredit: NSAttributedString? {
let attributes = [NSForegroundColorAttributeName: UIColor.gray,
NSFontAttributeName: UIFont.preferredFont(forTextStyle: .footnote)]
return NSAttributedString(string: value.credit, attributes: attributes)
}
}

// MARK: NSObject Equality

extension NYTPhotoBox {
@objc
override func isEqual(_ object: Any?) -> Bool {
guard let otherPhoto = object as? NYTPhotoBox else { return false }
return value.identifier == otherPhoto.value.identifier
}
}
53 changes: 53 additions & 0 deletions Example-Swift/PhotoViewerCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// PhotoViewerCoordinator.swift
// NYTPhotoViewer
//
// Created by Chris Dzombak on 2/2/17.
// Copyright © 2017 NYTimes. All rights reserved.
//

import NYTPhotoViewer

/// Coordinates interaction between the application's data layer and the photo viewer component.
final class PhotoViewerCoordinator: NYTPhotoViewerDataSource {
let slideshow: [NYTPhotoBox]
let provider: PhotosProvider

lazy var photoViewer: NYTPhotosViewController = {
return NYTPhotosViewController(dataSource: self)
}()

init(provider: PhotosProvider) {
self.provider = provider
self.slideshow = provider.fetchDemoSlideshow().map { NYTPhotoBox($0) }
self.fetchPhotos()
}

func fetchPhotos() {
for box in slideshow {
provider.fetchPhoto(named: box.value.name, then: { [weak self] (result) in
box.image = result
self?.photoViewer.update(box)
})
}
}

// MARK: NYTPhotoViewerDataSource

@objc
var numberOfPhotos: NSNumber? {
return NSNumber(integerLiteral: slideshow.count)
}

@objc
func index(of photo: NYTPhoto) -> Int {
guard let box = photo as? NYTPhotoBox else { return NSNotFound }
return slideshow.index(where: { $0.value.identifier == box.value.identifier }) ?? NSNotFound
}

@objc
func photo(at index: Int) -> NYTPhoto? {
guard index < slideshow.count else { return nil }
return slideshow[index]
}
}
64 changes: 34 additions & 30 deletions Example-Swift/PhotosProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,43 @@
//

import UIKit
import NYTPhotoViewer

/**
* In Swift 1.2, the following file level constants can be moved inside the class for better encapsulation
*/
let CustomEverythingPhotoIndex = 1, DefaultLoadingSpinnerPhotoIndex = 3, NoReferenceViewPhotoIndex = 4
let PrimaryImageName = "NYTimesBuilding"
let PlaceholderImageName = "NYTimesBuildingPlaceholder"
/// A component of your data layer, which might load photos from the cache or network.
final class PhotosProvider {
typealias Slideshow = [Photo]

class PhotosProvider: NSObject {
/// Simulate a synchronous fetch of a slideshow, perhaps from a local database.
func fetchDemoSlideshow() -> Slideshow {
return (0...4).map { demoPhoto(identifier: $0) }
}

let photos: [ExamplePhoto] = {

var mutablePhotos: [ExamplePhoto] = []
var image = UIImage(named: PrimaryImageName)
let NumberOfPhotos = 5

func shouldSetImageOnIndex(photoIndex: Int) -> Bool {
return photoIndex != CustomEverythingPhotoIndex && photoIndex != DefaultLoadingSpinnerPhotoIndex
/// Simulate fetching a photo from the network.
/// For simplicity in this demo, errors are not simulated, and the callback is invoked on the main queue.
func fetchPhoto(named name: String, then completionHandler: @escaping (UIImage) -> Void) {
let delay = Double(arc4random_uniform(3) + 2)
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
guard let result = UIImage(named: name) else { fatalError("Image '\(name)' could not be loaded from the bundle.") }
completionHandler(result)
}

for photoIndex in 0 ..< NumberOfPhotos {
let title = NSAttributedString(string: "\(photoIndex + 1)", attributes: [NSForegroundColorAttributeName: UIColor.whiteColor()])

let photo = shouldSetImageOnIndex(photoIndex) ? ExamplePhoto(image: image, attributedCaptionTitle: title) : ExamplePhoto(attributedCaptionTitle: title)

if photoIndex == CustomEverythingPhotoIndex {
photo.placeholderImage = UIImage(named: PlaceholderImageName)
}

mutablePhotos.append(photo)
}
}

extension PhotosProvider {
fileprivate func demoPhoto(identifier: Int) -> Photo {
let photoName: String
let photoSummary: String
let photoCredit: String

if (arc4random_uniform(2) == 0 && identifier != 0) {
photoName = "Chess"
photoSummary = "Children gather around a game of chess during the Ann Arbor Summer Festival. (Photo ID \(identifier))"
photoCredit = "Photo: Chris Dzombak"
} else {
photoName = "NYTimesBuilding"
photoSummary = "The New York Times office in Manhattan. (Photo ID \(identifier))"
photoCredit = "Photo: Nic Lehoux"
}
return mutablePhotos
}()

return Photo(name: photoName, summary: photoSummary, credit: photoCredit, identifier: identifier)
}
}
119 changes: 40 additions & 79 deletions Example-Swift/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,102 +9,63 @@
import UIKit
import NYTPhotoViewer

// The Swift demo project doesn't aim to exactly replicate the ObjC demo, which comprehensively demonstrates the photo viewer.
// Rather, the Swift demo aims to show how to interact with the photo viewer in Swift, and how an application might coordinate the photo viewer with a more complex data layer.

class ViewController: UIViewController, NYTPhotosViewControllerDelegate {
final class ViewController: UIViewController {

let ReferencePhotoName = "NYTimesBuilding"

var photoViewerCoordinator: PhotoViewerCoordinator?

@IBOutlet weak var imageButton : UIButton?
private let photos = PhotosProvider().photos

@IBAction func buttonTapped(sender: UIButton) {
let photosViewController = NYTPhotosViewController(photos: self.photos)
@IBAction func buttonTapped(_ sender: UIButton) {
let coordinator = PhotoViewerCoordinator(provider: PhotosProvider())
photoViewerCoordinator = coordinator

let photosViewController = coordinator.photoViewer
photosViewController.delegate = self
presentViewController(photosViewController, animated: true, completion: nil)

updateImagesOnPhotosViewController(photosViewController, afterDelayWithPhotos: photos)
present(photosViewController, animated: true, completion: nil)
}

func updateImagesOnPhotosViewController(photosViewController: NYTPhotosViewController, afterDelayWithPhotos: [ExamplePhoto]) {

let delayTime = dispatch_time(DISPATCH_TIME_NOW, 5 * Int64(NSEC_PER_SEC))

dispatch_after(delayTime, dispatch_get_main_queue()) {
for photo in self.photos {
if photo.image == nil {
photo.image = UIImage(named: PrimaryImageName)
photosViewController.updateImageForPhoto(photo)
}
}
}
}


override func viewDidLoad() {
super.viewDidLoad()
let buttonImage = UIImage(named: PrimaryImageName)
imageButton?.setBackgroundImage(buttonImage, forState: .Normal)
}

// MARK: - NYTPhotosViewControllerDelegate

func photosViewController(photosViewController: NYTPhotosViewController, handleActionButtonTappedForPhoto photo: NYTPhoto) -> Bool {

if UIDevice.currentDevice().userInterfaceIdiom == .Pad {

guard let photoImage = photo.image else { return false }

let shareActivityViewController = UIActivityViewController(activityItems: [photoImage], applicationActivities: nil)

shareActivityViewController.completionWithItemsHandler = {(activityType: String?, completed: Bool, items: [AnyObject]?, error: NSError?) in
if completed {
photosViewController.delegate?.photosViewController!(photosViewController, actionCompletedWithActivityType: activityType!)
}
}
let buttonImage = UIImage(named: ReferencePhotoName)
imageButton?.setBackgroundImage(buttonImage, for: UIControlState())
}
}

shareActivityViewController.popoverPresentationController?.barButtonItem = photosViewController.rightBarButtonItem
photosViewController.presentViewController(shareActivityViewController, animated: true, completion: nil)
// MARK: NYTPhotosViewControllerDelegate

return true
}

return false
}
extension ViewController: NYTPhotosViewControllerDelegate {

func photosViewController(photosViewController: NYTPhotosViewController, referenceViewForPhoto photo: NYTPhoto) -> UIView? {
if photo as? ExamplePhoto == photos[NoReferenceViewPhotoIndex] {
return nil
func photosViewController(_ photosViewController: NYTPhotosViewController, handleActionButtonTappedFor photo: NYTPhoto) -> Bool {
guard UIDevice.current.userInterfaceIdiom == .pad, let photoImage = photo.image else {
return false
}
return imageButton
}

func photosViewController(photosViewController: NYTPhotosViewController, loadingViewForPhoto photo: NYTPhoto) -> UIView? {
if photo as! ExamplePhoto == photos[CustomEverythingPhotoIndex] {
let label = UILabel()
label.text = "Custom Loading..."
label.textColor = UIColor.greenColor()
return label
}
return nil
}

func photosViewController(photosViewController: NYTPhotosViewController, captionViewForPhoto photo: NYTPhoto) -> UIView? {
if photo as! ExamplePhoto == photos[CustomEverythingPhotoIndex] {
let label = UILabel()
label.text = "Custom Caption View"
label.textColor = UIColor.whiteColor()
label.backgroundColor = UIColor.redColor()
return label

let shareActivityViewController = UIActivityViewController(activityItems: [photoImage], applicationActivities: nil)
shareActivityViewController.completionWithItemsHandler = {(activityType: UIActivityType?, completed: Bool, items: [Any]?, error: Error?) in
if completed {
photosViewController.delegate?.photosViewController!(photosViewController, actionCompletedWithActivityType: activityType?.rawValue)
}
}
return nil
}

func photosViewController(photosViewController: NYTPhotosViewController, didNavigateToPhoto photo: NYTPhoto, atIndex photoIndex: UInt) {
print("Did Navigate To Photo: \(photo) identifier: \(photoIndex)")

shareActivityViewController.popoverPresentationController?.barButtonItem = photosViewController.rightBarButtonItem
photosViewController.present(shareActivityViewController, animated: true, completion: nil)

return true
}

func photosViewController(photosViewController: NYTPhotosViewController, actionCompletedWithActivityType activityType: String?) {
print("Action Completed With Activity Type: \(activityType)")
func photosViewController(_ photosViewController: NYTPhotosViewController, referenceViewFor photo: NYTPhoto) -> UIView? {
guard let box = photo as? NYTPhotoBox else { return nil }

return box.value.name == ReferencePhotoName ? imageButton : nil
}

func photosViewControllerDidDismiss(photosViewController: NYTPhotosViewController) {
print("Did dismiss Photo Viewer: \(photosViewController)")
func photosViewControllerDidDismiss(_ photosViewController: NYTPhotosViewController) {
photoViewerCoordinator = nil
}
}
Loading

0 comments on commit 533a1fa

Please sign in to comment.