Skip to content

ohdair/MagicIDR

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

53 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

์•ฑ ํ™”๋ฉด

์‚ฌ๊ฐํ˜•์„ ๊ฐ์ง€ํ•˜์—ฌ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋Š” ์•ฑ

  • ๊ฐ์ง€๋œ ์‚ฌ๊ฐํ˜•์„ ์ž๋™/์ˆ˜๋™์œผ๋กœ ์ดฌ์˜
  • ์ดฌ์˜์Œ ์œ /๋ฌด ๊ฒฐ์ •
  • ์ˆ˜๋™์œผ๋กœ ์ดฌ์˜๋œ ์ด๋ฏธ์ง€๋ฅผ ๋ชจ์„œ๋ฆฌ/๋ณ€์„ ํ„ฐ์น˜์— ๋”ฐ๋ผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“œ ์ง€์›
  • ์ดฌ์˜๋œ ์ด๋ฏธ์ง€ ์‚ญ์ œ ๋ฐ ํšŒ์ „ ๊ทธ๋ฆฌ๊ณ  ๊ณต์œ  ๊ธฐ๋Šฅ
๋ฉ”์ธํ™”๋ฉด ํŽธ์ง‘ํ™”๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐํ™”๋ฉด

์•ฑ ๋™์ž‘

์ž๋™์ดฌ์˜ UI ๋ณ€๊ฒฝ ํŽธ์ง‘ํ™”๋ฉด

ํ๋ฆ„์— ๋”ฐ๋ฅธ ๋‹ค์ด์–ด๊ทธ๋žจ

๊ฐ View์— ๋”ฐ๋ผ ์œ ์ € ์ œ์Šค์ฒ˜์— ๋Œ€ํ•œ ๋™์ž‘์„ ๋‹ค์ด์–ด๊ทธ๋žจ์œผ๋กœ ํ‘œํ˜„

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2024-02-05 แ„‹แ…ฉแ„’แ…ฎ 9 10 47

์ดฌ์˜ ๋ชจ๋“œ

ํ”„๋ ˆ์ž„์›Œํฌ AVFoundation๋ฅผ ํ™œ์šฉ

  1. ์นด๋ฉ”๋ผ ์ดฌ์˜ํ•˜๋Š” ์˜์ƒ์˜ ๋ฐ์ดํ„ฐ์—์„œ ์ด๋ฏธ์ง€๋ฅผ View์— ๋ฐ˜์˜
  2. ์ดฌ์˜ ๋ฒ„ํŠผ์„ ํด๋ฆญ ์‹œ, Capture๋œ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ด
  3. 1๋ฒˆ์—์„œ ๋ฐ›์•„์˜จ ์ด๋ฏธ์ง€์—์„œ CIDetector๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ๊ฐํ˜•์„ ๊ฐ์ง€
  4. (์ž๋™ ๋ชจ๋“œ) 3๋ฒˆ์—์„œ ๊ฐ์ง€ ์‹œ๊ฐ„์ด 1.5์ดˆ ํ›„ ์ดฌ์˜
  5. ์ดฌ์˜๋˜๋ฉด ์ขŒ์ธก ํ•˜๋‹จ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ๋งˆ์ง€๋ง‰ ์ด๋ฏธ์ง€ ๋ฐ ๊ฐฏ์ˆ˜๋ฅผ ํ‘œํ˜„
  6. ๋ชจ๋“œ ๋ณ€๊ฒฝ์„ ์œ„ํ•œ Custom Button ์ถ”๊ฐ€

โ—ผ๏ธŽ Async/Await ํ•จ์ˆ˜ ์ƒ์„ฑ

์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ CIImage๋กœ ๋งŒ๋“ค๊ณ  ๋„˜๊ธฐ๊ธฐ ์œ„ํ•ด Async/Await ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ „๋‹ฌ

class Scanner: NSObject {
    private var scanSuccessBlock: ((CIImage?) -> Void)?

