Skip to content

Commit

Permalink
Merge pull request #332 from ba01ei/master
Browse files Browse the repository at this point in the history
Support iOS 16 Live Text
  • Loading branch information
JanGorman authored Sep 27, 2022
2 parents 5a884b0 + fd31f53 commit 72009dc
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 11 deletions.
30 changes: 19 additions & 11 deletions Agrume/Agrume.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public final class Agrume: UIViewController {
private var images: [AgrumeImage]!
private let startIndex: Int
private let dismissal: Dismissal
private let enableLiveText: Bool

private var overlayView: AgrumeOverlayView?
private weak var dataSource: AgrumeDataSource?
Expand Down Expand Up @@ -63,9 +64,10 @@ public final class Agrume: UIViewController {
/// - background: The background configuration
/// - dismissal: The dismiss configuration
/// - overlayView: View to overlay the image (does not display with 'button' dismissals)
/// - enableLiveText: Enables Live Text interaction, iOS 16 only
public convenience init(image: UIImage, background: Background = .colored(.black),
dismissal: Dismissal = .withPan(.standard), overlayView: AgrumeOverlayView? = nil) {
self.init(images: [image], background: background, dismissal: dismissal, overlayView: overlayView)
dismissal: Dismissal = .withPan(.standard), overlayView: AgrumeOverlayView? = nil, enableLiveText: Bool = false) {
self.init(images: [image], background: background, dismissal: dismissal, overlayView: overlayView, enableLiveText: enableLiveText)
}

/// Initialize with a single image url
Expand All @@ -75,9 +77,10 @@ public final class Agrume: UIViewController {
/// - background: The background configuration
/// - dismissal: The dismiss configuration
/// - overlayView: View to overlay the image (does not display with 'button' dismissals)
/// - enableLiveText: Enables Live Text interaction, iOS 16 only
public convenience init(url: URL, background: Background = .colored(.black), dismissal: Dismissal = .withPan(.standard),
overlayView: AgrumeOverlayView? = nil) {
self.init(urls: [url], background: background, dismissal: dismissal, overlayView: overlayView)
overlayView: AgrumeOverlayView? = nil, enableLiveText: Bool = false) {
self.init(urls: [url], background: background, dismissal: dismissal, overlayView: overlayView, enableLiveText: enableLiveText)
}

/// Initialize with a data source
Expand All @@ -88,10 +91,11 @@ public final class Agrume: UIViewController {
/// - background: The background configuration
/// - dismissal: The dismiss configuration
/// - overlayView: View to overlay the image (does not display with 'button' dismissals)
/// - enableLiveText: Enables Live Text interaction, iOS 16 only
public convenience init(dataSource: AgrumeDataSource, startIndex: Int = 0, background: Background = .colored(.black),
dismissal: Dismissal = .withPan(.standard), overlayView: AgrumeOverlayView? = nil) {
dismissal: Dismissal = .withPan(.standard), overlayView: AgrumeOverlayView? = nil, enableLiveText: Bool = false) {
self.init(images: nil, dataSource: dataSource, startIndex: startIndex, background: background, dismissal: dismissal,
overlayView: overlayView)
overlayView: overlayView, enableLiveText: enableLiveText)
}

/// Initialize with an array of images
Expand All @@ -102,9 +106,10 @@ public final class Agrume: UIViewController {
/// - background: The background configuration
/// - dismissal: The dismiss configuration
/// - overlayView: View to overlay the image (does not display with 'button' dismissals)
/// - enableLiveText: Enables Live Text interaction, iOS 16 only
public convenience init(images: [UIImage], startIndex: Int = 0, background: Background = .colored(.black),
dismissal: Dismissal = .withPan(.standard), overlayView: AgrumeOverlayView? = nil) {
self.init(images: images, urls: nil, startIndex: startIndex, background: background, dismissal: dismissal, overlayView: overlayView)
dismissal: Dismissal = .withPan(.standard), overlayView: AgrumeOverlayView? = nil, enableLiveText: Bool = false) {
self.init(images: images, urls: nil, startIndex: startIndex, background: background, dismissal: dismissal, overlayView: overlayView, enableLiveText: enableLiveText)
}

/// Initialize with an array of image urls
Expand All @@ -115,13 +120,14 @@ public final class Agrume: UIViewController {
/// - background: The background configuration
/// - dismissal: The dismiss configuration
/// - overlayView: View to overlay the image (does not display with 'button' dismissals)
/// - enableLiveText: Enables Live Text interaction, iOS 16 only
public convenience init(urls: [URL], startIndex: Int = 0, background: Background = .colored(.black),
dismissal: Dismissal = .withPan(.standard), overlayView: AgrumeOverlayView? = nil) {
self.init(images: nil, urls: urls, startIndex: startIndex, background: background, dismissal: dismissal, overlayView: overlayView)
dismissal: Dismissal = .withPan(.standard), overlayView: AgrumeOverlayView? = nil, enableLiveText: Bool = false) {
self.init(images: nil, urls: urls, startIndex: startIndex, background: background, dismissal: dismissal, overlayView: overlayView, enableLiveText: enableLiveText)
}

private init(images: [UIImage]? = nil, urls: [URL]? = nil, dataSource: AgrumeDataSource? = nil, startIndex: Int,
background: Background, dismissal: Dismissal, overlayView: AgrumeOverlayView? = nil) {
background: Background, dismissal: Dismissal, overlayView: AgrumeOverlayView? = nil, enableLiveText: Bool = false) {
switch (images, urls) {
case (let images?, nil):
self.images = images.map { AgrumeImage(image: $0) }
Expand All @@ -135,6 +141,7 @@ public final class Agrume: UIViewController {
self.currentIndex = startIndex
self.background = background
self.dismissal = dismissal
self.enableLiveText = enableLiveText
super.init(nibName: nil, bundle: nil)

self.overlayView = overlayView
Expand Down Expand Up @@ -468,6 +475,7 @@ extension Agrume: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: AgrumeCell = collectionView.dequeue(indexPath: indexPath)

cell.enableLiveText = enableLiveText
cell.tapBehavior = tapBehavior
switch dismissal {
case .withPan(let physics), .withPanAndButton(let physics, _):
Expand Down
29 changes: 29 additions & 0 deletions Agrume/AgrumeCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import SwiftyGif
import UIKit
import VisionKit

protocol AgrumeCellDelegate: AnyObject {

Expand Down Expand Up @@ -60,12 +61,18 @@ final class AgrumeCell: UICollectionViewCell {
// if set to true, it means we are updating image on the same cell, so we want to reserve the zoom level & position
var updatingImageOnSameCell = false

// enables Live Text analysis & interaction
var enableLiveText = false

var image: UIImage? {
didSet {
if image?.imageData != nil, let image = image {
imageView.setGifImage(image)
} else {
imageView.image = image
if #available(iOS 16, *), enableLiveText, let image = image {
analyzeImage(image)
}
}
if !updatingImageOnSameCell {
updateScrollViewAndImageViewForCurrentMetrics()
Expand Down Expand Up @@ -482,4 +489,26 @@ extension AgrumeCell: UIScrollViewDelegate {
dismiss()
}
}

@available(iOS 16, *)
private func analyzeImage(_ image: UIImage) {
guard ImageAnalyzer.isSupported else {
return
}
let interaction = ImageAnalysisInteraction()
imageView.addInteraction(interaction)

let analyzer = ImageAnalyzer()
let configuration = ImageAnalyzer.Configuration([.text, .machineReadableCode])

Task { @MainActor in
do {
let analysis = try await analyzer.analyze(image, configuration: configuration)
interaction.analysis = analysis
interaction.preferredInteractionTypes = .automatic
} catch {
print(error.localizedDescription)
}
}
}
}
4 changes: 4 additions & 0 deletions Example/Agrume Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
39B9D7C228DE0B500016BE7F /* LiveTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B9D7C128DE0B500016BE7F /* LiveTextViewController.swift */; };
39CA658926EFFC5700A5A910 /* URLUpdatedToImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CA658826EFFC5700A5A910 /* URLUpdatedToImageViewController.swift */; };
771DA7342179EF1800541206 /* SwiftyGif.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 771DA7332179EF1800541206 /* SwiftyGif.framework */; };
9464AFE923C692C7006ADEBD /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9464AFE823C692C7006ADEBD /* OverlayView.swift */; };
Expand Down Expand Up @@ -80,6 +81,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
39B9D7C128DE0B500016BE7F /* LiveTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTextViewController.swift; sourceTree = "<group>"; };
39CA658826EFFC5700A5A910 /* URLUpdatedToImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLUpdatedToImageViewController.swift; sourceTree = "<group>"; };
771DA7332179EF1800541206 /* SwiftyGif.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftyGif.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9464AFE823C692C7006ADEBD /* OverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -181,6 +183,7 @@
E77809E21D17821400CC60F1 /* SingleImageModalViewController.swift */,
F2D9598D1B1A133800073772 /* SingleImageViewController.swift */,
F2D959901B1A140200073772 /* SingleURLViewController.swift */,
39B9D7C128DE0B500016BE7F /* LiveTextViewController.swift */,
39CA658826EFFC5700A5A910 /* URLUpdatedToImageViewController.swift */,
F2A520181B130C7E00924912 /* Supporting Files */,
);
Expand Down Expand Up @@ -362,6 +365,7 @@
F2D9598E1B1A133800073772 /* SingleImageViewController.swift in Sources */,
F2539BD420F2418900062C80 /* CustomCloseButtonViewController.swift in Sources */,
F2A5201B1B130C7E00924912 /* AppDelegate.swift in Sources */,
39B9D7C228DE0B500016BE7F /* LiveTextViewController.swift in Sources */,
9464AFE923C692C7006ADEBD /* OverlayView.swift in Sources */,
F2D959971B1A199F00073772 /* MultipleURLsCollectionViewController.swift in Sources */,
F224A73227832DD900A8F5ED /* SwiftUIExampleViewController.swift in Sources */,
Expand Down
49 changes: 49 additions & 0 deletions Example/Agrume Example/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,26 @@
<segue destination="cCQ-GJ-dUD" kind="show" id="r46-tO-UZQ"/>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="6Zp-bu-jIU" style="IBUITableViewCellStyleDefault" id="Hfv-Wz-qVQ">
<rect key="frame" x="0.0" y="578" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Hfv-Wz-qVQ" id="KQk-qO-EJ6">
<rect key="frame" x="0.0" y="0.0" width="348.5" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="With Live Text" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6Zp-bu-jIU">
<rect key="frame" x="16" y="0.0" width="324.5" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="qLg-q5-gAS" kind="show" id="5bd-8h-ouw"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
Expand Down Expand Up @@ -733,6 +753,35 @@
</objects>
<point key="canvasLocation" x="2001" y="2266"/>
</scene>
<!--Live Text Image View Controller-->
<scene sceneID="1WM-Xw-5br">
<objects>
<viewController title="Live Text Image View Controller" id="qLg-q5-gAS" customClass="LiveTextViewController" customModule="Agrume_Example" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="6HJ-JL-fid"/>
<viewControllerLayoutGuide type="bottom" id="hXH-Vp-sjT"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Vfa-Ml-qsJ">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dFR-Lm-f5v">
<rect key="frame" x="146" y="318" width="83" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Open Image"/>
<connections>
<action selector="openImage:" destination="qLg-q5-gAS" eventType="touchUpInside" id="gRd-G8-Zjs"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<navigationItem key="navigationItem" id="Hrj-fJ-cCk"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="0eg-SZ-Rgn" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1281" y="2956"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "textAndQR.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions Example/Agrume Example/LiveTextViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// LiveTextViewController.swift
//

import Agrume
import UIKit
import VisionKit

final class LiveTextViewController: UIViewController {
@IBAction private func openImage(_ sender: Any) {
if #available(iOS 16, *), ImageAnalyzer.isSupported {
let agrume = Agrume(
image: UIImage(named: "TextAndQR")!,
enableLiveText: true
)
agrume.show(from: self)
return
}

let alert = UIAlertController(title: "Not supported on this device", message: "Live Text is available for devices with iOS 16 (or above) and A12 (or above) Bionic chip (iPhone XS and later, physical device only)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
present(alert, animated: true)
}
}
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,14 @@ agrume.onLongPress = helper.makeSaveToLibraryLongPressGesture

You can customise the look and functionality of the image views. To do so, you need create a class that inherits from `AgrumeOverlayView: UIView`. As this is nothing more than a regular `UIView` you can do anything you want with it like add a custom toolbar or buttons to it. The example app shows a detailed example of how this can be achieved.

### Live Text Support

Agrume supports Live Text introduced since iOS 16. This allows user to interact with texts and QR codes in the image. It is available for iOS 16 or newer, on devices with A12 Bionic Chip (iPhone XS) or newer.

```swift
let agrume = Agrume(image: UIImage(named: "")!, enableLiveText: true)
```

### Lifecycle

`Agrume` offers the following lifecycle closures that you can optionally set:
Expand Down

0 comments on commit 72009dc

Please sign in to comment.