├── .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 | |  |  |
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 |
--------------------------------------------------------------------------------