    func scan() async -> CIImage? {
        let settings = AVCapturePhotoSettings()
        photoOutput.capturePhoto(with: settings, delegate: self)
    
        return await withCheckedContinuation { continuation in
            scanSuccessBlock = { image in
                continuation.resume(returning: image)
            }
        }
    }
}

extension Scanner: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        if error != nil {
            scanSuccessBlock?(nil)
            return
        }

        if let data = photo.fileDataRepresentation() {
            let image = CIImage(data: data)
            scanSuccessBlock?(image)
        }
    }
}

โ—ผ๏ธŽ ์ดฌ์˜์Œ์„ ์Œ์†Œ๊ฑฐ ๊ฐ€๋Šฅํ•˜๋„๋ก ์˜ต์…˜ ์ถ”๊ฐ€

๊ตญ๊ฐ€๋งˆ๋‹ค ์ดฌ์˜์Œ์˜ ์œ ๋ฌด๊ฐ€ ์žˆ๊ฒ ์ง€๋งŒ, ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ๋ฐ˜์˜

extension Scanner: AVCaptureVideoDataOutputSampleBufferDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
        if isMuted {
            AudioServicesDisposeSystemSoundID(1108)
        } else {
            AudioServicesPlaySystemSound(1108)
        }
    }
}

โ—ผ๏ธŽ ์ธ์‹๋˜๋Š” ์‹œ๊ฐ„์„ Timer๋กœ ํ™œ์šฉํ•˜์—ฌ ์ธก์ •

์•ฝ 1.5์ดˆ๋ฅผ ์ธ์‹ํ•˜๋Š” ๋™์•ˆ ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง„ํ–‰์ƒํ™ฉ์„ delegate๋กœ ์ „๋‹ฌ 16.7%์˜ ์ง„ํ–‰๋„๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉฐ, 1.5์ดˆ๊ฐ€ ๋„๋‹ฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค๋ฉด ์ดฌ์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก delegate๋กœ ์ „๋‹ฌ

class AutoDetector {
    // ...
    private func startTimer() {
        timer?.invalidate()

        timer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
    }

    @objc private func fireTimer() {
        processing += 0.167
        delegate?.autoDectectorDidDetected(self, processing: processing)

        if processing >= 1.0 {
            delegate?.autoDectectorCompleted(self)
            resetTimer()
        }
    }
}

โ—ผ๏ธŽ ๊ฐ์ง€๋œ ์‚ฌ๊ฐํ˜•์„ View์— ํ‘œํ˜„

CIDetector์˜ CIDetectorTypeRectangle ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€ ๋‚ด ์‚ฌ๊ฐํ˜•์„ ๊ฐ์ง€ ๊ฐ์ง€๋œ CIRectangleFeature์˜ ๊ฐ’์€ ์ดฌ์˜๋œ ์ด๋ฏธ์ง€ ๋‚ด ์‚ฌ๊ฐํ˜•์˜ ์ขŒํ‘œ๋กœ ๋ณด์ •์ด ํ•„์š” ์ขŒ์šฐ ๋ฐ˜์ „, ๊ฐ๋„ ๋ณ€๊ฒฝ ๋“ฑ ๋””๋ฐ”์ด์Šค์˜ ํฌ๊ธฐ์™€ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ๊ฐ’์„ ๋ณด์ •

๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ชจ๋“œ

์ดฌ์˜๋œ ์ด๋ฏธ์ง€๋“ค์„ ์œ ์ €์˜ slide ์ œ์Šค์ณ๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€๋ฅผ ํ•œ ์žฅ์”ฉ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ํ‘œํ˜„ ์ดฌ์˜๋œ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ˜์‹œ๊ณ„ ํšŒ์ „/์‚ญ์ œ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“œ

โ—ผ๏ธŽ UIPageViewController๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ‘œํ˜„

