Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

ForceZoom: Popup Image Detail View Using 3D Touch Peek

DZone's Guide to

ForceZoom: Popup Image Detail View Using 3D Touch Peek

More experiments showing how useful 3D Touch can be for your apps

· Mobile Zone
Free Resource

Launching an app doesn’t need to be daunting. Whether you’re just getting started or need a refresher on mobile app testing best practices, this guide is your resource! Brought to you in partnership with Perfecto

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!

        override func 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
    {
        return min(UIScreen.mainScreen().bounds.size.width,
            UIScreen.mainScreen().bounds.size.height)
    }

The white preview box, which is a CAShapeLayer, needs to be that size at the same scale as the image has been scaled on the screen. The maths to do this is in the displayPreviewFrame() method which is invoked by touchesBegan:

    let previewFrameSize = peekPreviewSize * imageScale

Where imageScale is simply the component's width or height divided by the image's width or height:

   var imageScale: CGFloat
    {
        return min(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, ForceZoomneeds 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


Next, I calculate the distance between the edge of the component and the edge of the image it contains:

    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 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)

Source Code

As always, the full source code for this project is available in my GitHub repository here. Enjoy!

Keep up with the latest DevTest Jargon with the latest Mobile DevTest Dictionary. Brought to you in partnership with Perfecto.

Topics:
swift ,ios

Published at DZone with permission of Simon Gladman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}