From 52a4fb8f2ec2fcb2c072a2c6fca68583ce6e533b Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Wed, 16 Mar 2022 20:05:31 +0900 Subject: [PATCH 01/13] fix: Fix test cases --- DrawingApp/DrawingApp/Model/Shape.swift | 8 +++++ ...ngleShapable.swift => ShapeViewable.swift} | 4 +-- .../DrawingAppTests/DrawingAppTests.swift | 30 +++++++++++++------ 3 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 DrawingApp/DrawingApp/Model/Shape.swift rename DrawingApp/DrawingApp/View/Components/{RectangleShapable.swift => ShapeViewable.swift} (97%) diff --git a/DrawingApp/DrawingApp/Model/Shape.swift b/DrawingApp/DrawingApp/Model/Shape.swift new file mode 100644 index 00000000..4ab14bac --- /dev/null +++ b/DrawingApp/DrawingApp/Model/Shape.swift @@ -0,0 +1,8 @@ +// +// Shape.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/16. +// + +import Foundation diff --git a/DrawingApp/DrawingApp/View/Components/RectangleShapable.swift b/DrawingApp/DrawingApp/View/Components/ShapeViewable.swift similarity index 97% rename from DrawingApp/DrawingApp/View/Components/RectangleShapable.swift rename to DrawingApp/DrawingApp/View/Components/ShapeViewable.swift index 77fd934b..af3a24d5 100644 --- a/DrawingApp/DrawingApp/View/Components/RectangleShapable.swift +++ b/DrawingApp/DrawingApp/View/Components/ShapeViewable.swift @@ -7,7 +7,7 @@ import UIKit -protocol RectangleShapable: UIView { +protocol ShapeViewable: UIView { func setBorder(width: Int, radius: Int, color: UIColor?) func removeBorder() func setBackgroundColor(color: Color, alpha: Alpha) @@ -18,7 +18,7 @@ protocol RectangleShapable: UIView { func animateScale(_ scale: CGFloat, duration: Double, delay: Double) } -extension RectangleShapable { +extension ShapeViewable { func setBorder(width: Int, radius: Int = 0, color: UIColor?) { self.layer.cornerCurve = .continuous self.layer.cornerRadius = CGFloat(radius) diff --git a/DrawingApp/DrawingAppTests/DrawingAppTests.swift b/DrawingApp/DrawingAppTests/DrawingAppTests.swift index b74c34fc..d74113a0 100644 --- a/DrawingApp/DrawingAppTests/DrawingAppTests.swift +++ b/DrawingApp/DrawingAppTests/DrawingAppTests.swift @@ -8,6 +8,7 @@ import XCTest class DrawingAppTests: XCTestCase { + // MARK: - Size func testSize() { var size = Size(width: 30, height: 30) XCTAssertEqual(size.width, 30) @@ -18,6 +19,7 @@ class DrawingAppTests: XCTestCase { XCTAssertEqual(size.height, 30.5) } + // MARK: - Point func testPoint() { var point = Point(x: 0, y: 10) XCTAssertEqual(point.x, 0) @@ -28,8 +30,15 @@ class DrawingAppTests: XCTestCase { XCTAssertEqual(point.y, 27.5) } + func testPointConversion() { + let point = Point(x: 10, y: 10) + let result = point.convert(using: CGPoint.self) + XCTAssertEqual(point.x, result.x) + } + + // MARK: - Color func testColor() { - var color = Color(red: 0, green: 100, blue: 0) ?? .white + var color = Color(red: 0, green: 100, blue: 0) XCTAssertNotEqual(color, Color.white) XCTAssertEqual(color.red, 0) XCTAssertEqual(color.green, 100) @@ -37,12 +46,11 @@ class DrawingAppTests: XCTestCase { color = .red XCTAssertEqual(color.red, 255.0) - - XCTAssertNil(Color(red: 260, green: 0, blue: 10)) } + // MARK: - Alpha func testAlpha() { - var alpha = Alpha(rawValue: 5) + var alpha = Alpha(rawValue: 0.5) XCTAssertNotNil(alpha) XCTAssertEqual(alpha, .five) @@ -50,6 +58,7 @@ class DrawingAppTests: XCTestCase { XCTAssertNil(alpha) } + // MARK: - Rectangle func testRectangle() { let rect = RectangleFactory.makeRectangle() XCTAssertEqual(rect.backgroundColor, .white) @@ -70,18 +79,21 @@ class DrawingAppTests: XCTestCase { XCTAssertNotEqual(rect.id, secondRect.id) } + // MARK: - Plane func testPlane() { - var plane = Plane() - XCTAssertEqual(plane.countItems, 0) + let plane = Plane() + XCTAssertEqual(plane.count, 0) XCTAssertNil(plane.findItemBy(point: Point(x: 1, y: 1))) let rect = RectangleFactory.makeRectangle(x: 100, y: 100, width: 50, height: 50) plane.append(item: rect) - XCTAssertEqual(plane.countItems, 1) - XCTAssertEqual(plane[id: rect.id], rect) + XCTAssertEqual(plane.count, 1) + XCTAssertNotNil(plane[id: rect.id]) + XCTAssertEqual(plane[id: rect.id] as! Rectangle, rect) let secondRect = RectangleFactory.makeRectangle(x: 0, y: 0, width: 50, height: 50) plane.append(item: secondRect) - XCTAssertEqual(plane.findItemBy(point: Point(x: 30, y: 10)), secondRect) + XCTAssertNotNil(plane.findItemBy(point: Point(x: 30, y: 10))) + XCTAssertEqual(plane.findItemBy(point: Point(x: 30, y: 10)) as! Rectangle, secondRect) } } From a0acaa783a3ac52ad0152e87af383eefc270c83c Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Wed, 16 Mar 2022 20:06:50 +0900 Subject: [PATCH 02/13] refactor: Refactor ColorFactory --- DrawingApp/DrawingApp/Model/Color.swift | 2 ++ DrawingApp/DrawingApp/Model/Shape.swift | 8 -------- DrawingApp/DrawingApp/Utility/ColorFactory.swift | 7 ++++--- .../Utility/Extensions/CGPoint+Extensions.swift | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) delete mode 100644 DrawingApp/DrawingApp/Model/Shape.swift diff --git a/DrawingApp/DrawingApp/Model/Color.swift b/DrawingApp/DrawingApp/Model/Color.swift index eac52e01..8e5f3dd3 100644 --- a/DrawingApp/DrawingApp/Model/Color.swift +++ b/DrawingApp/DrawingApp/Model/Color.swift @@ -8,6 +8,8 @@ import Foundation struct Color { + static let range = UInt8.min...UInt8.max + static let black = Color(red: 0, green: 0, blue: 0) static let red = Color(red: 255, green: 0, blue: 0) static let green = Color(red: 0, green: 255, blue: 0) diff --git a/DrawingApp/DrawingApp/Model/Shape.swift b/DrawingApp/DrawingApp/Model/Shape.swift deleted file mode 100644 index 4ab14bac..00000000 --- a/DrawingApp/DrawingApp/Model/Shape.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// Shape.swift -// DrawingApp -// -// Created by 송태환 on 2022/03/16. -// - -import Foundation diff --git a/DrawingApp/DrawingApp/Utility/ColorFactory.swift b/DrawingApp/DrawingApp/Utility/ColorFactory.swift index 3be3302f..d9a91aad 100644 --- a/DrawingApp/DrawingApp/Utility/ColorFactory.swift +++ b/DrawingApp/DrawingApp/Utility/ColorFactory.swift @@ -11,9 +11,10 @@ enum ColorFactory: TypeBuilder { typealias T = Color static func makeTypeRandomly() -> Color { - let red = UInt8.random(in: UInt8.min...UInt8.max) - let green = UInt8.random(in: UInt8.min...UInt8.max) - let blue = UInt8.random(in: UInt8.min...UInt8.max) + let range = Color.range + let red = UInt8.random(in: range) + let green = UInt8.random(in: range) + let blue = UInt8.random(in: range) return Color(red: red, green: green, blue: blue) } diff --git a/DrawingApp/DrawingApp/Utility/Extensions/CGPoint+Extensions.swift b/DrawingApp/DrawingApp/Utility/Extensions/CGPoint+Extensions.swift index 635dc5dc..effa1f92 100644 --- a/DrawingApp/DrawingApp/Utility/Extensions/CGPoint+Extensions.swift +++ b/DrawingApp/DrawingApp/Utility/Extensions/CGPoint+Extensions.swift @@ -11,7 +11,7 @@ protocol PointBuildable { init(x: Double, y: Double) } -extension CGPoint { +extension CGPoint: PointBuildable { init(with point: Point) { self.init(x: point.x, y: point.y) } From 13f4b28dd73241328d29f90da07abe1ef20888e9 Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Wed, 16 Mar 2022 20:08:58 +0900 Subject: [PATCH 03/13] refactor: Define protocols for RectangleView and ImageRectangleView to improve polymorphism --- .../Utility/RectangleViewFactory.swift | 3 +- .../View/Components/ImageRectangleView.swift | 22 ++++++++--- .../View/Components/RectangleView.swift | 38 +++++++++++++++++-- .../View/Components/ShapeViewable.swift | 29 +------------- 4 files changed, 54 insertions(+), 38 deletions(-) diff --git a/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift b/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift index e67b4a1c..1d664087 100644 --- a/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift +++ b/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift @@ -9,9 +9,10 @@ import UIKit enum RectangleViewFactory { - static func makeView(ofClass Class: RectangleShapable.Type, with data: Rectangle) -> RectangleShapable? { + static func makeView(ofClass Class: ShapeViewable.Type, with data: Shapable) -> ShapeViewable? { switch Class { case is RectangleView.Type: + guard let data = data as? Rectangle else { return nil } let rectangleView = RectangleView(frame: data.convert(using: CGRect.self)) rectangleView.setBackgroundColor(color: data.backgroundColor, alpha: data.alpha) diff --git a/DrawingApp/DrawingApp/View/Components/ImageRectangleView.swift b/DrawingApp/DrawingApp/View/Components/ImageRectangleView.swift index 009d0bcb..1197dc54 100644 --- a/DrawingApp/DrawingApp/View/Components/ImageRectangleView.swift +++ b/DrawingApp/DrawingApp/View/Components/ImageRectangleView.swift @@ -7,7 +7,11 @@ import UIKit -class ImageRectangleView: UIView, RectangleShapable { +protocol ImageViewable { + func setImage(_ image: UIImage) +} + +class ImageRectangleView: UIView { private let imageView = UIImageView() override init(frame: CGRect) { @@ -24,12 +28,18 @@ class ImageRectangleView: UIView, RectangleShapable { self.imageView.frame = self.bounds self.addSubview(imageView) } - +} + +// MARK: - ShapeViewable +extension ImageRectangleView: ShapeViewable { + func setAlpha(_ alpha: Alpha) { + self.alpha = alpha.convert(using: CGFloat.self) + } +} + +// MARK: - ImageViewable Protocol +extension ImageRectangleView: ImageViewable { func setImage(_ image: UIImage) { self.imageView.image = image } - - func setBackgroundColor(color: Color, alpha: Alpha) { - return - } } diff --git a/DrawingApp/DrawingApp/View/Components/RectangleView.swift b/DrawingApp/DrawingApp/View/Components/RectangleView.swift index a7dba691..37ba9ad6 100644 --- a/DrawingApp/DrawingApp/View/Components/RectangleView.swift +++ b/DrawingApp/DrawingApp/View/Components/RectangleView.swift @@ -7,7 +7,14 @@ import UIKit -class RectangleView: UIView, RectangleShapable { +protocol BackgroundColorable { + func setBackgroundColor(color: Color, alpha: Alpha) + func setBackgroundColor(with color: Color) + func setBackgroundColor(with alpha: CGFloat) + func setBackgroundColor(with alpha: Alpha) +} + +class RectangleView: UIView { // MARK: - Initialisers override init(frame: CGRect) { super.init(frame: frame) @@ -18,11 +25,36 @@ class RectangleView: UIView, RectangleShapable { } convenience init(with rectangle: Rectangle) { - self.init(frame: rectangle.convert(using: CGRect.self)) + self.init(frame: CGRect(with: rectangle)) self.setBackgroundColor(color: rectangle.backgroundColor, alpha: rectangle.alpha) } - +} + +// MARK: - ShapeViewable Protocol +extension RectangleView: ShapeViewable { func setAlpha(_ alpha: Alpha) { self.setBackgroundColor(with: alpha) } } + +// MARK: - RectangleViewable +extension RectangleView: BackgroundColorable { + func setBackgroundColor(color: Color, alpha: Alpha) { + self.backgroundColor = UIColor(with: color, alpha: alpha) + } + + func setBackgroundColor(with color: Color) { + self.backgroundColor = UIColor(with: color) + } + + func setBackgroundColor(with alpha: CGFloat) { + let color = self.backgroundColor?.withAlphaComponent(alpha) + self.backgroundColor = color + } + + func setBackgroundColor(with alpha: Alpha) { + let alphaValue = alpha.convert(using: CGFloat.self) + let color = self.backgroundColor?.withAlphaComponent(alphaValue) + self.backgroundColor = color + } +} diff --git a/DrawingApp/DrawingApp/View/Components/ShapeViewable.swift b/DrawingApp/DrawingApp/View/Components/ShapeViewable.swift index af3a24d5..01510720 100644 --- a/DrawingApp/DrawingApp/View/Components/ShapeViewable.swift +++ b/DrawingApp/DrawingApp/View/Components/ShapeViewable.swift @@ -1,5 +1,5 @@ // -// BaseView.swift +// ShapeViewable.swift // DrawingApp // // Created by 송태환 on 2022/03/16. @@ -10,10 +10,6 @@ import UIKit protocol ShapeViewable: UIView { func setBorder(width: Int, radius: Int, color: UIColor?) func removeBorder() - func setBackgroundColor(color: Color, alpha: Alpha) - func setBackgroundColor(with color: Color) - func setBackgroundColor(with alpha: CGFloat) - func setBackgroundColor(with alpha: Alpha) func setAlpha(_ alpha: Alpha) func animateScale(_ scale: CGFloat, duration: Double, delay: Double) } @@ -33,29 +29,6 @@ extension ShapeViewable { self.layer.borderColor = .none } - func setBackgroundColor(color: Color, alpha: Alpha) { - self.backgroundColor = UIColor(with: color, alpha: alpha) - } - - func setBackgroundColor(with color: Color) { - self.backgroundColor = UIColor(with: color) - } - - func setBackgroundColor(with alpha: CGFloat) { - let color = self.backgroundColor?.withAlphaComponent(alpha) - self.backgroundColor = color - } - - func setBackgroundColor(with alpha: Alpha) { - let alphaValue = alpha.convert(using: CGFloat.self) - let color = self.backgroundColor?.withAlphaComponent(alphaValue) - self.backgroundColor = color - } - - func setAlpha(_ alpha: Alpha) { - self.alpha = alpha.convert(using: CGFloat.self) - } - func animateScale(_ scale: CGFloat, duration: Double, delay: Double) { let originCenter = self.center let originSize = self.frame.size From 7ef57a43bfb4213e6db9f07762f06a7926753dcd Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Wed, 16 Mar 2022 20:10:44 +0900 Subject: [PATCH 04/13] build: Set test coverage visible on Report Instpector --- .../xcshareddata/xcschemes/DrawingApp.xcscheme | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/DrawingApp/DrawingApp.xcodeproj/xcshareddata/xcschemes/DrawingApp.xcscheme b/DrawingApp/DrawingApp.xcodeproj/xcshareddata/xcschemes/DrawingApp.xcscheme index 36ec08b4..09737d06 100644 --- a/DrawingApp/DrawingApp.xcodeproj/xcshareddata/xcschemes/DrawingApp.xcscheme +++ b/DrawingApp/DrawingApp.xcodeproj/xcshareddata/xcschemes/DrawingApp.xcscheme @@ -26,7 +26,18 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES" + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + From 432834e25fe2b28fb609ff0beb32030a3777c292 Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Wed, 16 Mar 2022 20:12:03 +0900 Subject: [PATCH 05/13] refactor: Define protocols for Rectangle and ImageRectangle to improve polymorphism Also apply changes from model to ViewController --- .../DrawingApp.xcodeproj/project.pbxproj | 60 +++++++++++++++++-- .../Controller/ViewController.swift | 24 +++++--- .../DrawingApp/Model/ImageRectangle.swift | 7 ++- DrawingApp/DrawingApp/Model/Rectangle.swift | 7 ++- DrawingApp/DrawingApp/Model/Shapable.swift | 8 +-- 5 files changed, 83 insertions(+), 23 deletions(-) diff --git a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj index 338629d6..8f78ba30 100644 --- a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj +++ b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj @@ -11,7 +11,31 @@ E41EE3AF27D6EAA7007C7595 /* PlaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */; }; E41EE3B127D6F478007C7595 /* ControlPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3B027D6F478007C7595 /* ControlPanelView.swift */; }; E435F06527E173F7005AD99C /* RectangleViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06427E173F7005AD99C /* RectangleViewFactory.swift */; }; - E435F06727E17581005AD99C /* RectangleShapable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06627E17581005AD99C /* RectangleShapable.swift */; }; + E435F06727E17581005AD99C /* ShapeViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06627E17581005AD99C /* ShapeViewable.swift */; }; + E435F06B27E1CA4A005AD99C /* Notification.Name+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E7E27D86CE700E24DE8 /* Notification.Name+Extensions.swift */; }; + E435F06C27E1CA4A005AD99C /* Hashable+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */; }; + E435F06D27E1CA4A005AD99C /* Equatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */; }; + E435F06E27E1CA4A005AD99C /* Double+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E527D61E2F007B0E3F /* Double+Random.swift */; }; + E435F06F27E1CA4A005AD99C /* Float+ToFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57227DF3E3E005075B6 /* Float+ToFix.swift */; }; + E435F07027E1CA4A005AD99C /* String+Alphanumeric.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E327D61E11007B0E3F /* String+Alphanumeric.swift */; }; + E435F07127E1CA4A005AD99C /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E827D61E93007B0E3F /* UIColor+Extensions.swift */; }; + E435F07227E1CA4A005AD99C /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8427D8843900E24DE8 /* CGRect+Extensions.swift */; }; + E435F07327E1CA4A005AD99C /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8027D8836400E24DE8 /* CGPoint+Extensions.swift */; }; + E435F07427E1CA4A005AD99C /* CGSize+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8227D883DD00E24DE8 /* CGSize+Extensions.swift */; }; + E435F07527E1CA4F005AD99C /* RectangleViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06427E173F7005AD99C /* RectangleViewFactory.swift */; }; + E435F07627E1CA4F005AD99C /* RectangleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */; }; + E435F07727E1CA4F005AD99C /* PointFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B027D1E70500B90B24 /* PointFactory.swift */; }; + E435F07827E1CA4F005AD99C /* SizeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637AC27D1E6D300B90B24 /* SizeFactory.swift */; }; + E435F07927E1CA4F005AD99C /* ColorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637AE27D1E6EE00B90B24 /* ColorFactory.swift */; }; + E435F07A27E1CA4F005AD99C /* TypeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B427D1EC4F00B90B24 /* TypeBuilder.swift */; }; + E435F07B27E1CA56005AD99C /* ImageRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57427E079EF005075B6 /* ImageRectangle.swift */; }; + E435F07C27E1CA70005AD99C /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109EE27D6204A007B0E3F /* RoundedButton.swift */; }; + E435F07D27E1CA70005AD99C /* SimpleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E49679E727D71AF900A9BA27 /* SimpleLabel.swift */; }; + E435F07E27E1CA70005AD99C /* ShapeViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06627E17581005AD99C /* ShapeViewable.swift */; }; + E435F07F27E1CA70005AD99C /* RectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4002E9527D5B00300F14AFD /* RectangleView.swift */; }; + E435F08027E1CA70005AD99C /* ImageRectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57627E07F28005075B6 /* ImageRectangleView.swift */; }; + E435F08127E1CA7B005AD99C /* PlaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */; }; + E435F08227E1CA7B005AD99C /* ControlPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3B027D6F478007C7595 /* ControlPanelView.swift */; }; E4630C2727CDD2BC00644B70 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4630C2627CDD2BC00644B70 /* SceneDelegate.swift */; }; E4641B4927D09C0000815795 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4827D09C0000815795 /* Plane.swift */; }; E4641B4A27D09E1800815795 /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFAF27CF42560096810A /* CGFloat+Extensions.swift */; }; @@ -75,7 +99,7 @@ E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneView.swift; sourceTree = ""; }; E41EE3B027D6F478007C7595 /* ControlPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlPanelView.swift; sourceTree = ""; }; E435F06427E173F7005AD99C /* RectangleViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleViewFactory.swift; sourceTree = ""; }; - E435F06627E17581005AD99C /* RectangleShapable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleShapable.swift; sourceTree = ""; }; + E435F06627E17581005AD99C /* ShapeViewable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeViewable.swift; sourceTree = ""; }; E4630C2627CDD2BC00644B70 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E4641B4827D09C0000815795 /* Plane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plane.swift; sourceTree = ""; }; E4641B4C27D0A00F00815795 /* Shapable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shapable.swift; sourceTree = ""; }; @@ -140,7 +164,7 @@ children = ( E48109EE27D6204A007B0E3F /* RoundedButton.swift */, E49679E727D71AF900A9BA27 /* SimpleLabel.swift */, - E435F06627E17581005AD99C /* RectangleShapable.swift */, + E435F06627E17581005AD99C /* ShapeViewable.swift */, E4002E9527D5B00300F14AFD /* RectangleView.swift */, E4D6B57627E07F28005075B6 /* ImageRectangleView.swift */, ); @@ -151,13 +175,13 @@ isa = PBXGroup; children = ( E4641B4C27D0A00F00815795 /* Shapable.swift */, + E4E8082E27CDF0C100F4C062 /* Rectangle.swift */, + E4D6B57427E079EF005075B6 /* ImageRectangle.swift */, E4D3DFA127CEFE490096810A /* Point.swift */, E4D3DFA327CEFE7C0096810A /* Size.swift */, E4D3DFA527CEFE7F0096810A /* Color.swift */, E4D3DFA727CEFEB90096810A /* Alpha.swift */, E4641B4827D09C0000815795 /* Plane.swift */, - E4E8082E27CDF0C100F4C062 /* Rectangle.swift */, - E4D6B57427E079EF005075B6 /* ImageRectangle.swift */, ); path = Model; sourceTree = ""; @@ -378,7 +402,7 @@ E48109E927D61E93007B0E3F /* UIColor+Extensions.swift in Sources */, E4D3DFA827CEFEB90096810A /* Alpha.swift in Sources */, E49679E827D71AF900A9BA27 /* SimpleLabel.swift in Sources */, - E435F06727E17581005AD99C /* RectangleShapable.swift in Sources */, + E435F06727E17581005AD99C /* ShapeViewable.swift in Sources */, E48109E627D61E2F007B0E3F /* Double+Random.swift in Sources */, E4E8083127CE062600F4C062 /* ShapeFactoryCluster.swift in Sources */, E481266627CCF8E000A3FFF6 /* ViewController.swift in Sources */, @@ -409,6 +433,30 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E435F08127E1CA7B005AD99C /* PlaneView.swift in Sources */, + E435F08227E1CA7B005AD99C /* ControlPanelView.swift in Sources */, + E435F07C27E1CA70005AD99C /* RoundedButton.swift in Sources */, + E435F07D27E1CA70005AD99C /* SimpleLabel.swift in Sources */, + E435F07E27E1CA70005AD99C /* ShapeViewable.swift in Sources */, + E435F07F27E1CA70005AD99C /* RectangleView.swift in Sources */, + E435F08027E1CA70005AD99C /* ImageRectangleView.swift in Sources */, + E435F07B27E1CA56005AD99C /* ImageRectangle.swift in Sources */, + E435F07527E1CA4F005AD99C /* RectangleViewFactory.swift in Sources */, + E435F07627E1CA4F005AD99C /* RectangleFactory.swift in Sources */, + E435F07727E1CA4F005AD99C /* PointFactory.swift in Sources */, + E435F07827E1CA4F005AD99C /* SizeFactory.swift in Sources */, + E435F07927E1CA4F005AD99C /* ColorFactory.swift in Sources */, + E435F07A27E1CA4F005AD99C /* TypeBuilder.swift in Sources */, + E435F06B27E1CA4A005AD99C /* Notification.Name+Extensions.swift in Sources */, + E435F06C27E1CA4A005AD99C /* Hashable+Hash.swift in Sources */, + E435F06D27E1CA4A005AD99C /* Equatable+Extensions.swift in Sources */, + E435F06E27E1CA4A005AD99C /* Double+Random.swift in Sources */, + E435F06F27E1CA4A005AD99C /* Float+ToFix.swift in Sources */, + E435F07027E1CA4A005AD99C /* String+Alphanumeric.swift in Sources */, + E435F07127E1CA4A005AD99C /* UIColor+Extensions.swift in Sources */, + E435F07227E1CA4A005AD99C /* CGRect+Extensions.swift in Sources */, + E435F07327E1CA4A005AD99C /* CGPoint+Extensions.swift in Sources */, + E435F07427E1CA4A005AD99C /* CGSize+Extensions.swift in Sources */, E4641B5227D0A8BA00815795 /* IdentifierFactory.swift in Sources */, E4641B4E27D0A0FE00815795 /* Shapable.swift in Sources */, E4641B4B27D09E1B00815795 /* Plane.swift in Sources */, diff --git a/DrawingApp/DrawingApp/Controller/ViewController.swift b/DrawingApp/DrawingApp/Controller/ViewController.swift index 5b809129..d45b6ed6 100644 --- a/DrawingApp/DrawingApp/Controller/ViewController.swift +++ b/DrawingApp/DrawingApp/Controller/ViewController.swift @@ -15,7 +15,7 @@ class ViewController: UIViewController { // MARK: - Property for Model private let plane = Plane() - private var rectangleMap = [Rectangle: RectangleShapable]() + private var rectangleMap = [AnyHashable: ShapeViewable]() // MARK: - View Life Cycle Methods override func viewDidLoad() { @@ -32,13 +32,13 @@ class ViewController: UIViewController { private func setObservers() { NotificationCenter.default.addObserver(forName: .RectangleModelDidCreated, object: nil, queue: .main, using: { notification in - guard let rectangle = notification.object as? Rectangle else { return } + guard let rectangle = notification.object as? Shapable else { return } self.createRectangleView(ofClass: RectangleView.self, with: rectangle) }) NotificationCenter.default.addObserver(forName: .RectangleModelDidUpdated, object: nil, queue: .main, using: self.rectangleDataDidChanged) NotificationCenter.default.addObserver(forName: .ImageRectangleModelDidCreated, object: nil, queue: .main, using: { notification in - guard let rectangle = notification.object as? Rectangle else { return } + guard let rectangle = notification.object as? Shapable else { return } self.createRectangleView(ofClass: ImageRectangleView.self, with: rectangle) }) NotificationCenter.default.addObserver(forName: .RectangleModelDidUpdated, object: nil, queue: .main, using: self.rectangleDataDidChanged) @@ -76,10 +76,10 @@ extension ViewController: PlaneViewDelegate { // MARK: - ControlPanelView To ViewController extension ViewController: ControlPanelViewDelegate { func controlPanelDidPressColorButton() { - guard let rectangle = self.plane.currentItem else { return } + guard let rectangle = self.plane.currentItem as? RectangleShapable else { return } let color = ColorFactory.makeTypeRandomly() - + rectangle.setBackgroundColor(color) } @@ -108,7 +108,7 @@ extension ViewController { // MARK: - Rectangle Model To ViewController extension ViewController { - private func createRectangleView(ofClass Class: RectangleShapable.Type, with rectangle: Rectangle) { + private func createRectangleView(ofClass Class: ShapeViewable.Type, with rectangle: Shapable) { guard let rectangleView = RectangleViewFactory.makeView(ofClass: Class, with: rectangle) else { return } let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleOnTapRectangleView)) @@ -116,9 +116,14 @@ extension ViewController { rectangleView.addGestureRecognizer(tap) self.planeView.addSubview(rectangleView) - self.rectangleMap.updateValue(rectangleView, forKey: rectangle) rectangleView.animateScale(CGFloat(1.2), duration: 0.15, delay: 0) + + // TODO: Dictionary 키 타입을 프로토콜로 추상화하여 Hashable 하게 변경 + // Shapable 프로토콜은 Hashable 할 수 없음 + guard let rectangle = rectangle as? Rectangle else { return } + + self.rectangleMap.updateValue(rectangleView, forKey: rectangle) } private func rectangleDataDidChanged(_ notification: Notification) { @@ -130,7 +135,7 @@ extension ViewController { } if let color = notification.userInfo?[Rectangle.NotificationKey.color] as? Color { - rectangleView.setBackgroundColor(color: color, alpha: rectangle.alpha) + (rectangleView as? BackgroundColorable)?.setBackgroundColor(color: color, alpha: rectangle.alpha) self.controlPanelView.setColorButtonTitle(title: rectangleView.backgroundColor?.toHexString() ?? "None") } } @@ -145,8 +150,9 @@ extension ViewController { rectangleView.setBorder(width: 2, color: .blue) let hexString = UIColor(with: rectangle.backgroundColor).toHexString() + let isConformed = (rectangle as? ImagePossessable) == nil - self.controlPanelView.setColorButtonControllable(enable: rectangle.isType(of: Rectangle.self)) + self.controlPanelView.setColorButtonControllable(enable: isConformed) self.controlPanelView.setAlphaSliderControllable(enable: true) self.controlPanelView.setColorButtonTitle(title: hexString) self.controlPanelView.setAlphaSliderValue(value: rectangle.alpha) diff --git a/DrawingApp/DrawingApp/Model/ImageRectangle.swift b/DrawingApp/DrawingApp/Model/ImageRectangle.swift index 2cf2d627..c290698f 100644 --- a/DrawingApp/DrawingApp/Model/ImageRectangle.swift +++ b/DrawingApp/DrawingApp/Model/ImageRectangle.swift @@ -7,7 +7,12 @@ import Foundation -class ImageRectangle: Rectangle { +protocol ImagePossessable { + var image: URL? { get } + func setImage(with url: URL) +} + +class ImageRectangle: Rectangle, ImagePossessable { private(set) var image: URL? init(id: String, origin: Point, size: Size, image: URL? = nil) { diff --git a/DrawingApp/DrawingApp/Model/Rectangle.swift b/DrawingApp/DrawingApp/Model/Rectangle.swift index 60410cf2..7865a47b 100644 --- a/DrawingApp/DrawingApp/Model/Rectangle.swift +++ b/DrawingApp/DrawingApp/Model/Rectangle.swift @@ -11,7 +11,12 @@ protocol RectangleBuildable { init(x: Double, y: Double, width: Double, height: Double) } -class Rectangle: Shapable, Notifiable, Hashable { +protocol RectangleShapable { + var backgroundColor: Color { get } + func setBackgroundColor(_ color: Color) +} + +class Rectangle: Shapable, RectangleShapable, Notifiable, Hashable { enum NotificationKey { case alpha case color diff --git a/DrawingApp/DrawingApp/Model/Shapable.swift b/DrawingApp/DrawingApp/Model/Shapable.swift index bf0f32d2..e1a1d35c 100644 --- a/DrawingApp/DrawingApp/Model/Shapable.swift +++ b/DrawingApp/DrawingApp/Model/Shapable.swift @@ -10,24 +10,20 @@ import Foundation protocol Shapable: CustomStringConvertible, AnyObject { var id: String { get } var size: Size { get } + var alpha: Alpha { get } var origin: Point { get } var backgroundColor: Color { get } - var alpha: Alpha { get } func contains(point: Point) -> Bool func setBackgroundColor(_ color: Color) func setAlpha(_ alpha: Alpha) - func isType(of Protocol: Shapable.Type) -> Bool } extension Shapable { func contains(point: Point) -> Bool { let maxX = self.origin.x + self.size.width let maxY = self.origin.y + self.size.height + return self.origin <= point && point < Point(x: maxX, y: maxY) } - - func isType(of Protocol: Shapable.Type) -> Bool { - return type(of: self) == Protocol.self - } } From 522582e2925a27c57fa2dadcad04e44049fbd778 Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Thu, 17 Mar 2022 14:54:14 +0900 Subject: [PATCH 06/13] refactor: set id property of Rectangle model as dictionary key temporarily --- .../DrawingApp/Controller/ViewController.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/DrawingApp/DrawingApp/Controller/ViewController.swift b/DrawingApp/DrawingApp/Controller/ViewController.swift index d45b6ed6..dfd024a4 100644 --- a/DrawingApp/DrawingApp/Controller/ViewController.swift +++ b/DrawingApp/DrawingApp/Controller/ViewController.swift @@ -121,14 +121,13 @@ extension ViewController { // TODO: Dictionary 키 타입을 프로토콜로 추상화하여 Hashable 하게 변경 // Shapable 프로토콜은 Hashable 할 수 없음 - guard let rectangle = rectangle as? Rectangle else { return } - self.rectangleMap.updateValue(rectangleView, forKey: rectangle) + self.rectangleMap.updateValue(rectangleView, forKey: rectangle.id) } private func rectangleDataDidChanged(_ notification: Notification) { - guard let rectangle = self.plane.currentItem as? Rectangle else { return } - guard let rectangleView = self.rectangleMap[rectangle] else { return } + guard let rectangle = self.plane.currentItem else { return } + guard let rectangleView = self.rectangleMap[rectangle.id] else { return } if let alpha = notification.userInfo?[Rectangle.NotificationKey.alpha] as? Alpha { rectangleView.setAlpha(alpha) @@ -144,8 +143,8 @@ extension ViewController { // MARK: - Plane Model to ViewController extension ViewController { private func planeDidSelectItem(_ notification: Notification) { - guard let rectangle = notification.userInfo?[Plane.NotificationKey.select] as? Rectangle else { return } - guard let rectangleView = self.rectangleMap[rectangle] else { return } + guard let rectangle = notification.userInfo?[Plane.NotificationKey.select] as? Shapable else { return } + guard let rectangleView = self.rectangleMap[rectangle.id] else { return } rectangleView.setBorder(width: 2, color: .blue) @@ -159,8 +158,8 @@ extension ViewController { } private func planeDidUnselectItem(_ notification: Notification) { - guard let rectangle = notification.userInfo?[Plane.NotificationKey.unselect] as? Rectangle else { return } - guard let rectangleView = self.rectangleMap[rectangle] else { return } + guard let rectangle = notification.userInfo?[Plane.NotificationKey.unselect] as? Shapable else { return } + guard let rectangleView = self.rectangleMap[rectangle.id] else { return } rectangleView.removeBorder() From 191c437f188fc6b069d374eebefb0c2e8c6da569 Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Thu, 17 Mar 2022 16:55:28 +0900 Subject: [PATCH 07/13] test: Add test cases for Utility and Model --- .../DrawingApp.xcodeproj/project.pbxproj | 139 ++++------ .../xcschemes/DrawingApp.xcscheme | 3 +- DrawingApp/DrawingApp/Model/Size.swift | 2 +- .../Extensions/CGSize+Extensions.swift | 2 +- .../DrawingAppTests/DrawingAppTests.swift | 243 ++++++++++++++++-- 5 files changed, 281 insertions(+), 108 deletions(-) diff --git a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj index 8f78ba30..c89eda6d 100644 --- a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj +++ b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj @@ -12,38 +12,10 @@ E41EE3B127D6F478007C7595 /* ControlPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3B027D6F478007C7595 /* ControlPanelView.swift */; }; E435F06527E173F7005AD99C /* RectangleViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06427E173F7005AD99C /* RectangleViewFactory.swift */; }; E435F06727E17581005AD99C /* ShapeViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06627E17581005AD99C /* ShapeViewable.swift */; }; - E435F06B27E1CA4A005AD99C /* Notification.Name+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E7E27D86CE700E24DE8 /* Notification.Name+Extensions.swift */; }; - E435F06C27E1CA4A005AD99C /* Hashable+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */; }; - E435F06D27E1CA4A005AD99C /* Equatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */; }; - E435F06E27E1CA4A005AD99C /* Double+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E527D61E2F007B0E3F /* Double+Random.swift */; }; - E435F06F27E1CA4A005AD99C /* Float+ToFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57227DF3E3E005075B6 /* Float+ToFix.swift */; }; - E435F07027E1CA4A005AD99C /* String+Alphanumeric.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E327D61E11007B0E3F /* String+Alphanumeric.swift */; }; - E435F07127E1CA4A005AD99C /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E827D61E93007B0E3F /* UIColor+Extensions.swift */; }; - E435F07227E1CA4A005AD99C /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8427D8843900E24DE8 /* CGRect+Extensions.swift */; }; - E435F07327E1CA4A005AD99C /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8027D8836400E24DE8 /* CGPoint+Extensions.swift */; }; - E435F07427E1CA4A005AD99C /* CGSize+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8227D883DD00E24DE8 /* CGSize+Extensions.swift */; }; - E435F07527E1CA4F005AD99C /* RectangleViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06427E173F7005AD99C /* RectangleViewFactory.swift */; }; - E435F07627E1CA4F005AD99C /* RectangleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */; }; - E435F07727E1CA4F005AD99C /* PointFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B027D1E70500B90B24 /* PointFactory.swift */; }; - E435F07827E1CA4F005AD99C /* SizeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637AC27D1E6D300B90B24 /* SizeFactory.swift */; }; - E435F07927E1CA4F005AD99C /* ColorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637AE27D1E6EE00B90B24 /* ColorFactory.swift */; }; - E435F07A27E1CA4F005AD99C /* TypeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B427D1EC4F00B90B24 /* TypeBuilder.swift */; }; - E435F07B27E1CA56005AD99C /* ImageRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57427E079EF005075B6 /* ImageRectangle.swift */; }; - E435F07C27E1CA70005AD99C /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109EE27D6204A007B0E3F /* RoundedButton.swift */; }; - E435F07D27E1CA70005AD99C /* SimpleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E49679E727D71AF900A9BA27 /* SimpleLabel.swift */; }; - E435F07E27E1CA70005AD99C /* ShapeViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06627E17581005AD99C /* ShapeViewable.swift */; }; - E435F07F27E1CA70005AD99C /* RectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4002E9527D5B00300F14AFD /* RectangleView.swift */; }; - E435F08027E1CA70005AD99C /* ImageRectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57627E07F28005075B6 /* ImageRectangleView.swift */; }; - E435F08127E1CA7B005AD99C /* PlaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */; }; - E435F08227E1CA7B005AD99C /* ControlPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3B027D6F478007C7595 /* ControlPanelView.swift */; }; E4630C2727CDD2BC00644B70 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4630C2627CDD2BC00644B70 /* SceneDelegate.swift */; }; E4641B4927D09C0000815795 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4827D09C0000815795 /* Plane.swift */; }; - E4641B4A27D09E1800815795 /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFAF27CF42560096810A /* CGFloat+Extensions.swift */; }; - E4641B4B27D09E1B00815795 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4827D09C0000815795 /* Plane.swift */; }; E4641B4D27D0A00F00815795 /* Shapable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4C27D0A00F00815795 /* Shapable.swift */; }; - E4641B4E27D0A0FE00815795 /* Shapable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4C27D0A00F00815795 /* Shapable.swift */; }; E4641B5027D0A80200815795 /* IdentifierFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4F27D0A80200815795 /* IdentifierFactory.swift */; }; - E4641B5227D0A8BA00815795 /* IdentifierFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4F27D0A80200815795 /* IdentifierFactory.swift */; }; E48109E427D61E11007B0E3F /* String+Alphanumeric.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E327D61E11007B0E3F /* String+Alphanumeric.swift */; }; E48109E627D61E2F007B0E3F /* Double+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E527D61E2F007B0E3F /* Double+Random.swift */; }; E48109E927D61E93007B0E3F /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E827D61E93007B0E3F /* UIColor+Extensions.swift */; }; @@ -54,16 +26,38 @@ E481266B27CCF8E100A3FFF6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E481266A27CCF8E100A3FFF6 /* Assets.xcassets */; }; E481266E27CCF8E100A3FFF6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E481266C27CCF8E100A3FFF6 /* LaunchScreen.storyboard */; }; E49679E827D71AF900A9BA27 /* SimpleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E49679E727D71AF900A9BA27 /* SimpleLabel.swift */; }; + E49F7CC427E30ED800BB19A2 /* Notification.Name+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E7E27D86CE700E24DE8 /* Notification.Name+Extensions.swift */; }; + E49F7CC527E30ED800BB19A2 /* Hashable+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */; }; + E49F7CC627E30ED800BB19A2 /* Equatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */; }; + E49F7CC727E30ED800BB19A2 /* Double+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E527D61E2F007B0E3F /* Double+Random.swift */; }; + E49F7CC827E30ED800BB19A2 /* Float+ToFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57227DF3E3E005075B6 /* Float+ToFix.swift */; }; + E49F7CC927E30ED800BB19A2 /* String+Alphanumeric.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E327D61E11007B0E3F /* String+Alphanumeric.swift */; }; + E49F7CCA27E30ED800BB19A2 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48109E827D61E93007B0E3F /* UIColor+Extensions.swift */; }; + E49F7CCB27E30ED800BB19A2 /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFAF27CF42560096810A /* CGFloat+Extensions.swift */; }; + E49F7CCC27E30ED800BB19A2 /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8427D8843900E24DE8 /* CGRect+Extensions.swift */; }; + E49F7CCD27E30ED800BB19A2 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8027D8836400E24DE8 /* CGPoint+Extensions.swift */; }; + E49F7CCE27E30ED800BB19A2 /* CGSize+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8227D883DD00E24DE8 /* CGSize+Extensions.swift */; }; + E49F7CCF27E30ED800BB19A2 /* ShapeFactoryCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */; }; + E49F7CD127E30ED800BB19A2 /* RectangleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */; }; + E49F7CD227E30ED900BB19A2 /* IdentifierFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4F27D0A80200815795 /* IdentifierFactory.swift */; }; + E49F7CD327E30ED900BB19A2 /* PointFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B027D1E70500B90B24 /* PointFactory.swift */; }; + E49F7CD427E30ED900BB19A2 /* SizeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637AC27D1E6D300B90B24 /* SizeFactory.swift */; }; + E49F7CD527E30ED900BB19A2 /* ColorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637AE27D1E6EE00B90B24 /* ColorFactory.swift */; }; + E49F7CD627E30ED900BB19A2 /* TypeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B427D1EC4F00B90B24 /* TypeBuilder.swift */; }; + E49F7CD727E30ED900BB19A2 /* Shapable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4C27D0A00F00815795 /* Shapable.swift */; }; + E49F7CD827E30ED900BB19A2 /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8082E27CDF0C100F4C062 /* Rectangle.swift */; }; + E49F7CD927E30ED900BB19A2 /* ImageRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57427E079EF005075B6 /* ImageRectangle.swift */; }; + E49F7CDA27E30ED900BB19A2 /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA127CEFE490096810A /* Point.swift */; }; + E49F7CDB27E30ED900BB19A2 /* Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA327CEFE7C0096810A /* Size.swift */; }; + E49F7CDC27E30ED900BB19A2 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA527CEFE7F0096810A /* Color.swift */; }; + E49F7CDD27E30ED900BB19A2 /* Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA727CEFEB90096810A /* Alpha.swift */; }; + E49F7CDE27E30ED900BB19A2 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4827D09C0000815795 /* Plane.swift */; }; E4C9622D27D975A3005DBD2B /* Hashable+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */; }; E4C9623127D97D7F005DBD2B /* Equatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */; }; E4D3DFA227CEFE490096810A /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA127CEFE490096810A /* Point.swift */; }; E4D3DFA427CEFE7C0096810A /* Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA327CEFE7C0096810A /* Size.swift */; }; E4D3DFA627CEFE7F0096810A /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA527CEFE7F0096810A /* Color.swift */; }; E4D3DFA827CEFEB90096810A /* Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA727CEFEB90096810A /* Alpha.swift */; }; - E4D3DFA927CF18740096810A /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA127CEFE490096810A /* Point.swift */; }; - E4D3DFAA27CF18740096810A /* Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA327CEFE7C0096810A /* Size.swift */; }; - E4D3DFAB27CF18740096810A /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA527CEFE7F0096810A /* Color.swift */; }; - E4D3DFAC27CF18740096810A /* Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA727CEFEB90096810A /* Alpha.swift */; }; E4D3DFB027CF42560096810A /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFAF27CF42560096810A /* CGFloat+Extensions.swift */; }; E4D6B57327DF3E3E005075B6 /* Float+ToFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57227DF3E3E005075B6 /* Float+ToFix.swift */; }; E4D6B57527E079EF005075B6 /* ImageRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57427E079EF005075B6 /* ImageRectangle.swift */; }; @@ -76,24 +70,12 @@ E4E8082F27CDF0C100F4C062 /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8082E27CDF0C100F4C062 /* Rectangle.swift */; }; E4E8083127CE062600F4C062 /* ShapeFactoryCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */; }; E4E8083B27CE0EBB00F4C062 /* DrawingAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083A27CE0EBB00F4C062 /* DrawingAppTests.swift */; }; - E4E8084227CE0EF700F4C062 /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8082E27CDF0C100F4C062 /* Rectangle.swift */; }; - E4E8084327CE0F0000F4C062 /* ShapeFactoryCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */; }; E4F20E7F27D86CE700E24DE8 /* Notification.Name+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E7E27D86CE700E24DE8 /* Notification.Name+Extensions.swift */; }; E4F20E8127D8836400E24DE8 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8027D8836400E24DE8 /* CGPoint+Extensions.swift */; }; E4F20E8327D883DD00E24DE8 /* CGSize+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8227D883DD00E24DE8 /* CGSize+Extensions.swift */; }; E4F20E8527D8843900E24DE8 /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8427D8843900E24DE8 /* CGRect+Extensions.swift */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - E4E8083C27CE0EBB00F4C062 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = E481265627CCF8E000A3FFF6 /* Project object */; - proxyType = 1; - remoteGlobalIDString = E481265D27CCF8E000A3FFF6; - remoteInfo = DrawingApp; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ E4002E9527D5B00300F14AFD /* RectangleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleView.swift; sourceTree = ""; }; E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneView.swift; sourceTree = ""; }; @@ -320,7 +302,6 @@ buildRules = ( ); dependencies = ( - E4E8083D27CE0EBB00F4C062 /* PBXTargetDependency */, ); name = DrawingAppTests; productName = DrawingAppTests; @@ -433,54 +414,38 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E435F08127E1CA7B005AD99C /* PlaneView.swift in Sources */, - E435F08227E1CA7B005AD99C /* ControlPanelView.swift in Sources */, - E435F07C27E1CA70005AD99C /* RoundedButton.swift in Sources */, - E435F07D27E1CA70005AD99C /* SimpleLabel.swift in Sources */, - E435F07E27E1CA70005AD99C /* ShapeViewable.swift in Sources */, - E435F07F27E1CA70005AD99C /* RectangleView.swift in Sources */, - E435F08027E1CA70005AD99C /* ImageRectangleView.swift in Sources */, - E435F07B27E1CA56005AD99C /* ImageRectangle.swift in Sources */, - E435F07527E1CA4F005AD99C /* RectangleViewFactory.swift in Sources */, - E435F07627E1CA4F005AD99C /* RectangleFactory.swift in Sources */, - E435F07727E1CA4F005AD99C /* PointFactory.swift in Sources */, - E435F07827E1CA4F005AD99C /* SizeFactory.swift in Sources */, - E435F07927E1CA4F005AD99C /* ColorFactory.swift in Sources */, - E435F07A27E1CA4F005AD99C /* TypeBuilder.swift in Sources */, - E435F06B27E1CA4A005AD99C /* Notification.Name+Extensions.swift in Sources */, - E435F06C27E1CA4A005AD99C /* Hashable+Hash.swift in Sources */, - E435F06D27E1CA4A005AD99C /* Equatable+Extensions.swift in Sources */, - E435F06E27E1CA4A005AD99C /* Double+Random.swift in Sources */, - E435F06F27E1CA4A005AD99C /* Float+ToFix.swift in Sources */, - E435F07027E1CA4A005AD99C /* String+Alphanumeric.swift in Sources */, - E435F07127E1CA4A005AD99C /* UIColor+Extensions.swift in Sources */, - E435F07227E1CA4A005AD99C /* CGRect+Extensions.swift in Sources */, - E435F07327E1CA4A005AD99C /* CGPoint+Extensions.swift in Sources */, - E435F07427E1CA4A005AD99C /* CGSize+Extensions.swift in Sources */, - E4641B5227D0A8BA00815795 /* IdentifierFactory.swift in Sources */, - E4641B4E27D0A0FE00815795 /* Shapable.swift in Sources */, - E4641B4B27D09E1B00815795 /* Plane.swift in Sources */, - E4641B4A27D09E1800815795 /* CGFloat+Extensions.swift in Sources */, - E4D3DFA927CF18740096810A /* Point.swift in Sources */, - E4D3DFAA27CF18740096810A /* Size.swift in Sources */, - E4D3DFAB27CF18740096810A /* Color.swift in Sources */, - E4D3DFAC27CF18740096810A /* Alpha.swift in Sources */, - E4E8084327CE0F0000F4C062 /* ShapeFactoryCluster.swift in Sources */, - E4E8084227CE0EF700F4C062 /* Rectangle.swift in Sources */, + E49F7CC427E30ED800BB19A2 /* Notification.Name+Extensions.swift in Sources */, + E49F7CC527E30ED800BB19A2 /* Hashable+Hash.swift in Sources */, + E49F7CC627E30ED800BB19A2 /* Equatable+Extensions.swift in Sources */, + E49F7CC727E30ED800BB19A2 /* Double+Random.swift in Sources */, + E49F7CC827E30ED800BB19A2 /* Float+ToFix.swift in Sources */, + E49F7CC927E30ED800BB19A2 /* String+Alphanumeric.swift in Sources */, + E49F7CCA27E30ED800BB19A2 /* UIColor+Extensions.swift in Sources */, + E49F7CCB27E30ED800BB19A2 /* CGFloat+Extensions.swift in Sources */, + E49F7CCC27E30ED800BB19A2 /* CGRect+Extensions.swift in Sources */, + E49F7CCD27E30ED800BB19A2 /* CGPoint+Extensions.swift in Sources */, + E49F7CCE27E30ED800BB19A2 /* CGSize+Extensions.swift in Sources */, + E49F7CCF27E30ED800BB19A2 /* ShapeFactoryCluster.swift in Sources */, + E49F7CD127E30ED800BB19A2 /* RectangleFactory.swift in Sources */, + E49F7CD227E30ED900BB19A2 /* IdentifierFactory.swift in Sources */, + E49F7CD327E30ED900BB19A2 /* PointFactory.swift in Sources */, + E49F7CD427E30ED900BB19A2 /* SizeFactory.swift in Sources */, + E49F7CD527E30ED900BB19A2 /* ColorFactory.swift in Sources */, + E49F7CD627E30ED900BB19A2 /* TypeBuilder.swift in Sources */, + E49F7CD727E30ED900BB19A2 /* Shapable.swift in Sources */, + E49F7CD827E30ED900BB19A2 /* Rectangle.swift in Sources */, + E49F7CD927E30ED900BB19A2 /* ImageRectangle.swift in Sources */, + E49F7CDA27E30ED900BB19A2 /* Point.swift in Sources */, + E49F7CDB27E30ED900BB19A2 /* Size.swift in Sources */, + E49F7CDC27E30ED900BB19A2 /* Color.swift in Sources */, + E49F7CDD27E30ED900BB19A2 /* Alpha.swift in Sources */, + E49F7CDE27E30ED900BB19A2 /* Plane.swift in Sources */, E4E8083B27CE0EBB00F4C062 /* DrawingAppTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - E4E8083D27CE0EBB00F4C062 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = E481265D27CCF8E000A3FFF6 /* DrawingApp */; - targetProxy = E4E8083C27CE0EBB00F4C062 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin PBXVariantGroup section */ E481266727CCF8E000A3FFF6 /* Main.storyboard */ = { isa = PBXVariantGroup; diff --git a/DrawingApp/DrawingApp.xcodeproj/xcshareddata/xcschemes/DrawingApp.xcscheme b/DrawingApp/DrawingApp.xcodeproj/xcshareddata/xcschemes/DrawingApp.xcscheme index 09737d06..324e47b5 100644 --- a/DrawingApp/DrawingApp.xcodeproj/xcshareddata/xcschemes/DrawingApp.xcscheme +++ b/DrawingApp/DrawingApp.xcodeproj/xcshareddata/xcschemes/DrawingApp.xcscheme @@ -27,8 +27,7 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES" - onlyGenerateCoverageForSpecifiedTargets = "YES"> + codeCoverageEnabled = "YES"> = 0...1 + XCTAssertTrue(range.contains(red)) + XCTAssertTrue(range.contains(green)) + XCTAssertTrue(range.contains(blue)) + XCTAssertEqual(alpha, 1) + } +} + +class Round: Shapable { + var id: String + var size: Size + var alpha: Alpha + var origin: Point + var backgroundColor: Color + func setBackgroundColor(_ color: Color) { + return + } + + func setAlpha(_ alpha: Alpha) { + return + } + + var description: String { + return "" + } + + init() { + self.id = IdentifierFactory.makeTypeRandomly() + self.size = SizeFactory.makeTypeRandomly() + self.alpha = Alpha.randomElement() + self.origin = PointFactory.makeTypeRandomly() + self.backgroundColor = .blue } } From f37334d7b052ab13ecbba9caa8be735b231cefde Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Fri, 18 Mar 2022 10:47:07 +0900 Subject: [PATCH 08/13] refactor: Hide image data of ImageRectangle and expose imagePath getter to keep encapsulation --- DrawingApp/DrawingApp/Model/ImageRectangle.swift | 13 ++++++++----- DrawingApp/DrawingApp/Model/Shape.swift | 8 ++++++++ .../DrawingApp/Utility/RectangleViewFactory.swift | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 DrawingApp/DrawingApp/Model/Shape.swift diff --git a/DrawingApp/DrawingApp/Model/ImageRectangle.swift b/DrawingApp/DrawingApp/Model/ImageRectangle.swift index c290698f..82a307e3 100644 --- a/DrawingApp/DrawingApp/Model/ImageRectangle.swift +++ b/DrawingApp/DrawingApp/Model/ImageRectangle.swift @@ -8,25 +8,28 @@ import Foundation protocol ImagePossessable { - var image: URL? { get } func setImage(with url: URL) } class ImageRectangle: Rectangle, ImagePossessable { - private(set) var image: URL? + private var imageURL: URL? + + var imagePath: String? { + return self.imageURL?.path + } init(id: String, origin: Point, size: Size, image: URL? = nil) { super.init(id: id, origin: origin, size: size) - self.image = image + self.imageURL = image } init(id: String, x: Double, y: Double, width: Double, height: Double, image: URL? = nil) { super.init(id: id, x: x, y: y, width: width, height: height) - self.image = image + self.imageURL = image } func setImage(with url: URL) { - self.image = url + self.imageURL = url } override func notifyDidCreated() { diff --git a/DrawingApp/DrawingApp/Model/Shape.swift b/DrawingApp/DrawingApp/Model/Shape.swift new file mode 100644 index 00000000..d830545c --- /dev/null +++ b/DrawingApp/DrawingApp/Model/Shape.swift @@ -0,0 +1,8 @@ +// +// Shape.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/18. +// + +import Foundation diff --git a/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift b/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift index 1d664087..f2573073 100644 --- a/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift +++ b/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift @@ -20,7 +20,7 @@ enum RectangleViewFactory { return rectangleView case is ImageRectangleView.Type: guard let data = data as? ImageRectangle else { return nil } - guard let path = data.image?.path, let image = UIImage(contentsOfFile: path) else { return nil } + guard let path = data.imagePath, let image = UIImage(contentsOfFile: path) else { return nil } let imageRectangleView = ImageRectangleView(frame: data.convert(using: CGRect.self)) From 69229fe60b93a039140d3dadca4d3e1e89a9210b Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Fri, 18 Mar 2022 12:49:15 +0900 Subject: [PATCH 09/13] refactor: Implement Shape class as abstract class and refactor related code --- .../DrawingApp.xcodeproj/project.pbxproj | 40 ++++++++++---- .../Controller/ViewController.swift | 52 +++++++++---------- .../DrawingApp/Model/AlphaAdaptable.swift | 14 +++++ .../Model/BackgroundAdaptable.swift | 13 +++++ DrawingApp/DrawingApp/Model/Color.swift | 5 ++ ...Rectangle.swift => ColoredRectangle.swift} | 48 +++++------------ .../DrawingApp/Model/ImageRectangle.swift | 36 ++++++++++--- DrawingApp/DrawingApp/Model/Notifiable.swift | 18 +++++++ DrawingApp/DrawingApp/Model/Plane.swift | 4 -- .../DrawingApp/Model/RectangleBuildable.swift | 12 +++++ DrawingApp/DrawingApp/Model/Shapable.swift | 15 +++--- DrawingApp/DrawingApp/Model/Shape.swift | 16 ++++++ .../Extensions/CGRect+Extensions.swift | 2 +- .../DrawingApp/Utility/RectangleFactory.swift | 14 ++--- .../Utility/RectangleViewFactory.swift | 6 +-- .../Utility/ShapeFactoryCluster.swift | 2 +- ...eView.swift => ColoredRectangleView.swift} | 10 ++-- 17 files changed, 202 insertions(+), 105 deletions(-) create mode 100644 DrawingApp/DrawingApp/Model/AlphaAdaptable.swift create mode 100644 DrawingApp/DrawingApp/Model/BackgroundAdaptable.swift rename DrawingApp/DrawingApp/Model/{Rectangle.swift => ColoredRectangle.swift} (60%) create mode 100644 DrawingApp/DrawingApp/Model/Notifiable.swift create mode 100644 DrawingApp/DrawingApp/Model/RectangleBuildable.swift rename DrawingApp/DrawingApp/View/Components/{RectangleView.swift => ColoredRectangleView.swift} (86%) diff --git a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj index c89eda6d..dce7ad33 100644 --- a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj +++ b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj @@ -7,11 +7,15 @@ objects = { /* Begin PBXBuildFile section */ - E4002E9627D5B00300F14AFD /* RectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4002E9527D5B00300F14AFD /* RectangleView.swift */; }; + E4002E9627D5B00300F14AFD /* ColoredRectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4002E9527D5B00300F14AFD /* ColoredRectangleView.swift */; }; E41EE3AF27D6EAA7007C7595 /* PlaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */; }; E41EE3B127D6F478007C7595 /* ControlPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3B027D6F478007C7595 /* ControlPanelView.swift */; }; E435F06527E173F7005AD99C /* RectangleViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06427E173F7005AD99C /* RectangleViewFactory.swift */; }; E435F06727E17581005AD99C /* ShapeViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06627E17581005AD99C /* ShapeViewable.swift */; }; + E453DC9D27E41B2400A317AF /* Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DC9C27E41B2400A317AF /* Shape.swift */; }; + E453DC9F27E41FC200A317AF /* RectangleBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */; }; + E453DCA127E4251B00A317AF /* AlphaAdaptable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DCA027E4251B00A317AF /* AlphaAdaptable.swift */; }; + E453DCA327E4262E00A317AF /* BackgroundAdaptable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DCA227E4262E00A317AF /* BackgroundAdaptable.swift */; }; E4630C2727CDD2BC00644B70 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4630C2627CDD2BC00644B70 /* SceneDelegate.swift */; }; E4641B4927D09C0000815795 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4827D09C0000815795 /* Plane.swift */; }; E4641B4D27D0A00F00815795 /* Shapable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4C27D0A00F00815795 /* Shapable.swift */; }; @@ -45,13 +49,14 @@ E49F7CD527E30ED900BB19A2 /* ColorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637AE27D1E6EE00B90B24 /* ColorFactory.swift */; }; E49F7CD627E30ED900BB19A2 /* TypeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B427D1EC4F00B90B24 /* TypeBuilder.swift */; }; E49F7CD727E30ED900BB19A2 /* Shapable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4C27D0A00F00815795 /* Shapable.swift */; }; - E49F7CD827E30ED900BB19A2 /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8082E27CDF0C100F4C062 /* Rectangle.swift */; }; + E49F7CD827E30ED900BB19A2 /* ColoredRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8082E27CDF0C100F4C062 /* ColoredRectangle.swift */; }; E49F7CD927E30ED900BB19A2 /* ImageRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D6B57427E079EF005075B6 /* ImageRectangle.swift */; }; E49F7CDA27E30ED900BB19A2 /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA127CEFE490096810A /* Point.swift */; }; E49F7CDB27E30ED900BB19A2 /* Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA327CEFE7C0096810A /* Size.swift */; }; E49F7CDC27E30ED900BB19A2 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA527CEFE7F0096810A /* Color.swift */; }; E49F7CDD27E30ED900BB19A2 /* Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA727CEFEB90096810A /* Alpha.swift */; }; E49F7CDE27E30ED900BB19A2 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4827D09C0000815795 /* Plane.swift */; }; + E4BA038227E42781005BE75C /* Notifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038127E42781005BE75C /* Notifiable.swift */; }; E4C9622D27D975A3005DBD2B /* Hashable+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */; }; E4C9623127D97D7F005DBD2B /* Equatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */; }; E4D3DFA227CEFE490096810A /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA127CEFE490096810A /* Point.swift */; }; @@ -67,7 +72,7 @@ E4E637B127D1E70500B90B24 /* PointFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B027D1E70500B90B24 /* PointFactory.swift */; }; E4E637B327D1EAC300B90B24 /* RectangleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */; }; E4E637B527D1EC4F00B90B24 /* TypeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B427D1EC4F00B90B24 /* TypeBuilder.swift */; }; - E4E8082F27CDF0C100F4C062 /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8082E27CDF0C100F4C062 /* Rectangle.swift */; }; + E4E8082F27CDF0C100F4C062 /* ColoredRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8082E27CDF0C100F4C062 /* ColoredRectangle.swift */; }; E4E8083127CE062600F4C062 /* ShapeFactoryCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */; }; E4E8083B27CE0EBB00F4C062 /* DrawingAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083A27CE0EBB00F4C062 /* DrawingAppTests.swift */; }; E4F20E7F27D86CE700E24DE8 /* Notification.Name+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E7E27D86CE700E24DE8 /* Notification.Name+Extensions.swift */; }; @@ -77,11 +82,15 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - E4002E9527D5B00300F14AFD /* RectangleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleView.swift; sourceTree = ""; }; + E4002E9527D5B00300F14AFD /* ColoredRectangleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredRectangleView.swift; sourceTree = ""; }; E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneView.swift; sourceTree = ""; }; E41EE3B027D6F478007C7595 /* ControlPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlPanelView.swift; sourceTree = ""; }; E435F06427E173F7005AD99C /* RectangleViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleViewFactory.swift; sourceTree = ""; }; E435F06627E17581005AD99C /* ShapeViewable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeViewable.swift; sourceTree = ""; }; + E453DC9C27E41B2400A317AF /* Shape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shape.swift; sourceTree = ""; }; + E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleBuildable.swift; sourceTree = ""; }; + E453DCA027E4251B00A317AF /* AlphaAdaptable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlphaAdaptable.swift; sourceTree = ""; }; + E453DCA227E4262E00A317AF /* BackgroundAdaptable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundAdaptable.swift; sourceTree = ""; }; E4630C2627CDD2BC00644B70 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E4641B4827D09C0000815795 /* Plane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plane.swift; sourceTree = ""; }; E4641B4C27D0A00F00815795 /* Shapable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shapable.swift; sourceTree = ""; }; @@ -98,6 +107,7 @@ E481266D27CCF8E100A3FFF6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; E481266F27CCF8E100A3FFF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E49679E727D71AF900A9BA27 /* SimpleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleLabel.swift; sourceTree = ""; }; + E4BA038127E42781005BE75C /* Notifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifiable.swift; sourceTree = ""; }; E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Hashable+Hash.swift"; sourceTree = ""; }; E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Equatable+Extensions.swift"; sourceTree = ""; }; E4D3DFA127CEFE490096810A /* Point.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Point.swift; sourceTree = ""; }; @@ -113,7 +123,7 @@ E4E637B027D1E70500B90B24 /* PointFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointFactory.swift; sourceTree = ""; }; E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleFactory.swift; sourceTree = ""; }; E4E637B427D1EC4F00B90B24 /* TypeBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeBuilder.swift; sourceTree = ""; }; - E4E8082E27CDF0C100F4C062 /* Rectangle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rectangle.swift; sourceTree = ""; }; + E4E8082E27CDF0C100F4C062 /* ColoredRectangle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredRectangle.swift; sourceTree = ""; }; E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeFactoryCluster.swift; sourceTree = ""; }; E4E8083827CE0EBB00F4C062 /* DrawingAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DrawingAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E4E8083A27CE0EBB00F4C062 /* DrawingAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawingAppTests.swift; sourceTree = ""; }; @@ -147,7 +157,7 @@ E48109EE27D6204A007B0E3F /* RoundedButton.swift */, E49679E727D71AF900A9BA27 /* SimpleLabel.swift */, E435F06627E17581005AD99C /* ShapeViewable.swift */, - E4002E9527D5B00300F14AFD /* RectangleView.swift */, + E4002E9527D5B00300F14AFD /* ColoredRectangleView.swift */, E4D6B57627E07F28005075B6 /* ImageRectangleView.swift */, ); path = Components; @@ -156,8 +166,13 @@ E4630C2227CDD22900644B70 /* Model */ = { isa = PBXGroup; children = ( + E453DCA027E4251B00A317AF /* AlphaAdaptable.swift */, + E453DCA227E4262E00A317AF /* BackgroundAdaptable.swift */, + E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */, + E4BA038127E42781005BE75C /* Notifiable.swift */, E4641B4C27D0A00F00815795 /* Shapable.swift */, - E4E8082E27CDF0C100F4C062 /* Rectangle.swift */, + E453DC9C27E41B2400A317AF /* Shape.swift */, + E4E8082E27CDF0C100F4C062 /* ColoredRectangle.swift */, E4D6B57427E079EF005075B6 /* ImageRectangle.swift */, E4D3DFA127CEFE490096810A /* Point.swift */, E4D3DFA327CEFE7C0096810A /* Size.swift */, @@ -373,12 +388,15 @@ E4E637B527D1EC4F00B90B24 /* TypeBuilder.swift in Sources */, E4D3DFA427CEFE7C0096810A /* Size.swift in Sources */, E4641B4D27D0A00F00815795 /* Shapable.swift in Sources */, - E4E8082F27CDF0C100F4C062 /* Rectangle.swift in Sources */, + E4E8082F27CDF0C100F4C062 /* ColoredRectangle.swift in Sources */, + E4BA038227E42781005BE75C /* Notifiable.swift in Sources */, E4D3DFA227CEFE490096810A /* Point.swift in Sources */, + E453DCA327E4262E00A317AF /* BackgroundAdaptable.swift in Sources */, E4E637AF27D1E6EE00B90B24 /* ColorFactory.swift in Sources */, E4641B5027D0A80200815795 /* IdentifierFactory.swift in Sources */, E4C9622D27D975A3005DBD2B /* Hashable+Hash.swift in Sources */, E4D3DFA627CEFE7F0096810A /* Color.swift in Sources */, + E453DC9F27E41FC200A317AF /* RectangleBuildable.swift in Sources */, E435F06527E173F7005AD99C /* RectangleViewFactory.swift in Sources */, E48109E927D61E93007B0E3F /* UIColor+Extensions.swift in Sources */, E4D3DFA827CEFEB90096810A /* Alpha.swift in Sources */, @@ -387,11 +405,12 @@ E48109E627D61E2F007B0E3F /* Double+Random.swift in Sources */, E4E8083127CE062600F4C062 /* ShapeFactoryCluster.swift in Sources */, E481266627CCF8E000A3FFF6 /* ViewController.swift in Sources */, + E453DC9D27E41B2400A317AF /* Shape.swift in Sources */, E41EE3B127D6F478007C7595 /* ControlPanelView.swift in Sources */, E4C9623127D97D7F005DBD2B /* Equatable+Extensions.swift in Sources */, E4D6B57527E079EF005075B6 /* ImageRectangle.swift in Sources */, E41EE3AF27D6EAA7007C7595 /* PlaneView.swift in Sources */, - E4002E9627D5B00300F14AFD /* RectangleView.swift in Sources */, + E4002E9627D5B00300F14AFD /* ColoredRectangleView.swift in Sources */, E4F20E7F27D86CE700E24DE8 /* Notification.Name+Extensions.swift in Sources */, E4E637AD27D1E6D300B90B24 /* SizeFactory.swift in Sources */, E481266227CCF8E000A3FFF6 /* AppDelegate.swift in Sources */, @@ -407,6 +426,7 @@ E4D6B57327DF3E3E005075B6 /* Float+ToFix.swift in Sources */, E4F20E8327D883DD00E24DE8 /* CGSize+Extensions.swift in Sources */, E48109E427D61E11007B0E3F /* String+Alphanumeric.swift in Sources */, + E453DCA127E4251B00A317AF /* AlphaAdaptable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -433,7 +453,7 @@ E49F7CD527E30ED900BB19A2 /* ColorFactory.swift in Sources */, E49F7CD627E30ED900BB19A2 /* TypeBuilder.swift in Sources */, E49F7CD727E30ED900BB19A2 /* Shapable.swift in Sources */, - E49F7CD827E30ED900BB19A2 /* Rectangle.swift in Sources */, + E49F7CD827E30ED900BB19A2 /* ColoredRectangle.swift in Sources */, E49F7CD927E30ED900BB19A2 /* ImageRectangle.swift in Sources */, E49F7CDA27E30ED900BB19A2 /* Point.swift in Sources */, E49F7CDB27E30ED900BB19A2 /* Size.swift in Sources */, diff --git a/DrawingApp/DrawingApp/Controller/ViewController.swift b/DrawingApp/DrawingApp/Controller/ViewController.swift index dfd024a4..8e818afe 100644 --- a/DrawingApp/DrawingApp/Controller/ViewController.swift +++ b/DrawingApp/DrawingApp/Controller/ViewController.swift @@ -8,6 +8,8 @@ import UIKit import PhotosUI +typealias AlphaAdaptableShape = Shape & AlphaAdaptable + class ViewController: UIViewController { // MARK: - Properties for View @IBOutlet weak var controlPanelView: ControlPanelView! @@ -15,7 +17,7 @@ class ViewController: UIViewController { // MARK: - Property for Model private let plane = Plane() - private var rectangleMap = [AnyHashable: ShapeViewable]() + private var rectangleMap = [Shape: ShapeViewable]() // MARK: - View Life Cycle Methods override func viewDidLoad() { @@ -32,13 +34,13 @@ class ViewController: UIViewController { private func setObservers() { NotificationCenter.default.addObserver(forName: .RectangleModelDidCreated, object: nil, queue: .main, using: { notification in - guard let rectangle = notification.object as? Shapable else { return } - self.createRectangleView(ofClass: RectangleView.self, with: rectangle) + guard let rectangle = notification.object as? Shape else { return } + self.createRectangleView(ofClass: ColoredRectangleView.self, with: rectangle) }) NotificationCenter.default.addObserver(forName: .RectangleModelDidUpdated, object: nil, queue: .main, using: self.rectangleDataDidChanged) NotificationCenter.default.addObserver(forName: .ImageRectangleModelDidCreated, object: nil, queue: .main, using: { notification in - guard let rectangle = notification.object as? Shapable else { return } + guard let rectangle = notification.object as? Shape else { return } self.createRectangleView(ofClass: ImageRectangleView.self, with: rectangle) }) NotificationCenter.default.addObserver(forName: .RectangleModelDidUpdated, object: nil, queue: .main, using: self.rectangleDataDidChanged) @@ -76,7 +78,7 @@ extension ViewController: PlaneViewDelegate { // MARK: - ControlPanelView To ViewController extension ViewController: ControlPanelViewDelegate { func controlPanelDidPressColorButton() { - guard let rectangle = self.plane.currentItem as? RectangleShapable else { return } + guard let rectangle = self.plane.currentItem as? BackgroundAdaptable else { return } let color = ColorFactory.makeTypeRandomly() @@ -86,7 +88,7 @@ extension ViewController: ControlPanelViewDelegate { func controlPanelDidMoveAlphaSlider(_ sender: UISlider) { let value = sender.value.toFixed(digits: 1) - guard let rectangle = self.plane.currentItem else { return } + guard let rectangle = self.plane.currentItem as? AlphaAdaptable else { return } guard let alpha = Alpha(rawValue: value) else { return } rectangle.setAlpha(alpha) @@ -108,33 +110,28 @@ extension ViewController { // MARK: - Rectangle Model To ViewController extension ViewController { - private func createRectangleView(ofClass Class: ShapeViewable.Type, with rectangle: Shapable) { + private func createRectangleView(ofClass Class: ShapeViewable.Type, with rectangle: Shape) { guard let rectangleView = RectangleViewFactory.makeView(ofClass: Class, with: rectangle) else { return } let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleOnTapRectangleView)) rectangleView.addGestureRecognizer(tap) - - self.planeView.addSubview(rectangleView) - rectangleView.animateScale(CGFloat(1.2), duration: 0.15, delay: 0) - // TODO: Dictionary 키 타입을 프로토콜로 추상화하여 Hashable 하게 변경 - // Shapable 프로토콜은 Hashable 할 수 없음 - - self.rectangleMap.updateValue(rectangleView, forKey: rectangle.id) + self.planeView.addSubview(rectangleView) + self.rectangleMap.updateValue(rectangleView, forKey: rectangle) } private func rectangleDataDidChanged(_ notification: Notification) { - guard let rectangle = self.plane.currentItem else { return } - guard let rectangleView = self.rectangleMap[rectangle.id] else { return } + guard let rectangle = self.plane.currentItem as? AlphaAdaptableShape else { return } + guard let rectangleView = self.rectangleMap[rectangle] else { return } - if let alpha = notification.userInfo?[Rectangle.NotificationKey.alpha] as? Alpha { + if let alpha = notification.userInfo?[NotificationKey.updated] as? Alpha { rectangleView.setAlpha(alpha) } - if let color = notification.userInfo?[Rectangle.NotificationKey.color] as? Color { - (rectangleView as? BackgroundColorable)?.setBackgroundColor(color: color, alpha: rectangle.alpha) + if let colorableView = rectangleView as? BackgroundViewable, let color = notification.userInfo?[NotificationKey.updated] as? Color { + colorableView.setBackgroundColor(color: color, alpha: rectangle.alpha) self.controlPanelView.setColorButtonTitle(title: rectangleView.backgroundColor?.toHexString() ?? "None") } } @@ -143,23 +140,26 @@ extension ViewController { // MARK: - Plane Model to ViewController extension ViewController { private func planeDidSelectItem(_ notification: Notification) { - guard let rectangle = notification.userInfo?[Plane.NotificationKey.select] as? Shapable else { return } - guard let rectangleView = self.rectangleMap[rectangle.id] else { return } + guard let rectangle = notification.userInfo?[Plane.NotificationKey.select] as? AlphaAdaptableShape else { return } + guard let rectangleView = self.rectangleMap[rectangle] else { return } rectangleView.setBorder(width: 2, color: .blue) - let hexString = UIColor(with: rectangle.backgroundColor).toHexString() - let isConformed = (rectangle as? ImagePossessable) == nil + if let colorableShape = rectangle as? BackgroundAdaptable { + let hexString = Color.toHexString(colorableShape.backgroundColor) + self.controlPanelView.setColorButtonTitle(title: hexString) + } + + let isConformed = (rectangle as? ImageAdaptableShape) == nil self.controlPanelView.setColorButtonControllable(enable: isConformed) self.controlPanelView.setAlphaSliderControllable(enable: true) - self.controlPanelView.setColorButtonTitle(title: hexString) self.controlPanelView.setAlphaSliderValue(value: rectangle.alpha) } private func planeDidUnselectItem(_ notification: Notification) { - guard let rectangle = notification.userInfo?[Plane.NotificationKey.unselect] as? Shapable else { return } - guard let rectangleView = self.rectangleMap[rectangle.id] else { return } + guard let rectangle = notification.userInfo?[Plane.NotificationKey.unselect] as? Shape else { return } + guard let rectangleView = self.rectangleMap[rectangle] else { return } rectangleView.removeBorder() diff --git a/DrawingApp/DrawingApp/Model/AlphaAdaptable.swift b/DrawingApp/DrawingApp/Model/AlphaAdaptable.swift new file mode 100644 index 00000000..039d034e --- /dev/null +++ b/DrawingApp/DrawingApp/Model/AlphaAdaptable.swift @@ -0,0 +1,14 @@ +// +// AlphaAdaptable.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/18. +// + +import Foundation + +protocol AlphaAdaptable { + var alpha: Alpha { get } + + func setAlpha(_ alpha: Alpha) +} diff --git a/DrawingApp/DrawingApp/Model/BackgroundAdaptable.swift b/DrawingApp/DrawingApp/Model/BackgroundAdaptable.swift new file mode 100644 index 00000000..897ac7f7 --- /dev/null +++ b/DrawingApp/DrawingApp/Model/BackgroundAdaptable.swift @@ -0,0 +1,13 @@ +// +// BackgroundAdaptable.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/18. +// + +import Foundation + +protocol BackgroundAdaptable { + var backgroundColor: Color { get } + func setBackgroundColor(_ color: Color) +} diff --git a/DrawingApp/DrawingApp/Model/Color.swift b/DrawingApp/DrawingApp/Model/Color.swift index 8e5f3dd3..cdc774f4 100644 --- a/DrawingApp/DrawingApp/Model/Color.swift +++ b/DrawingApp/DrawingApp/Model/Color.swift @@ -16,6 +16,11 @@ struct Color { static let blue = Color(red: 0, green: 0, blue: 255) static let white = Color(red: 255, green: 255, blue: 255) + static func toHexString(_ color: Self) -> String { + let RGB = Int(color.red * 255) << 16 | Int(color.green * 255) << 8 | Int(color.blue * 255) + return String(format:"#%06x", RGB).uppercased() + } + let red: Double let green: Double let blue: Double diff --git a/DrawingApp/DrawingApp/Model/Rectangle.swift b/DrawingApp/DrawingApp/Model/ColoredRectangle.swift similarity index 60% rename from DrawingApp/DrawingApp/Model/Rectangle.swift rename to DrawingApp/DrawingApp/Model/ColoredRectangle.swift index 7865a47b..bfa739b7 100644 --- a/DrawingApp/DrawingApp/Model/Rectangle.swift +++ b/DrawingApp/DrawingApp/Model/ColoredRectangle.swift @@ -1,5 +1,5 @@ // -// Rectangle.swift +// ColoredRectangle.swift // DrawingApp // // Created by 송태환 on 2022/03/01. @@ -7,59 +7,35 @@ import Foundation -protocol RectangleBuildable { - init(x: Double, y: Double, width: Double, height: Double) -} - -protocol RectangleShapable { - var backgroundColor: Color { get } - func setBackgroundColor(_ color: Color) -} +typealias BackgroundColorControllable = BackgroundAdaptable & AlphaAdaptable -class Rectangle: Shapable, RectangleShapable, Notifiable, Hashable { - enum NotificationKey { - case alpha - case color - } - +class ColoredRectangle: Shape, BackgroundColorControllable { // MARK: - Properties private(set) var backgroundColor: Color { didSet { - self.notifyDidUpdated(key: .color, data: self.backgroundColor) + self.notifyDidUpdated(key: .updated, data: self.backgroundColor) } } private(set) var alpha: Alpha { didSet { - self.notifyDidUpdated(key: .alpha, data: self.alpha) + self.notifyDidUpdated(key: .updated, data: self.alpha) } } - let id: String - let size: Size - let origin: Point - - var diagonalPoint: Point { - let maxX = self.origin.x + self.size.width - let maxY = self.origin.y + self.size.height - return Point(x: maxX, y: maxY) - } - // MARK: - Initialisers init(id: String, origin: Point, size: Size, color: Color = .white, alpha: Alpha = .opaque) { - self.id = id - self.origin = origin - self.size = size self.backgroundColor = color self.alpha = alpha + super.init(id: id, origin: origin, size: size) } init(id: String, x: Double, y: Double, width: Double, height: Double, color: Color = .white, alpha: Alpha = .opaque) { - self.id = id - self.origin = Point(x: x, y: y) - self.size = Size(width: width, height: height) - self.backgroundColor = color + let origin = Point(x: x, y: y) + let size = Size(width: width, height: height) self.alpha = alpha + self.backgroundColor = color + super.init(id: id, origin: origin, size: size) } func convert(using Convertor: T.Type) -> T { @@ -74,7 +50,7 @@ class Rectangle: Shapable, RectangleShapable, Notifiable, Hashable { self.alpha = alpha } - func notifyDidCreated() { + override func notifyDidCreated() { NotificationCenter.default.post(name: .RectangleModelDidCreated, object: self) } @@ -84,7 +60,7 @@ class Rectangle: Shapable, RectangleShapable, Notifiable, Hashable { } // MARK: - CustomStringConvertible Protocol -extension Rectangle { +extension ColoredRectangle: CustomStringConvertible { var description: String { return """ (\(self.id)), \(self.origin), \(self.size), \(self.backgroundColor), \(self.alpha) diff --git a/DrawingApp/DrawingApp/Model/ImageRectangle.swift b/DrawingApp/DrawingApp/Model/ImageRectangle.swift index 82a307e3..d0dfb2f6 100644 --- a/DrawingApp/DrawingApp/Model/ImageRectangle.swift +++ b/DrawingApp/DrawingApp/Model/ImageRectangle.swift @@ -7,11 +7,19 @@ import Foundation -protocol ImagePossessable { - func setImage(with url: URL) +protocol ImageAdaptable { + func setImagePath(with url: URL) } -class ImageRectangle: Rectangle, ImagePossessable { +typealias ImageAdaptableShape = AlphaAdaptable & ImageAdaptable + +class ImageRectangle: Shape, ImageAdaptableShape { + private(set) var alpha: Alpha { + didSet { + self.notifyDidUpdated(key: .updated, data: self.alpha) + } + } + private var imageURL: URL? var imagePath: String? { @@ -19,20 +27,36 @@ class ImageRectangle: Rectangle, ImagePossessable { } init(id: String, origin: Point, size: Size, image: URL? = nil) { - super.init(id: id, origin: origin, size: size) self.imageURL = image + self.alpha = .opaque + super.init(id: id, origin: origin, size: size) } init(id: String, x: Double, y: Double, width: Double, height: Double, image: URL? = nil) { - super.init(id: id, x: x, y: y, width: width, height: height) + let origin = Point(x: x, y: y) + let size = Size(width: width, height: height) self.imageURL = image + self.alpha = .opaque + super.init(id: id, origin: origin, size: size) } - func setImage(with url: URL) { + func setImagePath(with url: URL) { self.imageURL = url } + func setAlpha(_ alpha: Alpha) { + self.alpha = alpha + } + + func convert(using Convertor: T.Type) -> T { + return Convertor.init(x: self.origin.x, y: self.origin.y, width: self.size.width, height: self.size.height) + } + override func notifyDidCreated() { NotificationCenter.default.post(name: .ImageRectangleModelDidCreated, object: self) } + + func notifyDidUpdated(key: NotificationKey, data: Any) { + NotificationCenter.default.post(name: .RectangleModelDidUpdated, object: self, userInfo: [key: data]) + } } diff --git a/DrawingApp/DrawingApp/Model/Notifiable.swift b/DrawingApp/DrawingApp/Model/Notifiable.swift new file mode 100644 index 00000000..cbe666fd --- /dev/null +++ b/DrawingApp/DrawingApp/Model/Notifiable.swift @@ -0,0 +1,18 @@ +// +// Notifiable.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/18. +// + +import Foundation + +enum NotificationKey { + case created + case updated +} + +protocol Notifiable: AnyObject { + func notifyDidCreated() + func notifyDidUpdate(key: NotificationKey, data: Any) +} diff --git a/DrawingApp/DrawingApp/Model/Plane.swift b/DrawingApp/DrawingApp/Model/Plane.swift index 9942da1b..e49c2802 100644 --- a/DrawingApp/DrawingApp/Model/Plane.swift +++ b/DrawingApp/DrawingApp/Model/Plane.swift @@ -7,10 +7,6 @@ import Foundation -protocol Notifiable: AnyObject { - func notifyDidCreated() -} - class Plane { private var items = [String: Shapable]() diff --git a/DrawingApp/DrawingApp/Model/RectangleBuildable.swift b/DrawingApp/DrawingApp/Model/RectangleBuildable.swift new file mode 100644 index 00000000..a76f275f --- /dev/null +++ b/DrawingApp/DrawingApp/Model/RectangleBuildable.swift @@ -0,0 +1,12 @@ +// +// RectangleBuildable.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/18. +// + +import Foundation + +protocol RectangleBuildable { + init(x: Double, y: Double, width: Double, height: Double) +} diff --git a/DrawingApp/DrawingApp/Model/Shapable.swift b/DrawingApp/DrawingApp/Model/Shapable.swift index e1a1d35c..1c809ad4 100644 --- a/DrawingApp/DrawingApp/Model/Shapable.swift +++ b/DrawingApp/DrawingApp/Model/Shapable.swift @@ -7,16 +7,13 @@ import Foundation -protocol Shapable: CustomStringConvertible, AnyObject { +protocol Shapable: AnyObject { var id: String { get } var size: Size { get } - var alpha: Alpha { get } var origin: Point { get } - var backgroundColor: Color { get } - + var diagonalPoint: Point { get } + func contains(point: Point) -> Bool - func setBackgroundColor(_ color: Color) - func setAlpha(_ alpha: Alpha) } extension Shapable { @@ -26,4 +23,10 @@ extension Shapable { return self.origin <= point && point < Point(x: maxX, y: maxY) } + + var diagonalPoint: Point { + let maxX = self.origin.x + self.size.width + let maxY = self.origin.y + self.size.height + return Point(x: maxX, y: maxY) + } } diff --git a/DrawingApp/DrawingApp/Model/Shape.swift b/DrawingApp/DrawingApp/Model/Shape.swift index d830545c..b46f62c2 100644 --- a/DrawingApp/DrawingApp/Model/Shape.swift +++ b/DrawingApp/DrawingApp/Model/Shape.swift @@ -6,3 +6,19 @@ // import Foundation + +/// 추상 클래스 +class Shape: Shapable, Notifiable, Hashable { + let id: String + let size: Size + let origin: Point + + init(id: String, origin: Point, size: Size) { + self.id = id + self.origin = origin + self.size = size + } + + func notifyDidCreated() {} + func notifyDidUpdate(key: T, data: Any) {} +} diff --git a/DrawingApp/DrawingApp/Utility/Extensions/CGRect+Extensions.swift b/DrawingApp/DrawingApp/Utility/Extensions/CGRect+Extensions.swift index 1c42da41..f9b0dc8d 100644 --- a/DrawingApp/DrawingApp/Utility/Extensions/CGRect+Extensions.swift +++ b/DrawingApp/DrawingApp/Utility/Extensions/CGRect+Extensions.swift @@ -8,7 +8,7 @@ import UIKit extension CGRect: RectangleBuildable { - init(with rectangle: Rectangle) { + init(with rectangle: ColoredRectangle) { self.init(x: rectangle.origin.x, y: rectangle.origin.y, width: rectangle.size.width, height: rectangle.size.height) } } diff --git a/DrawingApp/DrawingApp/Utility/RectangleFactory.swift b/DrawingApp/DrawingApp/Utility/RectangleFactory.swift index 4243df60..ae3dc442 100644 --- a/DrawingApp/DrawingApp/Utility/RectangleFactory.swift +++ b/DrawingApp/DrawingApp/Utility/RectangleFactory.swift @@ -8,26 +8,26 @@ import Foundation protocol ShapeBuilder { - static func makeShape() -> Shapable + static func makeShape() -> Shape } enum RectangleFactory: ShapeBuilder { - static func makeRectangle(x: Double = 0, y: Double = 0, width: Double = 30, height: Double = 30) -> Rectangle { + static func makeRectangle(x: Double = 0, y: Double = 0, width: Double = 30, height: Double = 30) -> Shape { let id = IdentifierFactory.makeTypeRandomly() - return Rectangle(id: id, x: x, y: y, width: width, height: height, color: .white, alpha: .opaque) + return ColoredRectangle(id: id, x: x, y: y, width: width, height: height, color: .white, alpha: .opaque) } - static func makeRandomRectangle() -> Rectangle { + static func makeRandomRectangle() -> Shape { let id = IdentifierFactory.makeTypeRandomly() let point = PointFactory.makeTypeRandomly() let size = SizeFactory.makeType(width: 150, height: 120) let color = ColorFactory.makeTypeRandomly() let alpha = Alpha.randomElement() - return Rectangle(id: id, origin: point, size: size, color: color, alpha: alpha) + return ColoredRectangle(id: id, origin: point, size: size, color: color, alpha: alpha) } - static func makeRandomRectangle(with image: URL) -> Rectangle { + static func makeRandomRectangle(with image: URL) -> Shape { let id = IdentifierFactory.makeTypeRandomly() let point = PointFactory.makeTypeRandomly() let size = SizeFactory.makeType(width: 150, height: 120) @@ -35,7 +35,7 @@ enum RectangleFactory: ShapeBuilder { return ImageRectangle(id: id, origin: point, size: size, image: image) } - static func makeShape() -> Shapable { + static func makeShape() -> Shape { return self.makeRectangle() } } diff --git a/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift b/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift index f2573073..a95fc567 100644 --- a/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift +++ b/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift @@ -11,9 +11,9 @@ import UIKit enum RectangleViewFactory { static func makeView(ofClass Class: ShapeViewable.Type, with data: Shapable) -> ShapeViewable? { switch Class { - case is RectangleView.Type: - guard let data = data as? Rectangle else { return nil } - let rectangleView = RectangleView(frame: data.convert(using: CGRect.self)) + case is ColoredRectangleView.Type: + guard let data = data as? ColoredRectangle else { return nil } + let rectangleView = ColoredRectangleView(frame: data.convert(using: CGRect.self)) rectangleView.setBackgroundColor(color: data.backgroundColor, alpha: data.alpha) diff --git a/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift b/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift index 4d9fa90a..5556bba2 100644 --- a/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift +++ b/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift @@ -14,7 +14,7 @@ protocol ShapeFactory { enum ShapeFactoryCluster: ShapeFactory { static func makeShape(with type: Shapable.Type) -> Shapable? { switch type { - case is Rectangle.Type: + case is ColoredRectangle.Type: return RectangleFactory.makeShape() default: return nil diff --git a/DrawingApp/DrawingApp/View/Components/RectangleView.swift b/DrawingApp/DrawingApp/View/Components/ColoredRectangleView.swift similarity index 86% rename from DrawingApp/DrawingApp/View/Components/RectangleView.swift rename to DrawingApp/DrawingApp/View/Components/ColoredRectangleView.swift index 37ba9ad6..ff5e8e35 100644 --- a/DrawingApp/DrawingApp/View/Components/RectangleView.swift +++ b/DrawingApp/DrawingApp/View/Components/ColoredRectangleView.swift @@ -7,14 +7,14 @@ import UIKit -protocol BackgroundColorable { +protocol BackgroundViewable { func setBackgroundColor(color: Color, alpha: Alpha) func setBackgroundColor(with color: Color) func setBackgroundColor(with alpha: CGFloat) func setBackgroundColor(with alpha: Alpha) } -class RectangleView: UIView { +class ColoredRectangleView: UIView { // MARK: - Initialisers override init(frame: CGRect) { super.init(frame: frame) @@ -24,21 +24,21 @@ class RectangleView: UIView { super.init(coder: coder) } - convenience init(with rectangle: Rectangle) { + convenience init(with rectangle: ColoredRectangle) { self.init(frame: CGRect(with: rectangle)) self.setBackgroundColor(color: rectangle.backgroundColor, alpha: rectangle.alpha) } } // MARK: - ShapeViewable Protocol -extension RectangleView: ShapeViewable { +extension ColoredRectangleView: ShapeViewable { func setAlpha(_ alpha: Alpha) { self.setBackgroundColor(with: alpha) } } // MARK: - RectangleViewable -extension RectangleView: BackgroundColorable { +extension ColoredRectangleView: BackgroundViewable { func setBackgroundColor(color: Color, alpha: Alpha) { self.backgroundColor = UIColor(with: color, alpha: alpha) } From 9d61c70c013cc8eab72c6a2c6dbd081ac407522c Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Fri, 18 Mar 2022 13:02:56 +0900 Subject: [PATCH 10/13] refactor: Abstarct variables of model in ViewController --- .../Controller/ViewController.swift | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/DrawingApp/DrawingApp/Controller/ViewController.swift b/DrawingApp/DrawingApp/Controller/ViewController.swift index 8e818afe..b8071c49 100644 --- a/DrawingApp/DrawingApp/Controller/ViewController.swift +++ b/DrawingApp/DrawingApp/Controller/ViewController.swift @@ -17,7 +17,7 @@ class ViewController: UIViewController { // MARK: - Property for Model private let plane = Plane() - private var rectangleMap = [Shape: ShapeViewable]() + private var shapeMap = [Shape: ShapeViewable]() // MARK: - View Life Cycle Methods override func viewDidLoad() { @@ -34,16 +34,16 @@ class ViewController: UIViewController { private func setObservers() { NotificationCenter.default.addObserver(forName: .RectangleModelDidCreated, object: nil, queue: .main, using: { notification in - guard let rectangle = notification.object as? Shape else { return } - self.createRectangleView(ofClass: ColoredRectangleView.self, with: rectangle) + guard let shape = notification.object as? Shape else { return } + self.createShapeView(ofClass: ColoredRectangleView.self, with: shape) }) - NotificationCenter.default.addObserver(forName: .RectangleModelDidUpdated, object: nil, queue: .main, using: self.rectangleDataDidChanged) + NotificationCenter.default.addObserver(forName: .RectangleModelDidUpdated, object: nil, queue: .main, using: self.shapeModelDidChanged) NotificationCenter.default.addObserver(forName: .ImageRectangleModelDidCreated, object: nil, queue: .main, using: { notification in - guard let rectangle = notification.object as? Shape else { return } - self.createRectangleView(ofClass: ImageRectangleView.self, with: rectangle) + guard let shape = notification.object as? Shape else { return } + self.createShapeView(ofClass: ImageRectangleView.self, with: shape) }) - NotificationCenter.default.addObserver(forName: .RectangleModelDidUpdated, object: nil, queue: .main, using: self.rectangleDataDidChanged) + NotificationCenter.default.addObserver(forName: .RectangleModelDidUpdated, object: nil, queue: .main, using: self.shapeModelDidChanged) NotificationCenter.default.addObserver(forName: .PlaneDidSelectItem, object: self.plane, queue: .main, using: self.planeDidSelectItem) NotificationCenter.default.addObserver(forName: .PlaneDidUnselectItem, object: self.plane, queue: .main, using: self.planeDidUnselectItem) @@ -57,8 +57,9 @@ extension ViewController: PlaneViewDelegate { } func planeViewDidPressRectangleAddButton() { - let rectangle = RectangleFactory.makeRandomRectangle() - plane.append(item: rectangle) + // TODO: Factory 한단계 더 추상화 (ShapeFactory) + let shape = RectangleFactory.makeRandomRectangle() + plane.append(item: shape) } func planeViewDidPressImageAddButton() { @@ -78,61 +79,62 @@ extension ViewController: PlaneViewDelegate { // MARK: - ControlPanelView To ViewController extension ViewController: ControlPanelViewDelegate { func controlPanelDidPressColorButton() { - guard let rectangle = self.plane.currentItem as? BackgroundAdaptable else { return } + guard let shape = self.plane.currentItem as? BackgroundAdaptable else { return } let color = ColorFactory.makeTypeRandomly() - rectangle.setBackgroundColor(color) + shape.setBackgroundColor(color) } func controlPanelDidMoveAlphaSlider(_ sender: UISlider) { let value = sender.value.toFixed(digits: 1) - guard let rectangle = self.plane.currentItem as? AlphaAdaptable else { return } + guard let shape = self.plane.currentItem as? AlphaAdaptable else { return } guard let alpha = Alpha(rawValue: value) else { return } - rectangle.setAlpha(alpha) + shape.setAlpha(alpha) } } // MARK: - RectangleView To ViewController extension ViewController { - @objc private func handleOnTapRectangleView(_ sender: UITapGestureRecognizer) { + @objc private func handleOnTapShapeView(_ sender: UITapGestureRecognizer) { guard sender.state == .ended else { return } let point = sender.location(in: self.planeView).convert(using: Point.self) - guard let rectangle = self.plane.findItemBy(point: point) else { return } + guard let shape = self.plane.findItemBy(point: point) else { return } - self.plane.selectItem(id: rectangle.id) + self.plane.selectItem(id: shape.id) } } // MARK: - Rectangle Model To ViewController extension ViewController { - private func createRectangleView(ofClass Class: ShapeViewable.Type, with rectangle: Shape) { - guard let rectangleView = RectangleViewFactory.makeView(ofClass: Class, with: rectangle) else { return } + private func createShapeView(ofClass Class: ShapeViewable.Type, with shape: Shape) { + // TODO: Factory 한단계 더 추상화 (ShapeFactory) + guard let shapeView = RectangleViewFactory.makeView(ofClass: Class, with: shape) else { return } - let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleOnTapRectangleView)) + let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleOnTapShapeView)) - rectangleView.addGestureRecognizer(tap) - rectangleView.animateScale(CGFloat(1.2), duration: 0.15, delay: 0) + shapeView.addGestureRecognizer(tap) + shapeView.animateScale(CGFloat(1.2), duration: 0.15, delay: 0) - self.planeView.addSubview(rectangleView) - self.rectangleMap.updateValue(rectangleView, forKey: rectangle) + self.planeView.addSubview(shapeView) + self.shapeMap.updateValue(shapeView, forKey: shape) } - private func rectangleDataDidChanged(_ notification: Notification) { - guard let rectangle = self.plane.currentItem as? AlphaAdaptableShape else { return } - guard let rectangleView = self.rectangleMap[rectangle] else { return } + private func shapeModelDidChanged(_ notification: Notification) { + guard let shape = self.plane.currentItem as? AlphaAdaptableShape else { return } + guard let shapeView = self.shapeMap[shape] else { return } if let alpha = notification.userInfo?[NotificationKey.updated] as? Alpha { - rectangleView.setAlpha(alpha) + shapeView.setAlpha(alpha) } - if let colorableView = rectangleView as? BackgroundViewable, let color = notification.userInfo?[NotificationKey.updated] as? Color { - colorableView.setBackgroundColor(color: color, alpha: rectangle.alpha) - self.controlPanelView.setColorButtonTitle(title: rectangleView.backgroundColor?.toHexString() ?? "None") + if let colorableShapeView = shapeView as? BackgroundViewable, let color = notification.userInfo?[NotificationKey.updated] as? Color { + colorableShapeView.setBackgroundColor(color: color, alpha: shape.alpha) + self.controlPanelView.setColorButtonTitle(title: shapeView.backgroundColor?.toHexString() ?? "None") } } } @@ -140,28 +142,28 @@ extension ViewController { // MARK: - Plane Model to ViewController extension ViewController { private func planeDidSelectItem(_ notification: Notification) { - guard let rectangle = notification.userInfo?[Plane.NotificationKey.select] as? AlphaAdaptableShape else { return } - guard let rectangleView = self.rectangleMap[rectangle] else { return } + guard let shape = notification.userInfo?[Plane.NotificationKey.select] as? AlphaAdaptableShape else { return } + guard let shapeView = self.shapeMap[shape] else { return } - rectangleView.setBorder(width: 2, color: .blue) + shapeView.setBorder(width: 2, color: .blue) - if let colorableShape = rectangle as? BackgroundAdaptable { + if let colorableShape = shape as? BackgroundAdaptable { let hexString = Color.toHexString(colorableShape.backgroundColor) self.controlPanelView.setColorButtonTitle(title: hexString) } - let isConformed = (rectangle as? ImageAdaptableShape) == nil + let isConformed = (shape as? ImageAdaptableShape) == nil self.controlPanelView.setColorButtonControllable(enable: isConformed) self.controlPanelView.setAlphaSliderControllable(enable: true) - self.controlPanelView.setAlphaSliderValue(value: rectangle.alpha) + self.controlPanelView.setAlphaSliderValue(value: shape.alpha) } private func planeDidUnselectItem(_ notification: Notification) { - guard let rectangle = notification.userInfo?[Plane.NotificationKey.unselect] as? Shape else { return } - guard let rectangleView = self.rectangleMap[rectangle] else { return } + guard let shape = notification.userInfo?[Plane.NotificationKey.unselect] as? Shape else { return } + guard let shapeView = self.shapeMap[shape] else { return } - rectangleView.removeBorder() + shapeView.removeBorder() self.controlPanelView.reset() } @@ -180,8 +182,8 @@ extension ViewController: PHPickerViewControllerDelegate { itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in guard let url = url, error == nil else { return } - let imageRectangle = RectangleFactory.makeRandomRectangle(with: url) - self.plane.append(item: imageRectangle) + let imageShape = RectangleFactory.makeRandomRectangle(with: url) + self.plane.append(item: imageShape) } } } From c7597e417c656e4de4004ff7e727d23737035cf9 Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Fri, 18 Mar 2022 14:41:15 +0900 Subject: [PATCH 11/13] refactor: Improve level of polymorphism with protocols --- .../DrawingApp.xcodeproj/project.pbxproj | 10 ++++++ .../Controller/ViewController.swift | 32 +++++++++++-------- .../DrawingApp/Model/ColoredRectangle.swift | 4 +-- .../DrawingApp/Model/ImageRectangle.swift | 6 ++-- DrawingApp/DrawingApp/Model/Notifiable.swift | 2 +- DrawingApp/DrawingApp/Model/Shape.swift | 9 +++--- .../DrawingApp/Utility/RectangleFactory.swift | 10 +++--- 7 files changed, 44 insertions(+), 29 deletions(-) diff --git a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj index dce7ad33..99068f15 100644 --- a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj +++ b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj @@ -57,6 +57,11 @@ E49F7CDD27E30ED900BB19A2 /* Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA727CEFEB90096810A /* Alpha.swift */; }; E49F7CDE27E30ED900BB19A2 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4827D09C0000815795 /* Plane.swift */; }; E4BA038227E42781005BE75C /* Notifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038127E42781005BE75C /* Notifiable.swift */; }; + E4BA038327E43C1E005BE75C /* AlphaAdaptable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DCA027E4251B00A317AF /* AlphaAdaptable.swift */; }; + E4BA038427E43C1E005BE75C /* BackgroundAdaptable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DCA227E4262E00A317AF /* BackgroundAdaptable.swift */; }; + E4BA038527E43C1E005BE75C /* RectangleBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */; }; + E4BA038627E43C1E005BE75C /* Notifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038127E42781005BE75C /* Notifiable.swift */; }; + E4BA038727E43C1E005BE75C /* Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DC9C27E41B2400A317AF /* Shape.swift */; }; E4C9622D27D975A3005DBD2B /* Hashable+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */; }; E4C9623127D97D7F005DBD2B /* Equatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */; }; E4D3DFA227CEFE490096810A /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA127CEFE490096810A /* Point.swift */; }; @@ -434,6 +439,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E4BA038327E43C1E005BE75C /* AlphaAdaptable.swift in Sources */, + E4BA038427E43C1E005BE75C /* BackgroundAdaptable.swift in Sources */, + E4BA038527E43C1E005BE75C /* RectangleBuildable.swift in Sources */, + E4BA038627E43C1E005BE75C /* Notifiable.swift in Sources */, + E4BA038727E43C1E005BE75C /* Shape.swift in Sources */, E49F7CC427E30ED800BB19A2 /* Notification.Name+Extensions.swift in Sources */, E49F7CC527E30ED800BB19A2 /* Hashable+Hash.swift in Sources */, E49F7CC627E30ED800BB19A2 /* Equatable+Extensions.swift in Sources */, diff --git a/DrawingApp/DrawingApp/Controller/ViewController.swift b/DrawingApp/DrawingApp/Controller/ViewController.swift index b8071c49..d1bfdfd5 100644 --- a/DrawingApp/DrawingApp/Controller/ViewController.swift +++ b/DrawingApp/DrawingApp/Controller/ViewController.swift @@ -8,8 +8,6 @@ import UIKit import PhotosUI -typealias AlphaAdaptableShape = Shape & AlphaAdaptable - class ViewController: UIViewController { // MARK: - Properties for View @IBOutlet weak var controlPanelView: ControlPanelView! @@ -58,7 +56,7 @@ extension ViewController: PlaneViewDelegate { func planeViewDidPressRectangleAddButton() { // TODO: Factory 한단계 더 추상화 (ShapeFactory) - let shape = RectangleFactory.makeRandomRectangle() + guard let shape = RectangleFactory.makeRandomRectangle() as? NotifiableShape else { return } plane.append(item: shape) } @@ -125,14 +123,19 @@ extension ViewController { } private func shapeModelDidChanged(_ notification: Notification) { - guard let shape = self.plane.currentItem as? AlphaAdaptableShape else { return } + guard let shape = self.plane.currentItem as? Shape else { return } guard let shapeView = self.shapeMap[shape] else { return } - if let alpha = notification.userInfo?[NotificationKey.updated] as? Alpha { + let color = notification.userInfo?[NotificationKey.updated] as? Color + let alpha = notification.userInfo?[NotificationKey.updated] as? Alpha + let alphaAdaptableShape = shape as? AlphaAdaptableShape + let colorableShapeView = shapeView as? BackgroundViewable + + if let _ = alphaAdaptableShape, let alpha = alpha { shapeView.setAlpha(alpha) } - - if let colorableShapeView = shapeView as? BackgroundViewable, let color = notification.userInfo?[NotificationKey.updated] as? Color { + + if let shape = alphaAdaptableShape, let colorableShapeView = colorableShapeView, let color = color { colorableShapeView.setBackgroundColor(color: color, alpha: shape.alpha) self.controlPanelView.setColorButtonTitle(title: shapeView.backgroundColor?.toHexString() ?? "None") } @@ -142,21 +145,24 @@ extension ViewController { // MARK: - Plane Model to ViewController extension ViewController { private func planeDidSelectItem(_ notification: Notification) { - guard let shape = notification.userInfo?[Plane.NotificationKey.select] as? AlphaAdaptableShape else { return } + guard let shape = notification.userInfo?[Plane.NotificationKey.select] as? Shape else { return } guard let shapeView = self.shapeMap[shape] else { return } shapeView.setBorder(width: 2, color: .blue) - if let colorableShape = shape as? BackgroundAdaptable { - let hexString = Color.toHexString(colorableShape.backgroundColor) + if let backgroundAdaptableShape = shape as? BackgroundAdaptable { + let hexString = Color.toHexString(backgroundAdaptableShape.backgroundColor) self.controlPanelView.setColorButtonTitle(title: hexString) } - let isConformed = (shape as? ImageAdaptableShape) == nil + if let AlphaAdaptableShape = shape as? AlphaAdaptable { + self.controlPanelView.setAlphaSliderValue(value: AlphaAdaptableShape.alpha) + } + + let isConformed = (shape as? ImageAdaptable) == nil self.controlPanelView.setColorButtonControllable(enable: isConformed) self.controlPanelView.setAlphaSliderControllable(enable: true) - self.controlPanelView.setAlphaSliderValue(value: shape.alpha) } private func planeDidUnselectItem(_ notification: Notification) { @@ -182,7 +188,7 @@ extension ViewController: PHPickerViewControllerDelegate { itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in guard let url = url, error == nil else { return } - let imageShape = RectangleFactory.makeRandomRectangle(with: url) + guard let imageShape = RectangleFactory.makeRandomRectangle(with: url) as? NotifiableShape else { return } self.plane.append(item: imageShape) } } diff --git a/DrawingApp/DrawingApp/Model/ColoredRectangle.swift b/DrawingApp/DrawingApp/Model/ColoredRectangle.swift index bfa739b7..cdd1d7fd 100644 --- a/DrawingApp/DrawingApp/Model/ColoredRectangle.swift +++ b/DrawingApp/DrawingApp/Model/ColoredRectangle.swift @@ -9,7 +9,7 @@ import Foundation typealias BackgroundColorControllable = BackgroundAdaptable & AlphaAdaptable -class ColoredRectangle: Shape, BackgroundColorControllable { +class ColoredRectangle: NotifiableShape, BackgroundColorControllable { // MARK: - Properties private(set) var backgroundColor: Color { didSet { @@ -50,7 +50,7 @@ class ColoredRectangle: Shape, BackgroundColorControllable { self.alpha = alpha } - override func notifyDidCreated() { + func notifyDidCreated() { NotificationCenter.default.post(name: .RectangleModelDidCreated, object: self) } diff --git a/DrawingApp/DrawingApp/Model/ImageRectangle.swift b/DrawingApp/DrawingApp/Model/ImageRectangle.swift index d0dfb2f6..0eadce60 100644 --- a/DrawingApp/DrawingApp/Model/ImageRectangle.swift +++ b/DrawingApp/DrawingApp/Model/ImageRectangle.swift @@ -11,9 +11,7 @@ protocol ImageAdaptable { func setImagePath(with url: URL) } -typealias ImageAdaptableShape = AlphaAdaptable & ImageAdaptable - -class ImageRectangle: Shape, ImageAdaptableShape { +class ImageRectangle: NotifiableShape, ImageAdaptable, AlphaAdaptable { private(set) var alpha: Alpha { didSet { self.notifyDidUpdated(key: .updated, data: self.alpha) @@ -52,7 +50,7 @@ class ImageRectangle: Shape, ImageAdaptableShape { return Convertor.init(x: self.origin.x, y: self.origin.y, width: self.size.width, height: self.size.height) } - override func notifyDidCreated() { + func notifyDidCreated() { NotificationCenter.default.post(name: .ImageRectangleModelDidCreated, object: self) } diff --git a/DrawingApp/DrawingApp/Model/Notifiable.swift b/DrawingApp/DrawingApp/Model/Notifiable.swift index cbe666fd..8282bbd4 100644 --- a/DrawingApp/DrawingApp/Model/Notifiable.swift +++ b/DrawingApp/DrawingApp/Model/Notifiable.swift @@ -14,5 +14,5 @@ enum NotificationKey { protocol Notifiable: AnyObject { func notifyDidCreated() - func notifyDidUpdate(key: NotificationKey, data: Any) + func notifyDidUpdated(key: NotificationKey, data: Any) } diff --git a/DrawingApp/DrawingApp/Model/Shape.swift b/DrawingApp/DrawingApp/Model/Shape.swift index b46f62c2..fb61f115 100644 --- a/DrawingApp/DrawingApp/Model/Shape.swift +++ b/DrawingApp/DrawingApp/Model/Shape.swift @@ -7,8 +7,12 @@ import Foundation +typealias NotifiableShape = Shape & Notifiable +typealias AlphaAdaptableShape = Shape & AlphaAdaptable +typealias BackgroundAdaptableShape = Shape & BackgroundAdaptable + /// 추상 클래스 -class Shape: Shapable, Notifiable, Hashable { +class Shape: Shapable, Hashable { let id: String let size: Size let origin: Point @@ -18,7 +22,4 @@ class Shape: Shapable, Notifiable, Hashable { self.origin = origin self.size = size } - - func notifyDidCreated() {} - func notifyDidUpdate(key: T, data: Any) {} } diff --git a/DrawingApp/DrawingApp/Utility/RectangleFactory.swift b/DrawingApp/DrawingApp/Utility/RectangleFactory.swift index ae3dc442..a4f768fd 100644 --- a/DrawingApp/DrawingApp/Utility/RectangleFactory.swift +++ b/DrawingApp/DrawingApp/Utility/RectangleFactory.swift @@ -8,16 +8,16 @@ import Foundation protocol ShapeBuilder { - static func makeShape() -> Shape + static func makeShape() -> Shapable } enum RectangleFactory: ShapeBuilder { - static func makeRectangle(x: Double = 0, y: Double = 0, width: Double = 30, height: Double = 30) -> Shape { + static func makeRectangle(x: Double = 0, y: Double = 0, width: Double = 30, height: Double = 30) -> Shapable { let id = IdentifierFactory.makeTypeRandomly() return ColoredRectangle(id: id, x: x, y: y, width: width, height: height, color: .white, alpha: .opaque) } - static func makeRandomRectangle() -> Shape { + static func makeRandomRectangle() -> Shapable { let id = IdentifierFactory.makeTypeRandomly() let point = PointFactory.makeTypeRandomly() let size = SizeFactory.makeType(width: 150, height: 120) @@ -27,7 +27,7 @@ enum RectangleFactory: ShapeBuilder { return ColoredRectangle(id: id, origin: point, size: size, color: color, alpha: alpha) } - static func makeRandomRectangle(with image: URL) -> Shape { + static func makeRandomRectangle(with image: URL) -> Shapable { let id = IdentifierFactory.makeTypeRandomly() let point = PointFactory.makeTypeRandomly() let size = SizeFactory.makeType(width: 150, height: 120) @@ -35,7 +35,7 @@ enum RectangleFactory: ShapeBuilder { return ImageRectangle(id: id, origin: point, size: size, image: image) } - static func makeShape() -> Shape { + static func makeShape() -> Shapable { return self.makeRectangle() } } From bc31471e1d9973c212ced99b87ffcb53a34b9301 Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Fri, 18 Mar 2022 15:17:55 +0900 Subject: [PATCH 12/13] refactor: Refactor factories and model protocols --- .../DrawingApp.xcodeproj/project.pbxproj | 30 +++++++++++++---- .../Controller/ViewController.swift | 10 +++--- .../DrawingApp/Model/ColoredRectangle.swift | 2 +- .../DrawingApp/Model/ImageRectangle.swift | 6 ++-- .../{ => Protocols}/AlphaAdaptable.swift | 1 - .../{ => Protocols}/BackgroundAdaptable.swift | 0 .../Model/Protocols/ImageAdaptable.swift | 12 +++++++ .../Model/{ => Protocols}/Notifiable.swift | 0 .../{ => Protocols}/RectangleBuildable.swift | 0 .../Model/{ => Protocols}/Shapable.swift | 0 DrawingApp/DrawingApp/Model/Shape.swift | 1 + .../DrawingApp/Utility/RectangleFactory.swift | 32 ++++++++++++------- .../DrawingApp/Utility/ShapeBuildable.swift | 12 +++++++ .../Utility/ShapeFactoryCluster.swift | 12 +++---- DrawingApp/DrawingApp/View/PlaneView.swift | 20 ++++++------ 15 files changed, 92 insertions(+), 46 deletions(-) rename DrawingApp/DrawingApp/Model/{ => Protocols}/AlphaAdaptable.swift (97%) rename DrawingApp/DrawingApp/Model/{ => Protocols}/BackgroundAdaptable.swift (100%) create mode 100644 DrawingApp/DrawingApp/Model/Protocols/ImageAdaptable.swift rename DrawingApp/DrawingApp/Model/{ => Protocols}/Notifiable.swift (100%) rename DrawingApp/DrawingApp/Model/{ => Protocols}/RectangleBuildable.swift (100%) rename DrawingApp/DrawingApp/Model/{ => Protocols}/Shapable.swift (100%) create mode 100644 DrawingApp/DrawingApp/Utility/ShapeBuildable.swift diff --git a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj index 99068f15..c3633d2b 100644 --- a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj +++ b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj @@ -62,6 +62,9 @@ E4BA038527E43C1E005BE75C /* RectangleBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */; }; E4BA038627E43C1E005BE75C /* Notifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038127E42781005BE75C /* Notifiable.swift */; }; E4BA038727E43C1E005BE75C /* Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DC9C27E41B2400A317AF /* Shape.swift */; }; + E4BA038927E454EA005BE75C /* ShapeBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038827E454EA005BE75C /* ShapeBuildable.swift */; }; + E4BA038A27E454ED005BE75C /* ShapeBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038827E454EA005BE75C /* ShapeBuildable.swift */; }; + E4BA038D27E45864005BE75C /* ImageAdaptable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038C27E45864005BE75C /* ImageAdaptable.swift */; }; E4C9622D27D975A3005DBD2B /* Hashable+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */; }; E4C9623127D97D7F005DBD2B /* Equatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */; }; E4D3DFA227CEFE490096810A /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA127CEFE490096810A /* Point.swift */; }; @@ -113,6 +116,8 @@ E481266F27CCF8E100A3FFF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E49679E727D71AF900A9BA27 /* SimpleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleLabel.swift; sourceTree = ""; }; E4BA038127E42781005BE75C /* Notifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifiable.swift; sourceTree = ""; }; + E4BA038827E454EA005BE75C /* ShapeBuildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeBuildable.swift; sourceTree = ""; }; + E4BA038C27E45864005BE75C /* ImageAdaptable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAdaptable.swift; sourceTree = ""; }; E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Hashable+Hash.swift"; sourceTree = ""; }; E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Equatable+Extensions.swift"; sourceTree = ""; }; E4D3DFA127CEFE490096810A /* Point.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Point.swift; sourceTree = ""; }; @@ -171,11 +176,7 @@ E4630C2227CDD22900644B70 /* Model */ = { isa = PBXGroup; children = ( - E453DCA027E4251B00A317AF /* AlphaAdaptable.swift */, - E453DCA227E4262E00A317AF /* BackgroundAdaptable.swift */, - E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */, - E4BA038127E42781005BE75C /* Notifiable.swift */, - E4641B4C27D0A00F00815795 /* Shapable.swift */, + E4BA038B27E45837005BE75C /* Protocols */, E453DC9C27E41B2400A317AF /* Shape.swift */, E4E8082E27CDF0C100F4C062 /* ColoredRectangle.swift */, E4D6B57427E079EF005075B6 /* ImageRectangle.swift */, @@ -220,8 +221,9 @@ isa = PBXGroup; children = ( E48109E727D61E68007B0E3F /* Extensions */, - E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */, + E4BA038827E454EA005BE75C /* ShapeBuildable.swift */, E435F06427E173F7005AD99C /* RectangleViewFactory.swift */, + E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */, E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */, E4641B4F27D0A80200815795 /* IdentifierFactory.swift */, E4E637B027D1E70500B90B24 /* PointFactory.swift */, @@ -283,6 +285,19 @@ path = DrawingApp; sourceTree = ""; }; + E4BA038B27E45837005BE75C /* Protocols */ = { + isa = PBXGroup; + children = ( + E453DCA027E4251B00A317AF /* AlphaAdaptable.swift */, + E4BA038C27E45864005BE75C /* ImageAdaptable.swift */, + E453DCA227E4262E00A317AF /* BackgroundAdaptable.swift */, + E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */, + E4BA038127E42781005BE75C /* Notifiable.swift */, + E4641B4C27D0A00F00815795 /* Shapable.swift */, + ); + path = Protocols; + sourceTree = ""; + }; E4E8083927CE0EBB00F4C062 /* DrawingAppTests */ = { isa = PBXGroup; children = ( @@ -402,6 +417,7 @@ E4C9622D27D975A3005DBD2B /* Hashable+Hash.swift in Sources */, E4D3DFA627CEFE7F0096810A /* Color.swift in Sources */, E453DC9F27E41FC200A317AF /* RectangleBuildable.swift in Sources */, + E4BA038D27E45864005BE75C /* ImageAdaptable.swift in Sources */, E435F06527E173F7005AD99C /* RectangleViewFactory.swift in Sources */, E48109E927D61E93007B0E3F /* UIColor+Extensions.swift in Sources */, E4D3DFA827CEFEB90096810A /* Alpha.swift in Sources */, @@ -429,6 +445,7 @@ E4641B4927D09C0000815795 /* Plane.swift in Sources */, E4E637B127D1E70500B90B24 /* PointFactory.swift in Sources */, E4D6B57327DF3E3E005075B6 /* Float+ToFix.swift in Sources */, + E4BA038927E454EA005BE75C /* ShapeBuildable.swift in Sources */, E4F20E8327D883DD00E24DE8 /* CGSize+Extensions.swift in Sources */, E48109E427D61E11007B0E3F /* String+Alphanumeric.swift in Sources */, E453DCA127E4251B00A317AF /* AlphaAdaptable.swift in Sources */, @@ -455,6 +472,7 @@ E49F7CCC27E30ED800BB19A2 /* CGRect+Extensions.swift in Sources */, E49F7CCD27E30ED800BB19A2 /* CGPoint+Extensions.swift in Sources */, E49F7CCE27E30ED800BB19A2 /* CGSize+Extensions.swift in Sources */, + E4BA038A27E454ED005BE75C /* ShapeBuildable.swift in Sources */, E49F7CCF27E30ED800BB19A2 /* ShapeFactoryCluster.swift in Sources */, E49F7CD127E30ED800BB19A2 /* RectangleFactory.swift in Sources */, E49F7CD227E30ED900BB19A2 /* IdentifierFactory.swift in Sources */, diff --git a/DrawingApp/DrawingApp/Controller/ViewController.swift b/DrawingApp/DrawingApp/Controller/ViewController.swift index d1bfdfd5..e19f4bdf 100644 --- a/DrawingApp/DrawingApp/Controller/ViewController.swift +++ b/DrawingApp/DrawingApp/Controller/ViewController.swift @@ -54,13 +54,12 @@ extension ViewController: PlaneViewDelegate { self.plane.unselectItem() } - func planeViewDidPressRectangleAddButton() { - // TODO: Factory 한단계 더 추상화 (ShapeFactory) - guard let shape = RectangleFactory.makeRandomRectangle() as? NotifiableShape else { return } + func planeViewDidPressColoredRectangleAddButton() { + let shape = RectangleFactory.makeRandomRectangle() plane.append(item: shape) } - func planeViewDidPressImageAddButton() { + func planeViewDidPressImageRectangleAddButton() { var configuration = PHPickerConfiguration(photoLibrary: .shared()) configuration.filter = .images @@ -188,7 +187,8 @@ extension ViewController: PHPickerViewControllerDelegate { itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in guard let url = url, error == nil else { return } - guard let imageShape = RectangleFactory.makeRandomRectangle(with: url) as? NotifiableShape else { return } + let imageShape = RectangleFactory.makeRandomRectangle(with: url) + self.plane.append(item: imageShape) } } diff --git a/DrawingApp/DrawingApp/Model/ColoredRectangle.swift b/DrawingApp/DrawingApp/Model/ColoredRectangle.swift index cdd1d7fd..abba61a5 100644 --- a/DrawingApp/DrawingApp/Model/ColoredRectangle.swift +++ b/DrawingApp/DrawingApp/Model/ColoredRectangle.swift @@ -9,7 +9,7 @@ import Foundation typealias BackgroundColorControllable = BackgroundAdaptable & AlphaAdaptable -class ColoredRectangle: NotifiableShape, BackgroundColorControllable { +class ColoredRectangle: Shape, BackgroundColorControllable, Notifiable { // MARK: - Properties private(set) var backgroundColor: Color { didSet { diff --git a/DrawingApp/DrawingApp/Model/ImageRectangle.swift b/DrawingApp/DrawingApp/Model/ImageRectangle.swift index 0eadce60..90b5a7d0 100644 --- a/DrawingApp/DrawingApp/Model/ImageRectangle.swift +++ b/DrawingApp/DrawingApp/Model/ImageRectangle.swift @@ -7,11 +7,9 @@ import Foundation -protocol ImageAdaptable { - func setImagePath(with url: URL) -} +typealias ImageControllable = ImageAdaptable & AlphaAdaptable -class ImageRectangle: NotifiableShape, ImageAdaptable, AlphaAdaptable { +class ImageRectangle: Shape, ImageControllable, Notifiable { private(set) var alpha: Alpha { didSet { self.notifyDidUpdated(key: .updated, data: self.alpha) diff --git a/DrawingApp/DrawingApp/Model/AlphaAdaptable.swift b/DrawingApp/DrawingApp/Model/Protocols/AlphaAdaptable.swift similarity index 97% rename from DrawingApp/DrawingApp/Model/AlphaAdaptable.swift rename to DrawingApp/DrawingApp/Model/Protocols/AlphaAdaptable.swift index 039d034e..5029670c 100644 --- a/DrawingApp/DrawingApp/Model/AlphaAdaptable.swift +++ b/DrawingApp/DrawingApp/Model/Protocols/AlphaAdaptable.swift @@ -9,6 +9,5 @@ import Foundation protocol AlphaAdaptable { var alpha: Alpha { get } - func setAlpha(_ alpha: Alpha) } diff --git a/DrawingApp/DrawingApp/Model/BackgroundAdaptable.swift b/DrawingApp/DrawingApp/Model/Protocols/BackgroundAdaptable.swift similarity index 100% rename from DrawingApp/DrawingApp/Model/BackgroundAdaptable.swift rename to DrawingApp/DrawingApp/Model/Protocols/BackgroundAdaptable.swift diff --git a/DrawingApp/DrawingApp/Model/Protocols/ImageAdaptable.swift b/DrawingApp/DrawingApp/Model/Protocols/ImageAdaptable.swift new file mode 100644 index 00000000..32a8917a --- /dev/null +++ b/DrawingApp/DrawingApp/Model/Protocols/ImageAdaptable.swift @@ -0,0 +1,12 @@ +// +// ImageAdaptable.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/18. +// + +import Foundation + +protocol ImageAdaptable { + func setImagePath(with url: URL) +} diff --git a/DrawingApp/DrawingApp/Model/Notifiable.swift b/DrawingApp/DrawingApp/Model/Protocols/Notifiable.swift similarity index 100% rename from DrawingApp/DrawingApp/Model/Notifiable.swift rename to DrawingApp/DrawingApp/Model/Protocols/Notifiable.swift diff --git a/DrawingApp/DrawingApp/Model/RectangleBuildable.swift b/DrawingApp/DrawingApp/Model/Protocols/RectangleBuildable.swift similarity index 100% rename from DrawingApp/DrawingApp/Model/RectangleBuildable.swift rename to DrawingApp/DrawingApp/Model/Protocols/RectangleBuildable.swift diff --git a/DrawingApp/DrawingApp/Model/Shapable.swift b/DrawingApp/DrawingApp/Model/Protocols/Shapable.swift similarity index 100% rename from DrawingApp/DrawingApp/Model/Shapable.swift rename to DrawingApp/DrawingApp/Model/Protocols/Shapable.swift diff --git a/DrawingApp/DrawingApp/Model/Shape.swift b/DrawingApp/DrawingApp/Model/Shape.swift index fb61f115..29ba4684 100644 --- a/DrawingApp/DrawingApp/Model/Shape.swift +++ b/DrawingApp/DrawingApp/Model/Shape.swift @@ -9,6 +9,7 @@ import Foundation typealias NotifiableShape = Shape & Notifiable typealias AlphaAdaptableShape = Shape & AlphaAdaptable +typealias ImageAdaptableShape = Shape & ImageAdaptable typealias BackgroundAdaptableShape = Shape & BackgroundAdaptable /// 추상 클래스 diff --git a/DrawingApp/DrawingApp/Utility/RectangleFactory.swift b/DrawingApp/DrawingApp/Utility/RectangleFactory.swift index a4f768fd..c69c979b 100644 --- a/DrawingApp/DrawingApp/Utility/RectangleFactory.swift +++ b/DrawingApp/DrawingApp/Utility/RectangleFactory.swift @@ -7,17 +7,29 @@ import Foundation -protocol ShapeBuilder { - static func makeShape() -> Shapable -} - -enum RectangleFactory: ShapeBuilder { - static func makeRectangle(x: Double = 0, y: Double = 0, width: Double = 30, height: Double = 30) -> Shapable { +enum RectangleFactory: ShapeBuildable { + static func makeShape(ofClass type: Shapable.Type) -> Shapable? { + switch type { + case is ColoredRectangle.Type: + return self.makeColoredRectangle() + case is ImageRectangle.Type: + return self.makeImageRectangle() + default: + return nil + } + } + + static func makeColoredRectangle(x: Double = 0, y: Double = 0, width: Double = 150, height: Double = 120) -> ColoredRectangle { let id = IdentifierFactory.makeTypeRandomly() return ColoredRectangle(id: id, x: x, y: y, width: width, height: height, color: .white, alpha: .opaque) } - static func makeRandomRectangle() -> Shapable { + static func makeImageRectangle(x: Double = 0, y: Double = 0, width: Double = 150, height: Double = 120, url: URL? = nil) -> ImageRectangle { + let id = IdentifierFactory.makeTypeRandomly() + return ImageRectangle(id: id, x: x, y: y, width: width, height: height, image: url) + } + + static func makeRandomRectangle() -> ColoredRectangle { let id = IdentifierFactory.makeTypeRandomly() let point = PointFactory.makeTypeRandomly() let size = SizeFactory.makeType(width: 150, height: 120) @@ -27,15 +39,11 @@ enum RectangleFactory: ShapeBuilder { return ColoredRectangle(id: id, origin: point, size: size, color: color, alpha: alpha) } - static func makeRandomRectangle(with image: URL) -> Shapable { + static func makeRandomRectangle(with image: URL) -> ImageRectangle { let id = IdentifierFactory.makeTypeRandomly() let point = PointFactory.makeTypeRandomly() let size = SizeFactory.makeType(width: 150, height: 120) return ImageRectangle(id: id, origin: point, size: size, image: image) } - - static func makeShape() -> Shapable { - return self.makeRectangle() - } } diff --git a/DrawingApp/DrawingApp/Utility/ShapeBuildable.swift b/DrawingApp/DrawingApp/Utility/ShapeBuildable.swift new file mode 100644 index 00000000..ddb83b98 --- /dev/null +++ b/DrawingApp/DrawingApp/Utility/ShapeBuildable.swift @@ -0,0 +1,12 @@ +// +// ShapeBuildable.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/18. +// + +import Foundation + +protocol ShapeBuildable { + static func makeShape(ofClass type: Shapable.Type) -> Shapable? +} diff --git a/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift b/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift index 5556bba2..330fe6aa 100644 --- a/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift +++ b/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift @@ -7,15 +7,13 @@ import Foundation -protocol ShapeFactory { - static func makeShape(with type: Shapable.Type) -> Shapable? -} - -enum ShapeFactoryCluster: ShapeFactory { - static func makeShape(with type: Shapable.Type) -> Shapable? { +enum ShapeFactoryCluster: ShapeBuildable { + static func makeShape(ofClass type: Shapable.Type) -> Shapable? { switch type { case is ColoredRectangle.Type: - return RectangleFactory.makeShape() + return RectangleFactory.makeColoredRectangle() + case is ImageRectangle.Type: + return RectangleFactory.makeImageRectangle() default: return nil } diff --git a/DrawingApp/DrawingApp/View/PlaneView.swift b/DrawingApp/DrawingApp/View/PlaneView.swift index d1a0d7e5..f6789d18 100644 --- a/DrawingApp/DrawingApp/View/PlaneView.swift +++ b/DrawingApp/DrawingApp/View/PlaneView.swift @@ -9,8 +9,8 @@ import UIKit protocol PlaneViewDelegate { func planeViewDidTapped(_ sender: UITapGestureRecognizer) - func planeViewDidPressRectangleAddButton() - func planeViewDidPressImageAddButton() + func planeViewDidPressColoredRectangleAddButton() + func planeViewDidPressImageRectangleAddButton() } class PlaneView: UIView { @@ -65,21 +65,21 @@ class PlaneView: UIView { } private func configureActions() { - self.rectangleAddButton.addTarget(self, action: #selector(PlaneView.handleOnPressRectangleAddButton), for: .touchUpInside) - self.imageAddButton.addTarget(self, action: #selector(PlaneView.handleOnPressImageAddButton), for: .touchUpInside) + self.rectangleAddButton.addTarget(self, action: #selector(PlaneView.handleOnPressColoredRectangleAddButton), for: .touchUpInside) + self.imageAddButton.addTarget(self, action: #selector(PlaneView.handleOnPressImageRectangleAddButton), for: .touchUpInside) } // MARK: - Action Methods - @objc private func handleOnPressRectangleAddButton(_ sender: RoundedButton) { - self.delegate?.planeViewDidPressRectangleAddButton() - } - @objc private func handleOnTapPlaneView(_ sender: UITapGestureRecognizer) { guard sender.state == .ended else { return } self.delegate?.planeViewDidTapped(sender) } - @objc private func handleOnPressImageAddButton(_ sender: RoundedButton) { - self.delegate?.planeViewDidPressImageAddButton() + @objc private func handleOnPressColoredRectangleAddButton(_ sender: RoundedButton) { + self.delegate?.planeViewDidPressColoredRectangleAddButton() + } + + @objc private func handleOnPressImageRectangleAddButton(_ sender: RoundedButton) { + self.delegate?.planeViewDidPressImageRectangleAddButton() } } From 535e81c5e43f308d2a501b27fd0f90c27789ce80 Mon Sep 17 00:00:00 2001 From: Song TaeHwan Date: Fri, 18 Mar 2022 15:37:25 +0900 Subject: [PATCH 13/13] feat: Implement ShapeViewFactory and RectangleViewFactory --- .../DrawingApp.xcodeproj/project.pbxproj | 28 ++++++++----- .../Controller/ViewController.swift | 3 +- .../Extensions/CGRect+Extensions.swift | 2 +- .../Utility/RectangleViewFactory.swift | 42 ++++++++++++------- ...actoryCluster.swift => ShapeFactory.swift} | 8 ++-- .../Utility/ShapeViewBuildable.swift | 13 ++++++ .../DrawingApp/Utility/ShapeViewFactory.swift | 22 ++++++++++ 7 files changed, 85 insertions(+), 33 deletions(-) rename DrawingApp/DrawingApp/Utility/{ShapeFactoryCluster.swift => ShapeFactory.swift} (59%) create mode 100644 DrawingApp/DrawingApp/Utility/ShapeViewBuildable.swift create mode 100644 DrawingApp/DrawingApp/Utility/ShapeViewFactory.swift diff --git a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj index c3633d2b..96137205 100644 --- a/DrawingApp/DrawingApp.xcodeproj/project.pbxproj +++ b/DrawingApp/DrawingApp.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ E4002E9627D5B00300F14AFD /* ColoredRectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4002E9527D5B00300F14AFD /* ColoredRectangleView.swift */; }; E41EE3AF27D6EAA7007C7595 /* PlaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */; }; E41EE3B127D6F478007C7595 /* ControlPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EE3B027D6F478007C7595 /* ControlPanelView.swift */; }; - E435F06527E173F7005AD99C /* RectangleViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06427E173F7005AD99C /* RectangleViewFactory.swift */; }; + E435F06527E173F7005AD99C /* ShapeViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06427E173F7005AD99C /* ShapeViewFactory.swift */; }; E435F06727E17581005AD99C /* ShapeViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435F06627E17581005AD99C /* ShapeViewable.swift */; }; E453DC9D27E41B2400A317AF /* Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DC9C27E41B2400A317AF /* Shape.swift */; }; E453DC9F27E41FC200A317AF /* RectangleBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */; }; @@ -41,7 +41,7 @@ E49F7CCC27E30ED800BB19A2 /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8427D8843900E24DE8 /* CGRect+Extensions.swift */; }; E49F7CCD27E30ED800BB19A2 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8027D8836400E24DE8 /* CGPoint+Extensions.swift */; }; E49F7CCE27E30ED800BB19A2 /* CGSize+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8227D883DD00E24DE8 /* CGSize+Extensions.swift */; }; - E49F7CCF27E30ED800BB19A2 /* ShapeFactoryCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */; }; + E49F7CCF27E30ED800BB19A2 /* ShapeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083027CE062600F4C062 /* ShapeFactory.swift */; }; E49F7CD127E30ED800BB19A2 /* RectangleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */; }; E49F7CD227E30ED900BB19A2 /* IdentifierFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4641B4F27D0A80200815795 /* IdentifierFactory.swift */; }; E49F7CD327E30ED900BB19A2 /* PointFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B027D1E70500B90B24 /* PointFactory.swift */; }; @@ -65,6 +65,8 @@ E4BA038927E454EA005BE75C /* ShapeBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038827E454EA005BE75C /* ShapeBuildable.swift */; }; E4BA038A27E454ED005BE75C /* ShapeBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038827E454EA005BE75C /* ShapeBuildable.swift */; }; E4BA038D27E45864005BE75C /* ImageAdaptable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038C27E45864005BE75C /* ImageAdaptable.swift */; }; + E4BA038F27E45CE0005BE75C /* RectangleViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA038E27E45CE0005BE75C /* RectangleViewFactory.swift */; }; + E4BA039127E45CF8005BE75C /* ShapeViewBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BA039027E45CF8005BE75C /* ShapeViewBuildable.swift */; }; E4C9622D27D975A3005DBD2B /* Hashable+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */; }; E4C9623127D97D7F005DBD2B /* Equatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */; }; E4D3DFA227CEFE490096810A /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3DFA127CEFE490096810A /* Point.swift */; }; @@ -81,7 +83,7 @@ E4E637B327D1EAC300B90B24 /* RectangleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */; }; E4E637B527D1EC4F00B90B24 /* TypeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E637B427D1EC4F00B90B24 /* TypeBuilder.swift */; }; E4E8082F27CDF0C100F4C062 /* ColoredRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8082E27CDF0C100F4C062 /* ColoredRectangle.swift */; }; - E4E8083127CE062600F4C062 /* ShapeFactoryCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */; }; + E4E8083127CE062600F4C062 /* ShapeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083027CE062600F4C062 /* ShapeFactory.swift */; }; E4E8083B27CE0EBB00F4C062 /* DrawingAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8083A27CE0EBB00F4C062 /* DrawingAppTests.swift */; }; E4F20E7F27D86CE700E24DE8 /* Notification.Name+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E7E27D86CE700E24DE8 /* Notification.Name+Extensions.swift */; }; E4F20E8127D8836400E24DE8 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F20E8027D8836400E24DE8 /* CGPoint+Extensions.swift */; }; @@ -93,7 +95,7 @@ E4002E9527D5B00300F14AFD /* ColoredRectangleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredRectangleView.swift; sourceTree = ""; }; E41EE3AE27D6EAA7007C7595 /* PlaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneView.swift; sourceTree = ""; }; E41EE3B027D6F478007C7595 /* ControlPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlPanelView.swift; sourceTree = ""; }; - E435F06427E173F7005AD99C /* RectangleViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleViewFactory.swift; sourceTree = ""; }; + E435F06427E173F7005AD99C /* ShapeViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeViewFactory.swift; sourceTree = ""; }; E435F06627E17581005AD99C /* ShapeViewable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeViewable.swift; sourceTree = ""; }; E453DC9C27E41B2400A317AF /* Shape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shape.swift; sourceTree = ""; }; E453DC9E27E41FC200A317AF /* RectangleBuildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleBuildable.swift; sourceTree = ""; }; @@ -118,6 +120,8 @@ E4BA038127E42781005BE75C /* Notifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifiable.swift; sourceTree = ""; }; E4BA038827E454EA005BE75C /* ShapeBuildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeBuildable.swift; sourceTree = ""; }; E4BA038C27E45864005BE75C /* ImageAdaptable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAdaptable.swift; sourceTree = ""; }; + E4BA038E27E45CE0005BE75C /* RectangleViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleViewFactory.swift; sourceTree = ""; }; + E4BA039027E45CF8005BE75C /* ShapeViewBuildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeViewBuildable.swift; sourceTree = ""; }; E4C9622C27D975A3005DBD2B /* Hashable+Hash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Hashable+Hash.swift"; sourceTree = ""; }; E4C9623027D97D7F005DBD2B /* Equatable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Equatable+Extensions.swift"; sourceTree = ""; }; E4D3DFA127CEFE490096810A /* Point.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Point.swift; sourceTree = ""; }; @@ -134,7 +138,7 @@ E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleFactory.swift; sourceTree = ""; }; E4E637B427D1EC4F00B90B24 /* TypeBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeBuilder.swift; sourceTree = ""; }; E4E8082E27CDF0C100F4C062 /* ColoredRectangle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredRectangle.swift; sourceTree = ""; }; - E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeFactoryCluster.swift; sourceTree = ""; }; + E4E8083027CE062600F4C062 /* ShapeFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeFactory.swift; sourceTree = ""; }; E4E8083827CE0EBB00F4C062 /* DrawingAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DrawingAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E4E8083A27CE0EBB00F4C062 /* DrawingAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawingAppTests.swift; sourceTree = ""; }; E4F20E7E27D86CE700E24DE8 /* Notification.Name+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification.Name+Extensions.swift"; sourceTree = ""; }; @@ -222,8 +226,10 @@ children = ( E48109E727D61E68007B0E3F /* Extensions */, E4BA038827E454EA005BE75C /* ShapeBuildable.swift */, - E435F06427E173F7005AD99C /* RectangleViewFactory.swift */, - E4E8083027CE062600F4C062 /* ShapeFactoryCluster.swift */, + E4BA039027E45CF8005BE75C /* ShapeViewBuildable.swift */, + E435F06427E173F7005AD99C /* ShapeViewFactory.swift */, + E4BA038E27E45CE0005BE75C /* RectangleViewFactory.swift */, + E4E8083027CE062600F4C062 /* ShapeFactory.swift */, E4E637B227D1EAC300B90B24 /* RectangleFactory.swift */, E4641B4F27D0A80200815795 /* IdentifierFactory.swift */, E4E637B027D1E70500B90B24 /* PointFactory.swift */, @@ -414,17 +420,18 @@ E453DCA327E4262E00A317AF /* BackgroundAdaptable.swift in Sources */, E4E637AF27D1E6EE00B90B24 /* ColorFactory.swift in Sources */, E4641B5027D0A80200815795 /* IdentifierFactory.swift in Sources */, + E4BA039127E45CF8005BE75C /* ShapeViewBuildable.swift in Sources */, E4C9622D27D975A3005DBD2B /* Hashable+Hash.swift in Sources */, E4D3DFA627CEFE7F0096810A /* Color.swift in Sources */, E453DC9F27E41FC200A317AF /* RectangleBuildable.swift in Sources */, E4BA038D27E45864005BE75C /* ImageAdaptable.swift in Sources */, - E435F06527E173F7005AD99C /* RectangleViewFactory.swift in Sources */, + E435F06527E173F7005AD99C /* ShapeViewFactory.swift in Sources */, E48109E927D61E93007B0E3F /* UIColor+Extensions.swift in Sources */, E4D3DFA827CEFEB90096810A /* Alpha.swift in Sources */, E49679E827D71AF900A9BA27 /* SimpleLabel.swift in Sources */, E435F06727E17581005AD99C /* ShapeViewable.swift in Sources */, E48109E627D61E2F007B0E3F /* Double+Random.swift in Sources */, - E4E8083127CE062600F4C062 /* ShapeFactoryCluster.swift in Sources */, + E4E8083127CE062600F4C062 /* ShapeFactory.swift in Sources */, E481266627CCF8E000A3FFF6 /* ViewController.swift in Sources */, E453DC9D27E41B2400A317AF /* Shape.swift in Sources */, E41EE3B127D6F478007C7595 /* ControlPanelView.swift in Sources */, @@ -432,6 +439,7 @@ E4D6B57527E079EF005075B6 /* ImageRectangle.swift in Sources */, E41EE3AF27D6EAA7007C7595 /* PlaneView.swift in Sources */, E4002E9627D5B00300F14AFD /* ColoredRectangleView.swift in Sources */, + E4BA038F27E45CE0005BE75C /* RectangleViewFactory.swift in Sources */, E4F20E7F27D86CE700E24DE8 /* Notification.Name+Extensions.swift in Sources */, E4E637AD27D1E6D300B90B24 /* SizeFactory.swift in Sources */, E481266227CCF8E000A3FFF6 /* AppDelegate.swift in Sources */, @@ -473,7 +481,7 @@ E49F7CCD27E30ED800BB19A2 /* CGPoint+Extensions.swift in Sources */, E49F7CCE27E30ED800BB19A2 /* CGSize+Extensions.swift in Sources */, E4BA038A27E454ED005BE75C /* ShapeBuildable.swift in Sources */, - E49F7CCF27E30ED800BB19A2 /* ShapeFactoryCluster.swift in Sources */, + E49F7CCF27E30ED800BB19A2 /* ShapeFactory.swift in Sources */, E49F7CD127E30ED800BB19A2 /* RectangleFactory.swift in Sources */, E49F7CD227E30ED900BB19A2 /* IdentifierFactory.swift in Sources */, E49F7CD327E30ED900BB19A2 /* PointFactory.swift in Sources */, diff --git a/DrawingApp/DrawingApp/Controller/ViewController.swift b/DrawingApp/DrawingApp/Controller/ViewController.swift index e19f4bdf..8fb301b7 100644 --- a/DrawingApp/DrawingApp/Controller/ViewController.swift +++ b/DrawingApp/DrawingApp/Controller/ViewController.swift @@ -109,8 +109,7 @@ extension ViewController { // MARK: - Rectangle Model To ViewController extension ViewController { private func createShapeView(ofClass Class: ShapeViewable.Type, with shape: Shape) { - // TODO: Factory 한단계 더 추상화 (ShapeFactory) - guard let shapeView = RectangleViewFactory.makeView(ofClass: Class, with: shape) else { return } + guard let shapeView = ShapeViewFactory.makeView(ofClass: Class, with: shape) else { return } let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleOnTapShapeView)) diff --git a/DrawingApp/DrawingApp/Utility/Extensions/CGRect+Extensions.swift b/DrawingApp/DrawingApp/Utility/Extensions/CGRect+Extensions.swift index f9b0dc8d..a1d99b89 100644 --- a/DrawingApp/DrawingApp/Utility/Extensions/CGRect+Extensions.swift +++ b/DrawingApp/DrawingApp/Utility/Extensions/CGRect+Extensions.swift @@ -8,7 +8,7 @@ import UIKit extension CGRect: RectangleBuildable { - init(with rectangle: ColoredRectangle) { + init(with rectangle: Shapable) { self.init(x: rectangle.origin.x, y: rectangle.origin.y, width: rectangle.size.width, height: rectangle.size.height) } } diff --git a/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift b/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift index a95fc567..6eb3bad2 100644 --- a/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift +++ b/DrawingApp/DrawingApp/Utility/RectangleViewFactory.swift @@ -2,33 +2,43 @@ // RectangleViewFactory.swift // DrawingApp // -// Created by 송태환 on 2022/03/16. -// +// Created by 송태환 on 2022/03/18. // import UIKit -enum RectangleViewFactory { - static func makeView(ofClass Class: ShapeViewable.Type, with data: Shapable) -> ShapeViewable? { - switch Class { +enum RectangleViewFactory: ShapeViewBuildable { + static func makeView(ofClass type: ShapeViewable.Type, with data: Shapable) -> ShapeViewable? { + switch type { case is ColoredRectangleView.Type: guard let data = data as? ColoredRectangle else { return nil } - let rectangleView = ColoredRectangleView(frame: data.convert(using: CGRect.self)) - - rectangleView.setBackgroundColor(color: data.backgroundColor, alpha: data.alpha) - - return rectangleView + return self.makeColoredRectangleView(with: data) case is ImageRectangleView.Type: guard let data = data as? ImageRectangle else { return nil } - guard let path = data.imagePath, let image = UIImage(contentsOfFile: path) else { return nil } + return self.makeImageRectangleView(with: data) + default: + return nil + } + } + + static func makeColoredRectangleView(with data: ColoredRectangle) -> ColoredRectangleView { + let rectangleView = ColoredRectangleView(frame: data.convert(using: CGRect.self)) + + rectangleView.setBackgroundColor(color: data.backgroundColor, alpha: data.alpha) + + return rectangleView + } + + static func makeImageRectangleView(with data: ImageRectangle) -> ImageRectangleView { + if let path = data.imagePath, let image = UIImage(contentsOfFile: path) { - let imageRectangleView = ImageRectangleView(frame: data.convert(using: CGRect.self)) + let view = ImageRectangleView(frame: data.convert(using: CGRect.self)) - imageRectangleView.setImage(image) + view.setImage(image) - return imageRectangleView - default: - return nil + return view } + + return ImageRectangleView(frame: CGRect(with: data)) } } diff --git a/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift b/DrawingApp/DrawingApp/Utility/ShapeFactory.swift similarity index 59% rename from DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift rename to DrawingApp/DrawingApp/Utility/ShapeFactory.swift index 330fe6aa..79548217 100644 --- a/DrawingApp/DrawingApp/Utility/ShapeFactoryCluster.swift +++ b/DrawingApp/DrawingApp/Utility/ShapeFactory.swift @@ -1,5 +1,5 @@ // -// ShapeFactoryCluster.swift +// ShapeFactory.swift // DrawingApp // // Created by 송태환 on 2022/03/01. @@ -7,13 +7,13 @@ import Foundation -enum ShapeFactoryCluster: ShapeBuildable { +enum ShapeFactory: ShapeBuildable { static func makeShape(ofClass type: Shapable.Type) -> Shapable? { switch type { case is ColoredRectangle.Type: - return RectangleFactory.makeColoredRectangle() + return RectangleFactory.makeShape(ofClass: ColoredRectangle.self) case is ImageRectangle.Type: - return RectangleFactory.makeImageRectangle() + return RectangleFactory.makeShape(ofClass: ImageRectangle.self) default: return nil } diff --git a/DrawingApp/DrawingApp/Utility/ShapeViewBuildable.swift b/DrawingApp/DrawingApp/Utility/ShapeViewBuildable.swift new file mode 100644 index 00000000..8107cd2d --- /dev/null +++ b/DrawingApp/DrawingApp/Utility/ShapeViewBuildable.swift @@ -0,0 +1,13 @@ +// +// ShapeViewBuildable.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/18. +// + +import Foundation + + +protocol ShapeViewBuildable { + static func makeView(ofClass type: ShapeViewable.Type, with data: Shapable) -> ShapeViewable? +} diff --git a/DrawingApp/DrawingApp/Utility/ShapeViewFactory.swift b/DrawingApp/DrawingApp/Utility/ShapeViewFactory.swift new file mode 100644 index 00000000..429ca0eb --- /dev/null +++ b/DrawingApp/DrawingApp/Utility/ShapeViewFactory.swift @@ -0,0 +1,22 @@ +// +// RectangleViewFactory.swift +// DrawingApp +// +// Created by 송태환 on 2022/03/16. +// +// + +import Foundation + +enum ShapeViewFactory { + static func makeView(ofClass type: ShapeViewable.Type, with data: Shapable) -> ShapeViewable? { + switch type { + case is ColoredRectangleView.Type: + return RectangleViewFactory.makeView(ofClass: ColoredRectangleView.self, with: data) + case is ImageRectangleView.Type: + return RectangleViewFactory.makeView(ofClass: ImageRectangleView.self, with: data) + default: + return nil + } + } +}