slide๋ฅผ ํ†ตํ•ด ์ขŒ, ์šฐ๋กœ ์ดฌ์˜๋œ ์ด๋ฏธ์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ํ‘œํ˜„ ์ด๋ฏธ์ง€๊ฐ€ ์‚ญ์ œ๋˜๋Š” index์— ๋”ฐ๋ผ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋‹ค๋ฅด๊ฒŒ ํ‘œํ˜„

@objc private func deleteImage() {
    // ํ˜„์žฌ content์˜ pageIndex ํƒ์ƒ‰
    guard let viewController =  self.pageViewController.viewControllers?.first,
          let contentController = viewController as? ContentViewController,
          let currentIndex = contentController.pageIndex else {
        return
    }

    images.remove(at: currentIndex)

    // ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค๋ฉด ์ดฌ์˜ ๋ชจ๋“œ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
    guard !images.isEmpty else {
        delegate?.previewViewControllerWillDisappear(self, images: images)
        navigationController?.popViewController(animated: true)
        return
    }

    // ์‚ญ์ œ๋œ index๊ฐ€ ๋งˆ์ง€๋ง‰ ๋ฒˆํ˜ธ์˜€๋‹ค๋ฉด index - 1๋กœ .reverse ํ˜•ํƒœ๋กœ ํ‘œํ˜„
    guard currentIndex != images.count else {
        let willAppearController = contentViewController(atIndex: currentIndex - 1)!
        pageViewController.setViewControllers([willAppearController],
                                              direction: .reverse,
                                              animated: true)
        setTitle(withIndex: currentIndex - 1)
        return
    }

    // ์œ„ ์กฐ๊ฑด์„ ์ œ์™ธํ•œ ๋ชจ๋“  ๊ฒฝ์šฐ์˜ ์ˆ˜๋Š” ์‚ญ์ œ๋œ index์˜ ๋ฐ์ดํ„ฐ๋กœ .forward ํ˜•ํƒœ๋กœ ํ‘œํ˜„
    let willAppearController = contentViewController(atIndex: currentIndex)!
    pageViewController.setViewControllers([willAppearController],
                                          direction: .forward,
                                          animated: true)

    setTitle(withIndex: currentIndex)
}

โ—ผ๏ธŽ ์ด๋ฏธ์ง€์˜ ์ •๋ณด๋ฅผ ์ฝ์–ด์™€ ๋ฐ˜์‹œ๊ณ„๋กœ ํšŒ์ „

ํ•ด๋‹น ์ด๋ฏธ์ง€์˜ orientation์„ ์ฝ์–ด์„œ ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋„๋ก UIImage(ciImage:scale:orientation:)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜ํ™˜

func rotateCounterClockwise() -> UIImage? {
    var newOrientation: UIImage.Orientation?

    switch self.imageOrientation {
    case .up:
        newOrientation = .left
    case .down:
        newOrientation = .right
    case .left:
        newOrientation = .down
    case .right:
        newOrientation = .up
    default:
        break
    }

    // ...
}

ํŽธ์ง‘ ๋ชจ๋“œ

์ดฌ์˜๋œ ์ด๋ฏธ์ง€์—์„œ ๊ฐ์ง€๋œ ์‚ฌ๊ฐํ˜•์ด ์žˆ๋‹ค๋ฉด ์ด๋ฏธ์ง€๋ฅผ ์ž๋ฅผ ์ˆ˜ ์žˆ๋Š” ์‚ฌ๊ฐํ˜•์ด ์กด์žฌ ํ„ฐ์น˜์— ๋”ฐ๋ผ ์‚ฌ๊ฐํ˜•์˜ ๋ชจ์–‘์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก ํ‘œํ˜„

โ—ผ๏ธŽ ๊ฐ์ง€๋œ ์‚ฌ๊ฐํ˜• ๋ชจ์„œ๋ฆฌ์˜ ์ขŒํ‘œ๋ฅผ ํ‘œํ˜„ํ•˜๋Š” View

