๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“š Diary

macOS ๊ทธ๋ฆผํŒ ๋งŒ๋“ค๊ธฐ + @Swift UI ์ƒํƒœ ๊ด€๋ฆฌ, redo & undo

by dev.py 2025. 4. 14.

๊ฐ€๋” ๋””์Šค์ฝ”๋“œ๋‚˜ ํ™”์ƒํšŒ์˜๋ฅผ ํ•  ๋•Œ ์™„์ „ ๊ฐ„๋‹จํ•œ ๊ทธ๋ฆผํŒ์ด ํ•„์š”ํ•œ๋ฐ, ๋งฅ๋ถ์—์„  ๊ธฐ๋ณธ ๊ทธ๋ฆผํŒ ํ”„๋กœ๊ทธ๋žจ์ด ์•ˆ๋ณด์—ฌ ๋งŒ๋“ค์—ˆ๋‹ค.

 

1. ์ฃผ์š” ๊ธฐ๋Šฅ

  • ๊ธฐ๋ณธ ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ 8๊ฐœ + ์ตœ๊ทผ ์‚ฌ์šฉ ์ƒ‰์ƒ 8๊ฐœ
  • ์ž์œ ๋กญ๊ฒŒ ์„  ๊ทธ๋ฆฌ๊ธฐ
  • Undo / Redo (โŒ˜Z / โ‡งโŒ˜Z)
  • ColorPicker๋กœ ์‚ฌ์šฉ์ž ์ง€์ • ์ƒ‰์ƒ ์ถ”๊ฐ€
  • Clear ๋ฒ„ํŠผ์œผ๋กœ ์ „์ฒด ์ดˆ๊ธฐํ™”

๊ทธ๋ฆผํŒ

2. ๊ตฌ์กฐ

Paint/

โ”œโ”€โ”€ PaintApp.swift                     # ์•ฑ ์‹œ์ž‘์ 

โ”œโ”€โ”€ ContentView.swift               # ๋ฉ”์ธ ๋ทฐ

โ”œโ”€โ”€ CanvasView.swift                # ๊ทธ๋ฆผ ๊ทธ๋ฆฌ๋Š” ๋กœ์ง์ด ๋“ค์–ด์žˆ๋Š” ํ•˜์œ„ ๋ทฐ

โ”œโ”€โ”€ DrawingPath.swift               # ์„ ์„ ๊ตฌ์„ฑํ•˜๋Š” ๋ชจ๋ธ

โ”œโ”€โ”€ DrawingViewModel.swift    # ์ „์ฒด ์„  ๋ชฉ๋ก ๋ฐ undo/redo ๊ด€๋ฆฌํ•˜๋Š” ViewModel

 

SwiftUI ์ƒํƒœ ์–ด๋…ธํ…Œ์ด์…˜

์–ด๋…ธํ…Œ์ด์…˜ ์šฉ๋„ ์œ„์น˜ ์‚ฌ์šฉ ์˜ˆ์‹œ
@State ๋‹จ์ˆœํ•œ ๊ฐ’ View ๋‚ด๋ถ€ ์„ ํƒ๋œ ์ƒ‰์ƒ, ์ตœ๊ทผ ์ƒ‰์ƒ
@StateObject ViewModel์„ ์ง์ ‘ ์ƒ์„ฑํ•  ๋•Œ ์ตœ์ƒ์œ„ View DrawingViewModel
@ObservedObject ์ƒ์œ„์—์„œ ๋งŒ๋“  ViewModel์„ ํ•˜์œ„์—์„œ ๊ด€์ฐฐํ•  ๋•Œ ์ž์‹ View CanvasView์—์„œ ContentView์—์„œ ์„ ์–ธ๋œ DrawingViewModel์„ ์ „๋‹ฌ๋ฐ›์Œ

 

 

 

1. @State: ๋‹จ์ˆœํ•œ ๊ฐ’ ๊ด€๋ฆฌ

์‚ฌ์šฉ์ž๊ฐ€ ํด๋ฆญํ•œ ์ƒ‰์ƒ์ด๋‚˜ ์ตœ๊ทผ ์ƒ‰์ƒ ๋ชฉ๋ก์€ ๋‹จ์ˆœํ•œ ๊ฐ’์œผ๋กœ ๋ทฐ ๋‚ด๋ถ€์—์„œ ๊ด€๋ฆฌ

@State private var selectedColor: Color = .black
@State private var recentColors: [Color] = []

 

 

2. @StateObject: ์ƒํƒœ ๊ฐ์ฒด(ViewModel) ์ƒ์„ฑ ๋ฐ ์†Œ์œ 

๊ฒฝ๋กœ(๊ทธ๋ ค์ง„ ์„ ) , redo stack, clear, undo/redo ๋กœ์ง ๋“ฑ ์ƒํƒœ ๊ด€๋ฆฌ

@StateObject private var viewModel = DrawingViewModel()

 

 

3. @ObservedObject: ์ž์‹ ๋ทฐ์—์„œ ViewModel ์ฐธ์กฐ

์ƒ์œ„ View์—์„œ ์ƒ์„ฑํ•œ ViewModel์„ CanvasView๊ฐ€ ๊ด€์ฐฐํ•˜๋ฉฐ ๋ Œ๋”๋ง

# ContentView - ์ƒ์œ„ ๋ทฐ

struct ContentView: View {
    @Environment(\.undoManager) private var undoManager
    @StateObject private var viewModel = DrawingViewModel()
    @State private var selectedColor: Color = .black
    @State private var recentColors: [Color] = []
    
    ...
    
    CanvasView(
            paths: $viewModel.paths,
            viewModel: viewModel,
            selectedColor: selectedColor,
            lineWidth: 2,
            onDrawingEnded: { newPath in
                viewModel.addPath(newPath, using: undoManager)
                updateRecentColors(selectedColor)
            }
        )

}
# CanvasView - ํ•˜์œ„ ๋ทฐ

struct CanvasView: View {
    @Binding var paths: [DrawingPath]
    @ObservedObject var viewModel: DrawingViewModel
    let selectedColor: Color
    let lineWidth: CGFloat
    let onDrawingEnded: (DrawingPath) -> Void
	
    ...
    
    }

 

 

 

 

 

UndoManager ์—ฐ๋™(โŒ˜Z, โ‡งโŒ˜Z)

@Environment(\.undoManager) private var undoManager # ์ „์—ญ ๋ณ€์ˆ˜ ์„ ์–ธ

# ContentView.swift - ์ปค๋งจ๋“œ ๋“ฑ๋ก

        .onCommand(#selector(UndoManager.undo)) {
            viewModel.undo(using: undoManager)
        }
        .onCommand(#selector(UndoManager.redo)) {
            viewModel.redo(using: undoManager)
        }


# DrawingViewModel.swift - ์‹ค์ œ ๋™์ž‘ ํ•จ์ˆ˜

    func undo(using undoManager: UndoManager?) {
        guard let last = paths.popLast() else { return }
        redoStack.append(last)
        undoManager?.registerUndo(withTarget: self) { target in
            target.redo(using: undoManager)
        }
    }

    func redo(using undoManager: UndoManager?) {
        guard let last = redoStack.popLast() else { return }
        paths.append(last)
        undoManager?.registerUndo(withTarget: self) { target in
            target.undo(using: undoManager)
        }
    }

 

 

https://github.com/sungchan1/swift-paint

 

GitHub - sungchan1/swift-paint: swift-paint

swift-paint. Contribute to sungchan1/swift-paint development by creating an account on GitHub.

github.com