My experiments with 3D Touch on the iPhone 6s continue with ForceZoom, an extended UIImageView that displays a 1:1 peek detail view of a touched point on a large image.
The demo (above) contains three large images, forest (1600 x 1200), pharmacy (4535 x 1823) and petronas (3264 x 4896). An initial touch on the image displays the preview frame around the touch location and a deep press pops up a square preview of the image at that point at full resolution. The higher the resolution, the smaller the preview frame will be.
Installation & Implementation
ForceZoom consists of two files that need to be copied into a host application project:To implement a ForceZoom component in an application, instantiate with a default image and view controller and add to a view:
class ViewController: UIViewController
{
var imageView: ForceZoomWidget!
overridefunc viewDidLoad()
{
super.viewDidLoad()
imageView = ForceZoomWidget(image: UIImage(named: "forest.jpg")!,
viewController: self)
view.addSubview(imageView)
}
}
Displaying Preview Frame
Since the popup preview will be the largest square that can fit on the screen: var peekPreviewSize: CGFloat
{
returnmin(UIScreen.mainScreen().bounds.size.width,
UIScreen.mainScreen().bounds.size.height)
}
let previewFrameSize = peekPreviewSize * imageScale
var imageScale: CGFloat
{
returnmin(bounds.size.width / image!.size.width, bounds.size.height / image!.size.height)
}
Launching the Peek Preview
When previewingContext(viewControllerForLocation) is invoked in response to the user's deep press, ForceZoom needs to pass to the previewing component the normalised position of the touch. This is because I use the pop up image view's layer's contentsRect to position and clip the full resolution image and contentsRect uses normalised image coordinates.There are a few steps in previewingContext(viewControllerForLocation) to do this. First off, I calculate the size of the preview frame as a normalised value. This will be used as an offset from the touch origin to form the clip rectangle's origin:
let offset = ((peekPreviewSize * imageScale) / (imageWidth * imageScale)) / 2
let leftBorder = (bounds.width - (imageWidth * imageScale)) / 2
Then, with the location of the touch point and these two new values, I can create the normalised x origin of the clip rectangle:
let normalisedXPosition = ((location.x - leftBorder) / (imageWidth * imageScale)) - offset
I do the same for y and with those two normalised values create a preview point:
let topBorder = (bounds.height - (imageHeight * imageScale)) / 2
let normalisedYPosition = ((location.y - topBorder) / (imageHeight * imageScale)) - offset
let normalisedPreviewPoint = CGPoint(x: normalisedXPosition, y: normalisedYPosition)
...which is passed to my ForceZoomPreview:
let peek = ForceZoomPreview(normalisedPreviewPoint: normalisedPreviewPoint, image: image!)
The Peek Preview
The previewing component now has very little work to do. It's passed the normalised origin in its constructor (above), so all it needs to do is use those values to set the contentsRect of an image view: imageView.layer.contentsRect = CGRect(
x: max(min(normalisedPreviewPoint.x, 1), 0),
y: max(min(normalisedPreviewPoint.y, 1), 0),
width: view.frame.width / image.size.width,
height: view.frame.height / image.size.height)