func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) ํ•จ์ˆ˜๋ฅผ ์žฌ์ •์˜ํ•˜์—ฌ ํ‘œํ˜„ ํ„ฐ์น˜๋ฅผ ํ•  ๋–„๋งˆ๋‹ค View๊ฐ€ ๋†“์—ฌ์ง€๋Š” ์œ„์น˜ ๋ฐ ์‚ฌ๊ฐํ˜•์„ ๋‹ค์‹œ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋„๋ก ํ‘œํ˜„

โ—ผ๏ธŽ ์‚ฌ๊ฐํ˜•์„ ๊ทธ๋ฆฌ๋Š” path

func draw(_ rect: CGRect) ํ•จ์ˆ˜๋ฅผ ์žฌ์ •์˜ํ•˜์—ฌ path๋ฅผ ํ‘œํ˜„ ์‚ฌ๊ฐํ˜• ๋ชจ์„œ๋ฆฌ๋“ค์˜ ์ขŒํ‘œ๊ฐ€ ๋‹ฌ๋ผ์งˆ ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ ๊ทธ๋ ค์งˆ ์ˆ˜ ์žˆ๋„๋ก setNeedsDisplay()๋ฅผ ํ˜ธ์ถœ ์ขŒํ‘œ๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ context ์œ„์— path๋ฅผ ๊ทธ๋ฆฌ๊ธฐ

โ—ผ๏ธŽ ์‚ฌ๊ฐํ˜•์˜ ๋ณ€์„ ํ„ฐ์น˜ํ•˜์—ฌ๋„ ๋ชจ์„œ๋ฆฌ๋“ค์˜ ์ขŒํ‘œ๊ฐ€ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ View

View๋Š” frame์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ทธ๋ ค์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ง์„ ์˜ ํ˜•ํƒœ์— ํ„ฐ์น˜ํ•  ๋•Œ์—๋งŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?๋ฅผ ์žฌ์ •์˜ํ•˜์—ฌ์„œ ์‚ฌ์šฉ ๋‚ด๋ถ€ ์†์„ฑ์œผ๋กœ ์ขŒํ‘œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ์ง์„ ๊ณผ ํ„ฐ์น˜ํ•˜๋Š” ๋ถ€๋ถ„์˜ ๊ฑฐ๋ฆฌ๋ฅผ ์ธก์ •

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let line = line(start: startPoint, end: endPoint)
    let distance = distance(to: line, from: point)
    if distance <= 10 {
        return self
    }
    return nil
}

๋˜ํ•œ, superView๋ฅผ ๋„˜์–ด๊ฐ€์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ outOfSuperview๋กœ ์ฐธ/๊ฑฐ์ง“์„ ํ™•์ธ ์ œ์Šค์ฒ˜๊ฐ€ ๋„˜์–ด๊ฐ€๋”๋ผ๋„ ์ด๋™ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ๋ฐฉ์ง€

private func outOfSuperview(through point: CGPoint) -> Bool {
    guard let superview else {
        return true
    }

    let limitX = superview.bounds.maxX
    let limitY = superview.bounds.maxY

    guard startPoint.x + point.x > 0,
          startPoint.x + point.x < limitX,
          endPoint.x + point.x > 0,
          endPoint.x + point.x < limitX,
          startPoint.y + point.y > 0,
          startPoint.y + point.y < limitY,
          endPoint.y + point.y > 0,
          endPoint.y + point.y < limitY else {
        return true
    }

    return false
}

๋ณ€๊ฒฝ๋œ UI

SwiftUI๋กœ ์—ฐ๊ฒฐ๋œ ์ดฌ์˜ ์ˆ˜๋™/์ž๋™ ๋ชจ๋“œ ๋ฐ ์ดฌ์˜์Œ ์œ /๋ฌด๋ฅผ ์„ ํƒํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•œ View ์ด๋ฏธ์ง€๋ฅผ ํƒญํ•˜๋ฉด ์˜ต์…˜์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ถ”๊ฐ€

private lazy var abilitiesController = UIHostingController(rootView: abilitiesView)

view.addSubview(abilitiesController.view)
abilitiesController.view.backgroundColor = .clear

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages