Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

사진 앱에 있는 사진 검색하기 #10

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Gifty.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
43F711702A972EF400939C9C /* Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F7116F2A972EF400939C9C /* Const.swift */; };
43F711752A9733E000939C9C /* DetectBarcodeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F711742A9733E000939C9C /* DetectBarcodeService.swift */; };
43F711772A97481C00939C9C /* GiftBoxImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F711762A97481C00939C9C /* GiftBoxImage.swift */; };
B7350A872AB02BA00026CF13 /* Sequence+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7350A862AB02BA00026CF13 /* Sequence+.swift */; };
B79BC2BB2AAEE3F1005A54F7 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79BC2BA2AAEE3F1005A54F7 /* MainViewModel.swift */; };
B79BC2BD2AAEF7D0005A54F7 /* AppLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79BC2BC2AAEF7D0005A54F7 /* AppLogo.swift */; };
B79BC2C62AAEFDB7005A54F7 /* NanumSquareEB.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B79BC2C02AAEFDB7005A54F7 /* NanumSquareEB.ttf */; };
Expand All @@ -31,6 +32,7 @@
B79BC2C92AAEFDB7005A54F7 /* NanumSquareR.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B79BC2C32AAEFDB7005A54F7 /* NanumSquareR.ttf */; };
B79BC2CC2AAEFE0A005A54F7 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79BC2CB2AAEFE0A005A54F7 /* Font.swift */; };
B79BC2CE2AAF00B9005A54F7 /* DefaultLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79BC2CD2AAF00B9005A54F7 /* DefaultLabel.swift */; };
B7BAFB652AB04FF0007D6183 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = B7BAFB642AB04FF0007D6183 /* Algorithms */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -71,6 +73,7 @@
43F7116F2A972EF400939C9C /* Const.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Const.swift; sourceTree = "<group>"; };
43F711742A9733E000939C9C /* DetectBarcodeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectBarcodeService.swift; sourceTree = "<group>"; };
43F711762A97481C00939C9C /* GiftBoxImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiftBoxImage.swift; sourceTree = "<group>"; };
B7350A862AB02BA00026CF13 /* Sequence+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+.swift"; sourceTree = "<group>"; };
B79BC2BA2AAEE3F1005A54F7 /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = "<group>"; };
B79BC2BC2AAEF7D0005A54F7 /* AppLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLogo.swift; sourceTree = "<group>"; };
B79BC2C02AAEFDB7005A54F7 /* NanumSquareEB.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = NanumSquareEB.ttf; sourceTree = "<group>"; };
Expand All @@ -86,6 +89,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B7BAFB652AB04FF0007D6183 /* Algorithms in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -203,6 +207,7 @@
43F7116F2A972EF400939C9C /* Const.swift */,
43A134D92A9CE4B500428DC6 /* UITextField+LeftPadding.swift */,
B79BC2CB2AAEFE0A005A54F7 /* Font.swift */,
B7350A862AB02BA00026CF13 /* Sequence+.swift */,
);
path = Global;
sourceTree = "<group>";
Expand Down Expand Up @@ -269,6 +274,7 @@
);
name = Gifty;
packageProductDependencies = (
B7BAFB642AB04FF0007D6183 /* Algorithms */,
);
productName = Gifty;
productReference = 43D2A4542A964B2A002453BE /* Gifty.app */;
Expand Down Expand Up @@ -343,6 +349,7 @@
);
mainGroup = 43D2A44B2A964B2A002453BE;
packageReferences = (
B7BAFB632AB04FF0007D6183 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
);
productRefGroup = 43D2A4552A964B2A002453BE /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -404,6 +411,7 @@
43F711752A9733E000939C9C /* DetectBarcodeService.swift in Sources */,
43F711702A972EF400939C9C /* Const.swift in Sources */,
B79BC2BD2AAEF7D0005A54F7 /* AppLogo.swift in Sources */,
B7350A872AB02BA00026CF13 /* Sequence+.swift in Sources */,
B79BC2BB2AAEE3F1005A54F7 /* MainViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -743,6 +751,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
B7BAFB632AB04FF0007D6183 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-algorithms.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
B7BAFB642AB04FF0007D6183 /* Algorithms */ = {
isa = XCSwiftPackageProductDependency;
package = B7BAFB632AB04FF0007D6183 /* XCRemoteSwiftPackageReference "swift-algorithms" */;
productName = Algorithms;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 43D2A44C2A964B2A002453BE /* Project object */;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"pins" : [
{
"identity" : "swift-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-algorithms.git",
"state" : {
"revision" : "b14b7f4c528c942f121c8b860b9410b2bf57825e",
"version" : "1.0.0"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics",
"state" : {
"revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
"version" : "1.0.2"
}
}
],
"version" : 2
}
28 changes: 28 additions & 0 deletions Gifty/Global/Sequence+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Sequence+.swift
// Gifty
//
// Created by 주동석 on 2023/09/12.
//

import Foundation

extension Sequence {
func asyncMap<T>(_ transform: @escaping (Element) async -> T) async -> [T] {
return await withTaskGroup(of: T.self) { group in
var transformedElements = [T]()

for element in self {
group.addTask {
return await transform(element)
}
}

for await transformedElement in group {
transformedElements.append(transformedElement)
}

return transformedElements
}
}
}
9 changes: 6 additions & 3 deletions Gifty/Presentation/ViewController/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class MainViewController: UIViewController {
private let viewModel = MainViewModel()
private let defaultLabel = DefaultLabel(text: "불러온 사진이 없어요 🥲\n돋보기를 클릭해 기프티콘을 찾을 수 있어요!", size: 14, color: UIColor(hexCode: "333333"))
private let appLogo = AppLogo()
private let detectBarcodeService = DetectBarcodeService()
private let searchButton = SearchButton()
private let rangePopup = RangePopup()
private var cancellables = Set<AnyCancellable>()
Expand Down Expand Up @@ -79,8 +78,12 @@ extension MainViewController {

rangePopup.setupAction(
submit: UIAction { [self] _ in
rangePopup.removeFromSuperview()
detectBarcodeService.detectBarcodeInImage(images: [])
Task {
rangePopup.removeFromSuperview()
let barcodeImages = await viewModel.fetchPhotoImages()
print(viewModel.getBarcodeImageCount())
print(barcodeImages)
}
}
)
}
Expand Down
62 changes: 61 additions & 1 deletion Gifty/Presentation/ViewModel/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,29 @@
//

import UIKit
import Algorithms

actor ProgressTracker {
private var _count: Int = 0
private var _total: Int

init(total: Int) {
self._total = total
}

func increment() async {
_count += 1
}

var progressPercentage: Double {
return Double(_count) / Double(_total) * 100.0
}
}

class MainViewModel {
private let photoLibraryService = PhotoLibraryService()
private let detectBarcodeService = DetectBarcodeService()

@Published var photoCount = 0

init(photoCount: Int = 0) {
Expand All @@ -18,10 +38,50 @@ class MainViewModel {
func setPermission() async -> UIAlertController? {
let status = await photoLibraryService.requestPermission()
self.photoCount = photoLibraryService.getPhotoCount()

if (!status) {
return photoLibraryService.createPhotoLibraryRequestAlert()
}
return nil
}

func getBarcodeImageCount() -> Int {
return detectBarcodeService.detectedBarcodes.count
}

func fetchPhotoImages() async -> Set<UIImage> {
let photoAssets = photoLibraryService.getPhotoAssets()

let chunkSize = 8
let chunks = photoAssets.chunks(ofCount: chunkSize)
let progressTracker = ProgressTracker(total: photoAssets.count)

for chunk in chunks {
await withTaskGroup(of: Void.self) { group in
for asset in chunk {
group.addTask { [self] in
let image = photoLibraryService.photoAssetToUIImage(photoAsset: asset)
guard let imageData = image.jpegData(compressionQuality: 0.8) else {
await updateProgress(using: progressTracker)
return
}

detectBarcodeService.detectBarcodeInAsset(data: imageData)
await updateProgress(using: progressTracker)
}
}
}
}


print("바코드 찾기 끝")
return detectBarcodeService.detectedBarcodes
}


func updateProgress(using tracker: ProgressTracker) async {
await tracker.increment()
let progress = await tracker.progressPercentage
print("진행률: \(progress)%")
}
}
53 changes: 24 additions & 29 deletions Gifty/Service/DetectBarcodeService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,42 @@ import Foundation

import UIKit
import Vision
import Photos

class DetectBarcodeService {
private var detectedBarcodes: Set<String> = []

func detectBarcodeInImage(images: [UIImage]) {
for image in images {
guard let cgImage = image.cgImage else {
var detectedBarcodes: Set<UIImage> = []

func detectBarcodeInAsset(data: Data) {
let request = VNDetectBarcodesRequest { [self] (request, error) in
if let error = error {
print("Error detecting barcodes: \(error)")
return
}
let request = VNDetectBarcodesRequest { [weak self] (request, error) in
if let error = error {
print("Error detecting barcodes: \(error)")
return
}

self?.processBarcodes(request: request, in: cgImage)
}

let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
request.revision = VNDetectBarcodesRequestRevision1

do {
try handler.perform([request])
} catch {
print("Failed to perform request: \(error)")
}

self.processBarcodes(request: request, data: data)
}

let handler = VNImageRequestHandler(data: data, options: [:])

do {
try handler.perform([request])
} catch {
print("Failed to perform request: \(error)")
}
}

func processBarcodes(request: VNRequest, in cgImage: CGImage) {
func processBarcodes(request: VNRequest, data: Data) {
guard let results = request.results as? [VNBarcodeObservation] else {
return
}

for barcode in results {
if let payload = barcode.payloadStringValue {
detectedBarcodes.insert(payload)
print("Detected barcode with value: \(payload) and symbology: \(barcode.symbology.rawValue)")
if barcode.payloadStringValue != nil, barcode.symbology == .code128 {
print("find")
if let image = UIImage(data: data) {
detectedBarcodes.insert(image)
}
}
}

print("Find \(detectedBarcodes.count) barcodes")
}
}
37 changes: 37 additions & 0 deletions Gifty/Service/PhotoLibraryService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,41 @@ class PhotoLibraryService {
let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: .none)
return fetchResult.count
}

func getPhotoImages() -> [UIImage?] {
let photoAssets = getPhotoAssets()
return photoAssets.map { asset in
self.photoAssetToUIImage(photoAsset: asset)
}
}

func getPhotoAssets() -> [PHAsset] {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)

var photoAssets: [PHAsset] = []
for i in 0..<fetchResult.count {
print("사진 \(i)번째: \(fetchResult.object(at: i))")
photoAssets.append(fetchResult.object(at: i))
}

return photoAssets
}

func photoAssetToUIImage(photoAsset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.deliveryMode = .highQualityFormat
option.isSynchronous = true

manager.requestImage(for: photoAsset, targetSize: CGSize(width: 550, height: 550), contentMode: .aspectFit, options: option, resultHandler: {(result, info)->Void in
if let result = result{
thumbnail = result
}
})

return thumbnail
}
}