├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Documentations ├── livetext_barcode.jpg └── livetext_imagescan.jpg ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── VisionLiveText_SwiftUICompatible │ ├── DataScannerView.swift │ ├── ImageLiveTextView.swift │ ├── ImagePicker.swift │ ├── PickImageAndShowLiveText.swift │ └── ScanAndSelectTextView.swift └── Tests └── VisionLiveText_SwiftUICompatibleTests └── VisionLiveText_SwiftUICompatibleTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentations/livetext_barcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mszpro/VisionLiveText_SwiftUICompatible/fbd63a0bef5462c7aaac7168a99d74cc7f83862e/Documentations/livetext_barcode.jpg -------------------------------------------------------------------------------- /Documentations/livetext_imagescan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mszpro/VisionLiveText_SwiftUICompatible/fbd63a0bef5462c7aaac7168a99d74cc7f83862e/Documentations/livetext_imagescan.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shunzhe (mszpro.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "VisionLiveText_SwiftUICompatible", 8 | products: [ 9 | // Products define the executables and libraries a package produces, and make them visible to other packages. 10 | .library( 11 | name: "VisionLiveText_SwiftUICompatible", 12 | targets: ["VisionLiveText_SwiftUICompatible"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 21 | .target( 22 | name: "VisionLiveText_SwiftUICompatible", 23 | dependencies: []), 24 | .testTarget( 25 | name: "VisionLiveText_SwiftUICompatibleTests", 26 | dependencies: ["VisionLiveText_SwiftUICompatible"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live Text - 画像テキスト認識 - SwiftUI互換ビュー 2 | 3 | WWDC 2022 / iOS 16で公開された新機能「ライブテキスト」のSwiftUIで使用されている互換ビューです。 4 | These are compatible views used in SwiftUI for the new live text feature released in WWDC 2022 / iOS 16. 5 | 6 | | DataScannerView | ImageLiveTextView | 7 | |---|---| 8 | | ![bar-code-scan-demo](/Documentations/livetext_barcode.jpg) | ![live-text-from-image-demo](/Documentations/livetext_imagescan.jpg) | 9 | 10 | !! このフレームワークは、Xcode 14 betaとiOS 16のシミュレータ/デバイスで動作します。 11 | 12 | ## DataScannerView 13 | 14 | You can use the `DataScannerView` to use a live camera stream and scan for text or machine-readable codes. Found elements will be highlighted and selectable. (remember to add the permission to access the camera) 15 | 16 | You will present this view with camera view. 17 | 18 | `DataScannerView`を使用すると、ライブカメラストリームを使用して、テキストまたは機械可読コードをスキャンすることができます。見つかった要素はハイライト表示され、選択することができます。(カメラへのアクセス権を追加することを忘れないでください) 19 | 20 | カメラビューで表示されます。 21 | 22 | ```swift 23 | // Button to start scanning 24 | Button("Show scanning view") { 25 | self.showScanningView = true 26 | } 27 | .sheet(isPresented: $showScanningView) { 28 | NavigationStack { 29 | DataScannerView(startScanning: $showScanningView, 30 | tappedScanItem: $scannedItem) 31 | } 32 | } 33 | ``` 34 | 35 | You can check for device compatibility and disable the button when the device does not support this feature: `.disabled(!(DataScannerViewController.isSupported && DataScannerViewController.isAvailable))` 36 | 37 | You may also need to add a button to close the scanner view: 38 | 39 | デバイスの互換性をチェックして、デバイスがこの機能をサポートしていない場合はボタンを無効にすることができます: `.disabled(!(DataScannerViewController.isSupported && DataScannerViewController.isAvailable))` 40 | 41 | また、スキャナビューを閉じるためのボタンを追加する必要があるかもしれません。 42 | 43 | ```swift 44 | .toolbar { 45 | ToolbarItem(placement: .navigationBarLeading) { 46 | Button("Cancel") { 47 | self.showScanningView = false 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | [デモコードファイル](/Documentations/DataScannerView_Demo.swift)をご覧ください。 54 | 55 | ## ImageLiveTextView 56 | 57 | You can also use the `ImageLiveTextView` to analyze an UIImage file. Users will be able to long-press to select the text recognized. 58 | また、`ImageLiveTextView`を使用して`UIImage`ファイルを解析することができます。ユーザーは長押しで認識されたテキストを選択できるようになります。 59 | 60 | ```swift 61 | if let pickedImageObject { 62 | ImageLiveTextView(imageObject: pickedImageObject, analyzerConfiguration: .init(.text)) 63 | .frame(height: 500) 64 | } 65 | ``` 66 | 67 | Here, `pickedImageObject` will contain the `UIImage` object the user picked. 68 | ここで、 `pickedImageObject` にはユーザがピックした `UIImage` オブジェクトが入ります。 69 | 70 | Remember to define a frame for the view. 71 | ビューのフレームを定義することを忘れないでください。 72 | 73 | [デモコードファイル](/Documentations/ImageLiveTextView_Demo.swift)をご覧ください。 74 | 75 | ## 使用方法 76 | 77 | ### Swift Package Manager 78 | 79 | 1. Xcode内からプロジェクトを開く 80 | 2. 上部のシステムバーの"File"をクリック 81 | 3. "Add Packages..."をクリック 82 | 4. 以下のURLをペースト:`https://github.com/mszpro/VisionLiveText_SwiftUICompatible.git` 83 | 5. Version: Up to Next Major `1.0 <` 84 | 6. "Next"をクリック 85 | 7. "Done"をクリック。 86 | 87 | ## LICENSE 88 | 89 | MIT LICENSE. You have to include the complete and unmodified contents within the `LICENSE` file of this project and make it visible to the end user. 90 | -------------------------------------------------------------------------------- /Sources/VisionLiveText_SwiftUICompatible/DataScannerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataScannerView.swift 3 | // NewInVisionKit_SwiftUI 4 | // 5 | // Created by Shunzhe on 2022/06/16. 6 | // 7 | 8 | #if canImport(VisionKit) 9 | 10 | import SwiftUI 11 | import VisionKit 12 | 13 | @available(iOS 16.0, *) 14 | public struct DataScannerView: UIViewControllerRepresentable { 15 | 16 | @Binding var startScanning: Bool 17 | @Binding var tappedScanItem: RecognizedItem? 18 | 19 | public init(startScanning: Binding, tappedScanItem: Binding) { 20 | self._startScanning = startScanning 21 | self._tappedScanItem = tappedScanItem 22 | } 23 | 24 | public func makeUIViewController(context: Context) -> DataScannerViewController { 25 | let scannerVC = DataScannerViewController( 26 | recognizedDataTypes: [.text()], 27 | qualityLevel: .fast, 28 | recognizesMultipleItems: false, 29 | isHighFrameRateTrackingEnabled: false, 30 | isGuidanceEnabled: true, 31 | isHighlightingEnabled: true 32 | ) 33 | scannerVC.delegate = context.coordinator 34 | return scannerVC 35 | } 36 | 37 | public func updateUIViewController(_ viewController: DataScannerViewController, context: Context) { 38 | if startScanning { 39 | try? viewController.startScanning() 40 | } else { 41 | viewController.stopScanning() 42 | } 43 | } 44 | 45 | public func makeCoordinator() -> Coordinator { 46 | Coordinator(self) 47 | } 48 | 49 | public class Coordinator: NSObject, DataScannerViewControllerDelegate { 50 | 51 | var parent: DataScannerView 52 | 53 | init(_ parent: DataScannerView) { 54 | self.parent = parent 55 | } 56 | 57 | public func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) { 58 | parent.tappedScanItem = item 59 | } 60 | 61 | } 62 | 63 | } 64 | 65 | @available(iOS 16.0, *) 66 | extension RecognizedItem: Equatable { 67 | public static func == (lhs: RecognizedItem, rhs: RecognizedItem) -> Bool { 68 | return lhs.id == rhs.id 69 | } 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /Sources/VisionLiveText_SwiftUICompatible/ImageLiveTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLiveTextView.swift 3 | // NewInVisionKit_SwiftUI 4 | // 5 | // Created by Shunzhe on 2022/06/16. 6 | // 7 | 8 | #if canImport(UIKit) && canImport(VisionKit) 9 | 10 | import UIKit 11 | import SwiftUI 12 | import VisionKit 13 | 14 | @available(iOS 16.0, *) 15 | @MainActor 16 | public struct ImageLiveTextView: UIViewRepresentable { 17 | 18 | var imageObject: UIImage 19 | var analyzerConfiguration: ImageAnalyzer.Configuration 20 | 21 | // Some configurations 22 | var allowLongPressForDataDetectorsInTextMode: Bool 23 | var selectableItemsHighlighted: Bool 24 | 25 | private let imageView = LiveTextUIImageView() 26 | private let analyzer = ImageAnalyzer() 27 | private let interaction = ImageAnalysisInteraction() 28 | 29 | public init(imageObject: UIImage, analyzerConfiguration: ImageAnalyzer.Configuration, allowLongPressForDataDetectorsInTextMode: Bool = false, selectableItemsHighlighted: Bool = false) { 30 | self.imageObject = imageObject 31 | self.analyzerConfiguration = analyzerConfiguration 32 | self.allowLongPressForDataDetectorsInTextMode = allowLongPressForDataDetectorsInTextMode 33 | self.selectableItemsHighlighted = selectableItemsHighlighted 34 | } 35 | 36 | public func makeUIView(context: Context) -> UIImageView { 37 | imageView.image = imageObject 38 | imageView.addInteraction(interaction) 39 | imageView.contentMode = .scaleAspectFit 40 | return imageView 41 | } 42 | 43 | public func updateUIView(_ uiView: UIImageView, context: Context) { 44 | Task { 45 | if let image = imageView.image { 46 | let analysis = try? await analyzer.analyze(image, configuration: analyzerConfiguration) 47 | if let analysis = analysis { 48 | interaction.analysis = analysis 49 | interaction.preferredInteractionTypes = .textSelection 50 | interaction.selectableItemsHighlighted = true 51 | interaction.allowLongPressForDataDetectorsInTextMode = true 52 | } 53 | } 54 | } 55 | 56 | } 57 | 58 | } 59 | 60 | class LiveTextUIImageView: UIImageView { 61 | override var intrinsicContentSize: CGSize { 62 | .zero 63 | } 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /Sources/VisionLiveText_SwiftUICompatible/ImagePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Shunzhe Ma on 8/22/20. 6 | // 7 | 8 | /** 9 | NSPhotoLibraryUsageDescription 10 | 写真ライブラリへのアクセスが必要です 11 | */ 12 | 13 | #if os(iOS) && !targetEnvironment(macCatalyst) 14 | 15 | import Foundation 16 | import UIKit 17 | import SwiftUI 18 | import MobileCoreServices 19 | 20 | @available(iOS 13.0, *) 21 | public struct ImagePicker: UIViewControllerRepresentable { 22 | 23 | var onImagePicked: (UIImage) -> Void 24 | var onCancelled: () -> Void 25 | 26 | public init(onImagePicked: @escaping (UIImage) -> Void, onCancelled: @escaping () -> Void) { 27 | self.onImagePicked = onImagePicked 28 | self.onCancelled = onCancelled 29 | } 30 | 31 | public func makeCoordinator() -> Coordinator { 32 | Coordinator(self) 33 | } 34 | 35 | public func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext) { 36 | return 37 | } 38 | 39 | public func makeUIViewController(context: Context) -> UIImagePickerController { 40 | let pickerController = UIImagePickerController() 41 | pickerController.delegate = context.coordinator 42 | pickerController.mediaTypes = [kUTTypeImage as String] 43 | pickerController.sourceType = .photoLibrary 44 | return pickerController 45 | } 46 | 47 | public class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 48 | 49 | var parent: ImagePicker 50 | 51 | init(_ vc: ImagePicker) { 52 | self.parent = vc 53 | super.init() 54 | } 55 | 56 | public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 57 | picker.dismiss(animated: true, completion: nil) 58 | guard let image = info[.originalImage] as? UIImage else { 59 | self.parent.onCancelled() 60 | return 61 | } 62 | self.parent.onImagePicked(image) 63 | } 64 | 65 | } 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /Sources/VisionLiveText_SwiftUICompatible/PickImageAndShowLiveText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLiveTextView_Demo.swift 3 | // NewInVisionKit_SwiftUI 4 | // 5 | // Created by Shunzhe on 2022/06/16. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @available(iOS 16.0, *) 11 | public struct PickImageAndShowLiveText: View { 12 | 13 | @State private var pickedImageObject: UIImage? 14 | @State private var showImagePicker: Bool = false 15 | var photoPickingButtonLabel: String 16 | 17 | public init(photoPickingButtonLabel: String = "Pick a photo") { 18 | self.photoPickingButtonLabel = photoPickingButtonLabel 19 | } 20 | 21 | public var body: some View { 22 | 23 | VStack { 24 | 25 | Button(photoPickingButtonLabel) { 26 | self.showImagePicker = true 27 | } 28 | .sheet(isPresented: $showImagePicker) { 29 | ImagePicker { pickedImg in 30 | self.pickedImageObject = pickedImg 31 | self.showImagePicker = false 32 | } onCancelled: { 33 | self.showImagePicker = false 34 | } 35 | } 36 | 37 | if let pickedImageObject { 38 | ImageLiveTextView(imageObject: pickedImageObject, analyzerConfiguration: .init(.text)) 39 | .frame(height: 500) 40 | } 41 | 42 | 43 | } 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Sources/VisionLiveText_SwiftUICompatible/ScanAndSelectTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataScannerView_Demo.swift 3 | // NewInVisionKit_SwiftUI 4 | // 5 | // Created by Shunzhe on 2022/06/16. 6 | // 7 | 8 | #if canImport(VisionKit) 9 | 10 | import SwiftUI 11 | import VisionKit 12 | 13 | @available(iOS 16.0, *) 14 | public struct ScanAndSelectTextView: View { 15 | 16 | @State private var scannedItem: RecognizedItem? 17 | @State private var showScanningView: Bool = false 18 | var showScanningViewButtonLabel: String 19 | var cancelScanningViewButtonLabel: String 20 | 21 | public init(showScanningViewButtonLabel: String = "Show scanning view", 22 | cancelScanningViewButtonLabel: String = "Cancel") { 23 | self.showScanningViewButtonLabel = showScanningViewButtonLabel 24 | self.cancelScanningViewButtonLabel = cancelScanningViewButtonLabel 25 | } 26 | 27 | public var body: some View { 28 | 29 | VStack { 30 | 31 | // Button to start scanning 32 | Button(showScanningViewButtonLabel) { 33 | self.showScanningView = true 34 | } 35 | .sheet(isPresented: $showScanningView) { 36 | NavigationStack { 37 | DataScannerView(startScanning: $showScanningView, 38 | tappedScanItem: $scannedItem) 39 | .disabled(!(DataScannerViewController.isSupported && DataScannerViewController.isAvailable)) 40 | .toolbar { 41 | ToolbarItem(placement: .navigationBarLeading) { 42 | Button(cancelScanningViewButtonLabel) { 43 | self.showScanningView = false 44 | } 45 | } 46 | } 47 | } 48 | } 49 | .onChange(of: scannedItem) { newValue in 50 | if newValue != nil { 51 | self.showScanningView = false 52 | } 53 | } 54 | 55 | // Section to show tapped scan result 56 | if let scannedItem { 57 | switch scannedItem { 58 | case .text(let text): 59 | Text(text.transcript) 60 | .textSelection(.enabled) 61 | case .barcode(let barcode): 62 | Text(barcode.payloadStringValue ?? "") 63 | .textSelection(.enabled) 64 | } 65 | } 66 | 67 | } 68 | 69 | } 70 | 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /Tests/VisionLiveText_SwiftUICompatibleTests/VisionLiveText_SwiftUICompatibleTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import VisionLiveText_SwiftUICompatible 3 | 4 | final class VisionLiveText_SwiftUICompatibleTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssert(true) 10 | } 11 | } 12 | --------------------------------------------------------------------------------