├── .DS_Store ├── .gitignore ├── Draw_App_01_Final ├── .DS_Store ├── Draw App.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Draw App │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── Extensions │ ├── CGPoint + Helpers.swift │ ├── CGRect + Helpers.swift │ ├── CGSize + Helpers.swift │ ├── Number+Helpers.swift │ └── UIColor + Utils.swift │ ├── Metal │ ├── .DS_Store │ ├── DrawingGestureRecognizer.swift │ ├── DrawingView.swift │ ├── Point.swift │ ├── Renderer.swift │ └── Shader.metal │ ├── SceneDelegate.swift │ ├── Supporting Files │ ├── .DS_Store │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── DrawingApp-BridgingHeader.h │ └── Info.plist │ └── ViewController.swift ├── Draw_App_01_Starter ├── .DS_Store ├── Draw App.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Draw App │ ├── .DS_Store │ ├── Drawing │ ├── DrawingGestureRecognizer.swift │ └── Shader.metal │ ├── Extensions │ ├── CGPoint + Helpers.swift │ ├── CGRect + Helpers.swift │ ├── CGSize + Helpers.swift │ ├── Number+Helpers.swift │ └── UIColor + Utils.swift │ ├── Supporting Files │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── DrawingApp-BridgingHeader.h │ ├── Info.plist │ └── SceneDelegate.swift │ └── ViewController.swift ├── Draw_App_02_Final ├── .DS_Store ├── Draw App.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Draw App │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── BottomView │ ├── Color Picker │ │ ├── CollorPickerBottomSheetController.swift │ │ ├── ColorPaletteCollectionViewCell.swift │ │ ├── ColorPickerBottomSheetView.swift │ │ ├── ColorPickerGridView.swift │ │ ├── ColorPickerPresentationController.swift │ │ ├── ColorPickerSlider.swift │ │ ├── ColorPickerSliderInputView.swift │ │ └── ColorPickerSpectrumView.swift │ ├── EditorBottomSegementedControl.swift │ ├── EditorBottomSizeEditor.swift │ ├── EditorBottomView.swift │ ├── EditorBottomViewModel.swift │ └── SizeSlider.swift │ ├── DrawingViewController.swift │ ├── Extensions │ ├── CGImage + PixelColor.swift │ ├── CGPoint + Helpers.swift │ ├── CGRect + Helpers.swift │ ├── CGSize + Helpers.swift │ ├── MTLTexture + Helpers.swift │ ├── Number+Helpers.swift │ ├── UIApplication + currentWindow.swift │ ├── UIColor + Utils.swift │ ├── UIControl + Combine.swift │ ├── UIImage + Resources.swift │ ├── UIView + Helpers.swift │ └── UIView+Constraints.swift │ ├── Metal │ ├── .DS_Store │ ├── DrawingGestureRecognizer.swift │ ├── DrawingView.swift │ ├── Renderer.swift │ └── Shader.metal │ ├── Models │ ├── Brush.swift │ ├── Color.swift │ └── Point.swift │ ├── SceneDelegate.swift │ └── Supporting Files │ ├── .DS_Store │ ├── Assets.xcassets │ ├── .DS_Store │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── add.imageset │ │ ├── Contents.json │ │ └── add.png │ ├── arrowTip.imageset │ │ ├── Contents.json │ │ └── arrowTip.png │ ├── back.imageset │ │ ├── Contents.json │ │ └── back.png │ ├── blurEraser.imageset │ │ ├── Contents.json │ │ └── blurEraser.png │ ├── blurTip.imageset │ │ ├── Contents.json │ │ └── blurTip.png │ ├── brush.imageset │ │ ├── Contents.json │ │ └── brush.png │ ├── brush_tip.imageset │ │ ├── Contents.json │ │ └── brush.png │ ├── cancel.imageset │ │ ├── Contents.json │ │ └── cancel.png │ ├── close.imageset │ │ ├── Close.pdf │ │ └── Contents.json │ ├── color_picker.imageset │ │ ├── .DS_Store │ │ ├── Contents.json │ │ └── color_picker_gradient.png │ ├── download.imageset │ │ ├── Contents.json │ │ └── download.png │ ├── eraser.imageset │ │ ├── Contents.json │ │ └── eraser.png │ ├── eyedropper.imageset │ │ ├── Contents.json │ │ └── eyedropper.pdf │ ├── neon.imageset │ │ ├── Contents.json │ │ └── neon.png │ ├── neon_tip.imageset │ │ ├── Contents.json │ │ └── neon.png │ ├── objectEraser.imageset │ │ ├── Contents.json │ │ └── objectEraser.png │ ├── opacity_slider_background.imageset │ │ ├── Contents.json │ │ └── VStack.pdf │ ├── opacity_slider_thumb.imageset │ │ ├── Contents.json │ │ └── Ellipse 26.pdf │ ├── pen.imageset │ │ ├── Contents.json │ │ └── pen.png │ ├── pen_tip.imageset │ │ ├── Contents.json │ │ └── pen.png │ ├── pencil.imageset │ │ ├── Contents.json │ │ └── pencil.png │ ├── pencil_tip.imageset │ │ ├── Contents.json │ │ └── pencil.png │ ├── roundTip.imageset │ │ ├── Contents.json │ │ └── roundTip.png │ ├── shapeArrow.imageset │ │ ├── Contents.json │ │ └── shapeArrow.png │ ├── shapeBubble.imageset │ │ ├── Contents.json │ │ └── shapeBubble.png │ ├── shapeEllipse.imageset │ │ ├── Contents.json │ │ └── shapeEllipse.png │ ├── shapeRectangle.imageset │ │ ├── Contents.json │ │ └── shapeRectangle.png │ ├── shapeStar.imageset │ │ ├── Contents.json │ │ └── shapeStar.png │ ├── size_slslider.imageset │ │ ├── Contents.json │ │ └── Rectangle 53@2x.png │ ├── undo.imageset │ │ ├── Contents.json │ │ └── undo.png │ ├── xmarkTip.imageset │ │ ├── Contents.json │ │ └── xmarkTip.png │ └── zoomOut.imageset │ │ ├── Contents.json │ │ └── zoomOut.png │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── DrawingApp-BridgingHeader.h │ └── Info.plist ├── Draw_App_02_Starter ├── .DS_Store ├── Draw App.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Draw App │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── BottomView │ ├── Color Picker │ │ ├── CollorPickerBottomSheetController.swift │ │ ├── ColorPaletteCollectionViewCell.swift │ │ ├── ColorPickerBottomSheetView.swift │ │ ├── ColorPickerGridView.swift │ │ ├── ColorPickerPresentationController.swift │ │ ├── ColorPickerSlider.swift │ │ ├── ColorPickerSliderInputView.swift │ │ └── ColorPickerSpectrumView.swift │ ├── EditorBottomSegementedControl.swift │ ├── EditorBottomSizeEditor.swift │ ├── EditorBottomView.swift │ ├── EditorBottomViewModel.swift │ └── SizeSlider.swift │ ├── DrawingViewController.swift │ ├── Extensions │ ├── CGImage + PixelColor.swift │ ├── CGPoint + Helpers.swift │ ├── CGRect + Helpers.swift │ ├── CGSize + Helpers.swift │ ├── MTLTexture + Helpers.swift │ ├── Number+Helpers.swift │ ├── UIApplication + currentWindow.swift │ ├── UIColor + Utils.swift │ ├── UIControl + Combine.swift │ ├── UIImage + Resources.swift │ ├── UIView + Helpers.swift │ └── UIView+Constraints.swift │ ├── Metal │ ├── .DS_Store │ ├── DrawingGestureRecognizer.swift │ ├── DrawingView.swift │ ├── Renderer.swift │ └── Shader.metal │ ├── Models │ ├── Brush.swift │ ├── Color.swift │ └── Point.swift │ ├── SceneDelegate.swift │ └── Supporting Files │ ├── .DS_Store │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── blurEraser 1.imageset │ │ ├── Contents.json │ │ └── blurEraser.png │ ├── blurEraser.imageset │ │ ├── Contents.json │ │ └── blurEraser.png │ ├── blurTip 1.imageset │ │ ├── Contents.json │ │ └── blurTip.png │ ├── blurTip.imageset │ │ ├── Contents.json │ │ └── blurTip.png │ ├── brush 1.imageset │ │ ├── Contents.json │ │ └── brush.png │ ├── brush.imageset │ │ ├── Contents.json │ │ └── brush.png │ ├── brush_tip 1.imageset │ │ ├── Contents.json │ │ └── brush.png │ ├── brush_tip.imageset │ │ ├── Contents.json │ │ └── brush.png │ ├── close 1.imageset │ │ ├── Close.pdf │ │ └── Contents.json │ ├── close.imageset │ │ ├── Close.pdf │ │ └── Contents.json │ ├── color_picker.imageset │ │ ├── Contents.json │ │ └── color_picker_gradient.png │ ├── eraser.imageset │ │ ├── Contents.json │ │ └── eraser.png │ ├── neon.imageset │ │ ├── Contents.json │ │ └── neon.png │ ├── neon_tip.imageset │ │ ├── Contents.json │ │ └── neon.png │ ├── objectEraser.imageset │ │ ├── Contents.json │ │ └── objectEraser.png │ ├── opacity_slider_background.imageset │ │ ├── Contents.json │ │ └── VStack.pdf │ ├── opacity_slider_thumb.imageset │ │ ├── Contents.json │ │ └── Ellipse 26.pdf │ ├── pen.imageset │ │ ├── Contents.json │ │ └── pen.png │ ├── pen_tip.imageset │ │ ├── Contents.json │ │ └── pen.png │ ├── pencil.imageset │ │ ├── Contents.json │ │ └── pencil.png │ ├── pencil_tip.imageset │ │ ├── Contents.json │ │ └── pencil.png │ ├── roundTip.imageset │ │ ├── Contents.json │ │ └── roundTip.png │ └── size_slslider.imageset │ │ ├── Contents.json │ │ └── Rectangle 53@2x.png │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── DrawingApp-BridgingHeader.h │ └── Info.plist ├── LICENSE └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Draw_App_01_Final/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_01_Final/.DS_Store -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_01_Final/Draw App/.DS_Store -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Extensions/CGPoint + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint + Arithmetic.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | 11 | extension CGPoint { 12 | func interpolate(to: CGPoint, progress: CGFloat) -> CGPoint { 13 | guard progress >= 0.0 && progress <= 1.0 else { return self } 14 | let x = self.x.interpolate(to: to.x, progress: progress) 15 | let y = self.y.interpolate(to: to.y, progress: progress) 16 | return CGPoint(x: x, y: y) 17 | } 18 | 19 | init(vector: vector_uint2) { 20 | self.init(x: CGFloat(vector.x), y: CGFloat(vector.y)) 21 | } 22 | 23 | init(vector: vector_float2) { 24 | self.init(x: vector.x.cgFloat, y: vector.y.cgFloat) 25 | } 26 | 27 | var vectorFloat: vector_float2 { 28 | return vector_float2(x: x.float, y: y.float) 29 | } 30 | 31 | var vectorUint: vector_uint2 { 32 | return vector_uint2(x: x.int32, y: y.int32) 33 | } 34 | 35 | // Arithmetic 36 | 37 | static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 38 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 39 | } 40 | 41 | static func -(lhs: CGPoint, rhs: CGPoint) -> CGVector { 42 | return CGVector(dx: lhs.x - rhs.x, dy: lhs.y - rhs.y) 43 | } 44 | 45 | static func -(lhs: CGPoint, rhs: CGVector) -> CGPoint { 46 | return CGPoint(x: lhs.x - rhs.dx, y: lhs.y - rhs.dy) 47 | } 48 | 49 | static func *(lhs: CGPoint, rhs: CGFloat) -> CGPoint { 50 | return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) 51 | } 52 | 53 | static func *(lhs: CGFloat, rhs: CGPoint) -> CGPoint { 54 | return CGPoint(x: rhs.x * lhs, y: rhs.y * lhs) 55 | } 56 | 57 | func distanceToPoint(otherPoint: CGPoint) -> CGFloat { 58 | return sqrt(pow((otherPoint.x - x), 2) + pow((otherPoint.y - y), 2)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Extensions/CGRect + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension CGRect { 11 | 12 | public init(size: CGSize) { 13 | self.init(origin: .zero, size: size) 14 | } 15 | 16 | func scale(_ factor: CGFloat) -> CGRect { 17 | let transform = CGAffineTransform(scaleX: factor, y: factor) 18 | return self.applying(transform) 19 | } 20 | 21 | func interpolate(to: CGRect, progress: CGFloat) -> CGRect { 22 | guard progress >= 0.0, progress <= 1.0 else { return self } 23 | let origin = origin.interpolate(to: to.origin, progress: progress) 24 | let size = size.interpolate(to: to.size, progress: progress) 25 | return CGRect(origin: origin, size: size) 26 | } 27 | 28 | var center: CGPoint { 29 | return CGPoint(x: midX, y: midY) 30 | } 31 | 32 | init(center: CGPoint, size: CGSize) { 33 | self.init(origin: CGPoint(x: center.x - size.width / 2, y: center.y - size.height / 2), size: size) 34 | } 35 | 36 | func withOrigin(_ newOrigin: CGPoint) -> CGRect { 37 | var newRect = self 38 | newRect.origin = newOrigin 39 | return newRect 40 | } 41 | 42 | func withX(_ newX: CGFloat) -> CGRect { 43 | return withOrigin(CGPoint(x: newX, y: minY)) 44 | } 45 | 46 | func withY(_ newY: CGFloat) -> CGRect { 47 | return withOrigin(CGPoint(x: minX, y: newY)) 48 | } 49 | 50 | func withSize(_ newValue: CGSize) -> CGRect { 51 | var newRect = self 52 | newRect.size = newValue 53 | return newRect 54 | } 55 | 56 | func withWidth(_ newWidth: CGFloat) -> CGRect { 57 | return withSize(CGSize(width: newWidth, height: height)) 58 | } 59 | 60 | 61 | func withHeight(_ newHeight: CGFloat) -> CGRect { 62 | return withSize(CGSize(width: width, height: newHeight)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Extensions/CGSize + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSize + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension CGSize { 11 | 12 | init(size: CGFloat) { 13 | self.init(width: size, height: size) 14 | } 15 | 16 | func interpolate(to: CGSize, progress: CGFloat) -> CGSize { 17 | guard progress >= 0.0 && progress <= 1.0 else { return self } 18 | 19 | let width = self.width.interpolate(to: to.width, progress: progress) 20 | let height = self.height.interpolate(to: to.height, progress: progress) 21 | return CGSize(width: width, height: height) 22 | } 23 | 24 | func scale(_ factor: CGFloat) -> CGSize { 25 | let transform = CGAffineTransform(scaleX: factor, y: factor) 26 | return self.applying(transform) 27 | } 28 | 29 | static func *(lhs: CGSize, rhs: CGSize) -> CGSize { 30 | return CGSize(width: lhs.width * rhs.width, height: lhs.height * rhs.height) 31 | } 32 | 33 | static func *(lhs: CGSize, rhs: CGFloat) -> CGSize { 34 | return CGSize(width: lhs.width * rhs, height: lhs.height * rhs) 35 | } 36 | 37 | static func *(lhs: CGFloat, rhs: CGSize) -> CGSize { 38 | return CGSize(width: rhs.width * lhs, height: rhs.height * lhs) 39 | } 40 | 41 | static func /(lhs: CGSize, rhs: CGSize) -> CGSize { 42 | return CGSize(width: lhs.width / rhs.width, height: lhs.height / rhs.height) 43 | } 44 | 45 | static func /(lhs: CGSize, rhs: CGFloat) -> CGSize { 46 | return CGSize(width: lhs.width / rhs, height: lhs.height / rhs) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Extensions/Number+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Number+Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Int { 11 | func interpolate(to: Int, progress: CGFloat) -> Int { 12 | guard progress >= 0.0 && progress <= 1.0 else { return self } 13 | return self + Int(CGFloat(to - self) * progress) 14 | } 15 | 16 | var int32: UInt32 { 17 | return UInt32(self) 18 | } 19 | 20 | var float: Float { 21 | return Float(self) 22 | } 23 | 24 | var double: Double { 25 | return Double(self) 26 | } 27 | 28 | var cgFloat: CGFloat { 29 | return CGFloat(self) 30 | } 31 | 32 | var string: String { 33 | return String(describing: self) 34 | } 35 | 36 | static func extract(from string: String) -> Int? { 37 | return Int(string.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) 38 | } 39 | } 40 | 41 | extension Float { 42 | 43 | var int: Int { 44 | return Int(self) 45 | } 46 | 47 | var int32: UInt32 { 48 | return UInt32(self) 49 | } 50 | 51 | var double: Double { 52 | return Double(self) 53 | } 54 | 55 | var cgFloat: CGFloat { 56 | return CGFloat(self) 57 | } 58 | 59 | var string: String { 60 | return String(describing: self) 61 | } 62 | } 63 | 64 | extension Double { 65 | var float: Float { 66 | return Float(self) 67 | } 68 | 69 | var int: Int { 70 | return Int(self) 71 | } 72 | 73 | var int32: UInt32 { 74 | return UInt32(self) 75 | } 76 | 77 | var cgFloat: CGFloat { 78 | return CGFloat(self) 79 | } 80 | 81 | var string: String { 82 | return String(describing: self) 83 | } 84 | } 85 | 86 | let π = CGFloat.pi 87 | 88 | extension CGFloat { 89 | func interpolate(to: CGFloat, progress: CGFloat) -> CGFloat { 90 | guard progress >= 0.0 && progress <= 1.0 else { return self } 91 | return self + (to - self) * progress 92 | } 93 | 94 | var int: Int { 95 | return Int(self) 96 | } 97 | 98 | var int32: UInt32 { 99 | return UInt32(self) 100 | } 101 | 102 | var double: Double { 103 | return Double(self) 104 | } 105 | 106 | var float: Float { 107 | return Float(self) 108 | } 109 | 110 | var string: String { 111 | return String(describing: self) 112 | } 113 | // Converts an angle in degrees to radians. 114 | func degreesToRadians() -> CGFloat { 115 | 116 | return π * self / 180.0 117 | } 118 | 119 | // Converts an angle in radians to degrees. 120 | func radiansToDegrees() -> CGFloat { 121 | return self * 180.0 / π 122 | } 123 | 124 | // Ensures that the float value stays between the given values, inclusive. 125 | func clamped(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 126 | let min = v1 < v2 ? v1 : v2 127 | let max = v1 > v2 ? v1 : v2 128 | return self < min ? min : (self > max ? max : self) 129 | } 130 | 131 | // Ensures that the float value stays between the given values, inclusive. 132 | mutating func clamp(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 133 | self = clamped(v1, v2) 134 | return self 135 | } 136 | 137 | // Returns 1.0 if a floating point value is positive; -1.0 if it is negative. 138 | func sign() -> CGFloat { 139 | return (self >= 0.0) ? 1.0 : -1.0 140 | } 141 | 142 | func rounded(symbolsAfterComma count: Int) -> CGFloat { 143 | let multiplier = pow(10, count.double).cgFloat 144 | let result = (self * multiplier).rounded() / multiplier 145 | return result 146 | } 147 | } 148 | 149 | extension UInt32 { 150 | var float: Float { 151 | return Float(self) 152 | } 153 | 154 | var int: Int { 155 | return Int(self) 156 | } 157 | 158 | var cgFloat: CGFloat { 159 | return CGFloat(self) 160 | } 161 | 162 | var string: String { 163 | return String(describing: self) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Extensions/UIColor + Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor + Hex.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 10/10/2022. 6 | // 7 | 8 | import UIKit 9 | import simd 10 | 11 | extension UIColor { 12 | 13 | public convenience init?(hex: String) { 14 | var hexInt: UInt64 = 0 15 | let scanner = Scanner(string: hex) 16 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") 17 | scanner.scanHexInt64(&hexInt) 18 | 19 | let red = CGFloat((hexInt & 0xFF0000) >> 16) / 255.0 20 | let green = CGFloat((hexInt & 0xFF00) >> 8) / 255.0 21 | let blue = CGFloat((hexInt & 0xFF) >> 0) / 255.0 22 | 23 | self.init(red: red, green: green, blue: blue, alpha: 1.0) 24 | } 25 | 26 | static var random: UIColor { 27 | return UIColor(red: .random(in: 0...1), 28 | green: .random(in: 0...1), 29 | blue: .random(in: 0...1), 30 | alpha: 1.0) 31 | } 32 | 33 | var hexString: String { 34 | let (r, g, b, _) = rgbaCGFloat 35 | 36 | let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 37 | 38 | return String(format:"#%06x", rgb) 39 | } 40 | 41 | typealias RGBA = (r: T, g: T, b: T, a: T) 42 | 43 | var rgbaCGFloat: RGBA { 44 | var r: CGFloat = .zero 45 | var g: CGFloat = .zero 46 | var b: CGFloat = .zero 47 | var a: CGFloat = .zero 48 | getRed(&r, green: &g, blue: &b, alpha: &a) 49 | return (r, g, b, a) 50 | } 51 | 52 | var rgbaDouble: RGBA { 53 | let (r,g,b,a) = rgbaCGFloat 54 | return (r.double, g.double, b.double, a.double) 55 | } 56 | 57 | var vectorFloat4: vector_float4 { 58 | let (r,g,b,a) = rgbaDouble 59 | return vector_float4(r.float, g.float, b.float, a.float) 60 | } 61 | 62 | var hsba: (h: CGFloat, s: CGFloat, b: CGFloat, a: CGFloat) { 63 | var h: CGFloat = .zero 64 | var s: CGFloat = .zero 65 | var b: CGFloat = .zero 66 | var a: CGFloat = .zero 67 | getHue(&h, saturation: &s, brightness: &b, alpha: &a) 68 | return (h, s, b, a) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Metal/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_01_Final/Draw App/Metal/.DS_Store -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Metal/DrawingGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingGestureRecognizer.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 25/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class DrawingGestureRecognizer: UIPanGestureRecognizer { 11 | 12 | typealias touchesResponse = (touches: Set, event: UIEvent, velocity: CGPoint) 13 | 14 | var touchesBeganHandler: ((touchesResponse) -> Void)? 15 | var touchesMovedHandler: ((touchesResponse) -> Void)? 16 | var touchesEndedHandler: ((touchesResponse) -> Void)? 17 | var touchesCancelledHandler: ((touchesResponse) -> Void)? 18 | 19 | override func touchesBegan(_ touches: Set, with event: UIEvent) { 20 | let response: touchesResponse = (touches, event, velocity(in: view)) 21 | touchesBeganHandler?(response) 22 | super.touchesBegan(touches, with: event) 23 | } 24 | 25 | override func touchesMoved(_ touches: Set, with event: UIEvent) { 26 | let response: touchesResponse = (touches, event, velocity(in: view)) 27 | touchesMovedHandler?(response) 28 | super.touchesMoved(touches, with: event) 29 | } 30 | 31 | override func touchesEnded(_ touches: Set, with event: UIEvent) { 32 | let response: touchesResponse = (touches, event, velocity(in: view)) 33 | touchesEndedHandler?(response) 34 | super.touchesEnded(touches, with: event) 35 | } 36 | 37 | override func touchesCancelled(_ touches: Set, with event: UIEvent) { 38 | let response: touchesResponse = (touches, event, velocity(in: view)) 39 | touchesCancelledHandler?(response) 40 | super.touchesCancelled(touches, with: event) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Metal/DrawingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingView.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import MetalKit 9 | 10 | class DrawingView: MTKView { 11 | private var previousPreviousLocation: CGPoint? 12 | private var previousLocation: CGPoint? 13 | 14 | var brushSize = CGFloat(20) 15 | 16 | var points: [Point] = [] 17 | 18 | override var frame: CGRect { 19 | didSet { 20 | delegate?.mtkView(self, drawableSizeWillChange: frame.size) 21 | } 22 | } 23 | 24 | override init(frame frameRect: CGRect, device: MTLDevice?) { 25 | super.init(frame: frameRect, device: device) 26 | setupGestureRecognizer() 27 | } 28 | 29 | @available(*, unavailable) required init(coder: NSCoder) { 30 | fatalError("init(coder:) has not been implemented") 31 | } 32 | 33 | private func setupGestureRecognizer() { 34 | let panGesture = DrawingGestureRecognizer() 35 | panGesture.maximumNumberOfTouches = 1 36 | panGesture.touchesBeganHandler = { [weak self] response in 37 | guard let self else { return } 38 | for touch in response.touches { 39 | let pos = touch.location(in: self) * self.contentScaleFactor 40 | self.draw(at: pos) 41 | let predicted = response.event.coalescedTouches(for: touch) 42 | for p in (predicted ?? []) { 43 | let pos = p.location(in: self) * self.contentScaleFactor 44 | self.draw(at: pos) 45 | } 46 | } 47 | } 48 | 49 | panGesture.touchesMovedHandler = { [weak self] response in 50 | guard let self else { return } 51 | for touch in response.touches { 52 | let pos = touch.location(in: self) * self.contentScaleFactor 53 | self.draw(at: pos) 54 | } 55 | } 56 | 57 | let gestureFinishedHandler: ((DrawingGestureRecognizer.touchesResponse) -> Void)? = { [weak self] _ in 58 | guard let self else { return } 59 | self.previousLocation = nil 60 | self.previousPreviousLocation = nil 61 | } 62 | 63 | panGesture.touchesEndedHandler = gestureFinishedHandler 64 | panGesture.touchesCancelledHandler = gestureFinishedHandler 65 | 66 | addGestureRecognizer(panGesture) 67 | } 68 | 69 | private func draw(at point: CGPoint) { 70 | let previousPreviousLocation = self.previousPreviousLocation ?? point 71 | let previousLocation = self.previousLocation ?? point 72 | self.previousPreviousLocation = self.previousLocation 73 | self.previousLocation = point 74 | 75 | let mid1 = (previousLocation + previousPreviousLocation) * 0.5 76 | let mid2 = (point + previousLocation) * 0.5 77 | 78 | let pl = SIMD2(Float(previousLocation.x), Float(previousLocation.y)) 79 | let cl = SIMD2(Float(point.x), Float(point.y)) 80 | let d = distance(pl, cl) 81 | 82 | for i in 0 ... Int(d) { 83 | let p = d <= 0 ? point : quadBezierPoint(t: CGFloat(i) / CGFloat(d), start: mid1, c1: previousLocation, end: mid2) 84 | let point = Point(location: p, parentSize: self.bounds.size * contentScaleFactor, color: .blue, size: brushSize * contentScaleFactor) 85 | points.append(point) 86 | } 87 | } 88 | 89 | private func quadBezierPoint(t: CGFloat, start: CGPoint, c1: CGPoint, end: CGPoint) -> CGPoint { 90 | let x = quadBezier(t: t, start: start.x, c1: c1.x, end: end.x) 91 | let y = quadBezier(t: t, start: start.y, c1: c1.y, end: end.y) 92 | return CGPoint(x: x, y: y) 93 | } 94 | 95 | private func quadBezier(t: CGFloat, start: CGFloat, c1: CGFloat, end: CGFloat) -> CGFloat { 96 | let t_ = (1.0 - t) 97 | let tt_ = t_ * t_ 98 | let tt = t * t 99 | return start * tt_ + 2.0 * c1 * t_ * t + end * tt 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Metal/Point.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Point.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 07/05/2023. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | import UIKit 11 | 12 | struct Point { 13 | var position: vector_float4 14 | var color: vector_float4 15 | var size: Float 16 | 17 | init(position: vector_float4, color: vector_float4, size: Float) { 18 | self.position = position 19 | self.color = color 20 | self.size = size 21 | } 22 | 23 | init(x: CGFloat, y: CGFloat, color: UIColor, size: CGFloat) { 24 | self.init(position: vector_float4(Float(x), Float(y), 0, 1), color: color.vectorFloat4, size: Float(size)) 25 | } 26 | 27 | init(location: CGPoint, parentSize: CGSize, color: UIColor, size: CGFloat) { 28 | let roundedLocation = CGPoint(x: location.x.int.cgFloat, y: location.y.int.cgFloat) 29 | let xK = (1.0 / (parentSize.width / roundedLocation.x)).rounded(symbolsAfterComma: 5) 30 | let yK = (1.0 / (parentSize.height / roundedLocation.y)).rounded(symbolsAfterComma: 5) 31 | let x = xK == 0.5 ? 0.0 : (xK < 0.5 ? -(1 - xK / 0.5) : xK / 0.5 - 1) 32 | let y = yK == 0.5 ? 0.0 : (yK > 0.5 ? (1 - yK / 0.5) : 1 - (yK / 0.5)) 33 | self.init(x: x, y: y, color: color, size: size) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Metal/Shader.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Shader.metal 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 06/05/2023. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | struct PointVertex { 12 | float4 position [[position]]; 13 | float4 color; 14 | float size [[point_size]]; 15 | }; 16 | 17 | vertex PointVertex pointShaderVertex(constant PointVertex *points [[ buffer(0) ]], 18 | uint vid [[ vertex_id ]]) 19 | { 20 | PointVertex out = points[vid]; 21 | 22 | float2 pos = float2(out.position.x, out.position.y); 23 | out.position = float4(pos, 0, 1); 24 | return out; 25 | }; 26 | 27 | fragment half4 pointShaderFragment(PointVertex point_data [[ stage_in ]], 28 | float2 pointCoord [[ point_coord ]]) 29 | { 30 | float dist = length(pointCoord - float2(0.5)); 31 | float4 out_color = point_data.color; 32 | out_color.a = 1.0 - smoothstep(0.4, 0.5, dist); 33 | return half4(out_color); 34 | }; 35 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | 18 | let window = UIWindow(windowScene: windowScene) 19 | window.rootViewController = ViewController() 20 | self.window = window 21 | window.makeKeyAndVisible() 22 | } 23 | 24 | func sceneDidDisconnect(_ scene: UIScene) { 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | func sceneDidBecomeActive(_ scene: UIScene) { 32 | // Called when the scene has moved from an inactive state to an active state. 33 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 34 | } 35 | 36 | func sceneWillResignActive(_ scene: UIScene) { 37 | // Called when the scene will move from an active state to an inactive state. 38 | // This may occur due to temporary interruptions (ex. an incoming phone call). 39 | } 40 | 41 | func sceneWillEnterForeground(_ scene: UIScene) { 42 | // Called as the scene transitions from the background to the foreground. 43 | // Use this method to undo the changes made on entering the background. 44 | } 45 | 46 | func sceneDidEnterBackground(_ scene: UIScene) { 47 | // Called as the scene transitions from the foreground to the background. 48 | // Use this method to save data, release shared resources, and store enough scene-specific state information 49 | // to restore the scene back to its current state. 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Supporting Files/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_01_Final/Draw App/Supporting Files/.DS_Store -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Supporting Files/DrawingApp-BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingApp-BridgingHeader.h 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | #ifndef DrawingApp_BridgingHeader_h 9 | #define DrawingApp_BridgingHeader_h 10 | 11 | #endif /* DrawingApp_BridgingHeader_h */ 12 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Draw_App_01_Final/Draw App/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import MetalKit 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | private var drawingView: DrawingView? { 14 | return self.view as? DrawingView 15 | } 16 | 17 | private var renderer: Renderer? 18 | 19 | override func loadView() { 20 | super.loadView() 21 | self.view = DrawingView(frame: UIScreen.main.bounds) 22 | } 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | guard let drawingView else { return } 27 | renderer = Renderer(metalKitView: drawingView) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_01_Starter/.DS_Store -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_01_Starter/Draw App/.DS_Store -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Drawing/DrawingGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingGestureRecognizer.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 25/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class DrawingGestureRecognizer: UIPanGestureRecognizer { 11 | 12 | typealias touchesResponse = (touches: Set, event: UIEvent, velocity: CGPoint) 13 | 14 | var touchesBeganHandler: ((touchesResponse) -> Void)? 15 | var touchesMovedHandler: ((touchesResponse) -> Void)? 16 | var touchesEndedHandler: ((touchesResponse) -> Void)? 17 | var touchesCancelledHandler: ((touchesResponse) -> Void)? 18 | 19 | override func touchesBegan(_ touches: Set, with event: UIEvent) { 20 | let response: touchesResponse = (touches, event, velocity(in: view)) 21 | touchesBeganHandler?(response) 22 | super.touchesBegan(touches, with: event) 23 | } 24 | 25 | override func touchesMoved(_ touches: Set, with event: UIEvent) { 26 | let response: touchesResponse = (touches, event, velocity(in: view)) 27 | touchesMovedHandler?(response) 28 | super.touchesMoved(touches, with: event) 29 | } 30 | 31 | override func touchesEnded(_ touches: Set, with event: UIEvent) { 32 | let response: touchesResponse = (touches, event, velocity(in: view)) 33 | touchesEndedHandler?(response) 34 | super.touchesEnded(touches, with: event) 35 | } 36 | 37 | override func touchesCancelled(_ touches: Set, with event: UIEvent) { 38 | let response: touchesResponse = (touches, event, velocity(in: view)) 39 | touchesCancelledHandler?(response) 40 | super.touchesCancelled(touches, with: event) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Drawing/Shader.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Shader.metal 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 06/05/2023. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | struct PointVertex { 12 | float4 position [[position]]; 13 | float4 color; 14 | float size [[point_size]]; 15 | }; 16 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Extensions/CGPoint + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint + Arithmetic.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | 11 | extension CGPoint { 12 | func interpolate(to: CGPoint, progress: CGFloat) -> CGPoint { 13 | guard progress >= 0.0 && progress <= 1.0 else { return self } 14 | let x = self.x.interpolate(to: to.x, progress: progress) 15 | let y = self.y.interpolate(to: to.y, progress: progress) 16 | return CGPoint(x: x, y: y) 17 | } 18 | 19 | init(vector: vector_uint2) { 20 | self.init(x: CGFloat(vector.x), y: CGFloat(vector.y)) 21 | } 22 | 23 | init(vector: vector_float2) { 24 | self.init(x: vector.x.cgFloat, y: vector.y.cgFloat) 25 | } 26 | 27 | var vectorFloat: vector_float2 { 28 | return vector_float2(x: x.float, y: y.float) 29 | } 30 | 31 | var vectorUint: vector_uint2 { 32 | return vector_uint2(x: x.int32, y: y.int32) 33 | } 34 | 35 | // Arithmetic 36 | 37 | static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 38 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 39 | } 40 | 41 | static func -(lhs: CGPoint, rhs: CGPoint) -> CGVector { 42 | return CGVector(dx: lhs.x - rhs.x, dy: lhs.y - rhs.y) 43 | } 44 | 45 | static func -(lhs: CGPoint, rhs: CGVector) -> CGPoint { 46 | return CGPoint(x: lhs.x - rhs.dx, y: lhs.y - rhs.dy) 47 | } 48 | 49 | static func *(lhs: CGPoint, rhs: CGFloat) -> CGPoint { 50 | return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) 51 | } 52 | 53 | static func *(lhs: CGFloat, rhs: CGPoint) -> CGPoint { 54 | return CGPoint(x: rhs.x * lhs, y: rhs.y * lhs) 55 | } 56 | 57 | func distanceToPoint(otherPoint: CGPoint) -> CGFloat { 58 | return sqrt(pow((otherPoint.x - x), 2) + pow((otherPoint.y - y), 2)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Extensions/CGRect + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension CGRect { 11 | 12 | public init(size: CGSize) { 13 | self.init(origin: .zero, size: size) 14 | } 15 | 16 | func scale(_ factor: CGFloat) -> CGRect { 17 | let transform = CGAffineTransform(scaleX: factor, y: factor) 18 | return self.applying(transform) 19 | } 20 | 21 | func interpolate(to: CGRect, progress: CGFloat) -> CGRect { 22 | guard progress >= 0.0, progress <= 1.0 else { return self } 23 | let origin = origin.interpolate(to: to.origin, progress: progress) 24 | let size = size.interpolate(to: to.size, progress: progress) 25 | return CGRect(origin: origin, size: size) 26 | } 27 | 28 | var center: CGPoint { 29 | return CGPoint(x: midX, y: midY) 30 | } 31 | 32 | init(center: CGPoint, size: CGSize) { 33 | self.init(origin: CGPoint(x: center.x - size.width / 2, y: center.y - size.height / 2), size: size) 34 | } 35 | 36 | func withOrigin(_ newOrigin: CGPoint) -> CGRect { 37 | var newRect = self 38 | newRect.origin = newOrigin 39 | return newRect 40 | } 41 | 42 | func withX(_ newX: CGFloat) -> CGRect { 43 | return withOrigin(CGPoint(x: newX, y: minY)) 44 | } 45 | 46 | func withY(_ newY: CGFloat) -> CGRect { 47 | return withOrigin(CGPoint(x: minX, y: newY)) 48 | } 49 | 50 | func withSize(_ newValue: CGSize) -> CGRect { 51 | var newRect = self 52 | newRect.size = newValue 53 | return newRect 54 | } 55 | 56 | func withWidth(_ newWidth: CGFloat) -> CGRect { 57 | return withSize(CGSize(width: newWidth, height: height)) 58 | } 59 | 60 | 61 | func withHeight(_ newHeight: CGFloat) -> CGRect { 62 | return withSize(CGSize(width: width, height: newHeight)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Extensions/CGSize + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSize + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension CGSize { 11 | 12 | init(size: CGFloat) { 13 | self.init(width: size, height: size) 14 | } 15 | 16 | func interpolate(to: CGSize, progress: CGFloat) -> CGSize { 17 | guard progress >= 0.0 && progress <= 1.0 else { return self } 18 | 19 | let width = self.width.interpolate(to: to.width, progress: progress) 20 | let height = self.height.interpolate(to: to.height, progress: progress) 21 | return CGSize(width: width, height: height) 22 | } 23 | 24 | func scale(_ factor: CGFloat) -> CGSize { 25 | let transform = CGAffineTransform(scaleX: factor, y: factor) 26 | return self.applying(transform) 27 | } 28 | 29 | static func *(lhs: CGSize, rhs: CGSize) -> CGSize { 30 | return CGSize(width: lhs.width * rhs.width, height: lhs.height * rhs.height) 31 | } 32 | 33 | static func *(lhs: CGSize, rhs: CGFloat) -> CGSize { 34 | return CGSize(width: lhs.width * rhs, height: lhs.height * rhs) 35 | } 36 | 37 | static func *(lhs: CGFloat, rhs: CGSize) -> CGSize { 38 | return CGSize(width: rhs.width * lhs, height: rhs.height * lhs) 39 | } 40 | 41 | static func /(lhs: CGSize, rhs: CGSize) -> CGSize { 42 | return CGSize(width: lhs.width / rhs.width, height: lhs.height / rhs.height) 43 | } 44 | 45 | static func /(lhs: CGSize, rhs: CGFloat) -> CGSize { 46 | return CGSize(width: lhs.width / rhs, height: lhs.height / rhs) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Extensions/Number+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Number+Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Int { 11 | func interpolate(to: Int, progress: CGFloat) -> Int { 12 | guard progress >= 0.0 && progress <= 1.0 else { return self } 13 | return self + Int(CGFloat(to - self) * progress) 14 | } 15 | 16 | var int32: UInt32 { 17 | return UInt32(self) 18 | } 19 | 20 | var float: Float { 21 | return Float(self) 22 | } 23 | 24 | var double: Double { 25 | return Double(self) 26 | } 27 | 28 | var cgFloat: CGFloat { 29 | return CGFloat(self) 30 | } 31 | 32 | var string: String { 33 | return String(describing: self) 34 | } 35 | 36 | static func extract(from string: String) -> Int? { 37 | return Int(string.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) 38 | } 39 | } 40 | 41 | extension Float { 42 | 43 | var int: Int { 44 | return Int(self) 45 | } 46 | 47 | var int32: UInt32 { 48 | return UInt32(self) 49 | } 50 | 51 | var double: Double { 52 | return Double(self) 53 | } 54 | 55 | var cgFloat: CGFloat { 56 | return CGFloat(self) 57 | } 58 | 59 | var string: String { 60 | return String(describing: self) 61 | } 62 | } 63 | 64 | extension Double { 65 | var float: Float { 66 | return Float(self) 67 | } 68 | 69 | var int: Int { 70 | return Int(self) 71 | } 72 | 73 | var int32: UInt32 { 74 | return UInt32(self) 75 | } 76 | 77 | var cgFloat: CGFloat { 78 | return CGFloat(self) 79 | } 80 | 81 | var string: String { 82 | return String(describing: self) 83 | } 84 | } 85 | 86 | let π = CGFloat.pi 87 | 88 | extension CGFloat { 89 | func interpolate(to: CGFloat, progress: CGFloat) -> CGFloat { 90 | guard progress >= 0.0 && progress <= 1.0 else { return self } 91 | return self + (to - self) * progress 92 | } 93 | 94 | var int: Int { 95 | return Int(self) 96 | } 97 | 98 | var int32: UInt32 { 99 | return UInt32(self) 100 | } 101 | 102 | var double: Double { 103 | return Double(self) 104 | } 105 | 106 | var float: Float { 107 | return Float(self) 108 | } 109 | 110 | var string: String { 111 | return String(describing: self) 112 | } 113 | // Converts an angle in degrees to radians. 114 | func degreesToRadians() -> CGFloat { 115 | 116 | return π * self / 180.0 117 | } 118 | 119 | // Converts an angle in radians to degrees. 120 | func radiansToDegrees() -> CGFloat { 121 | return self * 180.0 / π 122 | } 123 | 124 | // Ensures that the float value stays between the given values, inclusive. 125 | func clamped(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 126 | let min = v1 < v2 ? v1 : v2 127 | let max = v1 > v2 ? v1 : v2 128 | return self < min ? min : (self > max ? max : self) 129 | } 130 | 131 | // Ensures that the float value stays between the given values, inclusive. 132 | mutating func clamp(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 133 | self = clamped(v1, v2) 134 | return self 135 | } 136 | 137 | // Returns 1.0 if a floating point value is positive; -1.0 if it is negative. 138 | func sign() -> CGFloat { 139 | return (self >= 0.0) ? 1.0 : -1.0 140 | } 141 | 142 | func rounded(symbolsAfterComma count: Int) -> CGFloat { 143 | let multiplier = pow(10, count.double).cgFloat 144 | let result = (self * multiplier).rounded() / multiplier 145 | return result 146 | } 147 | } 148 | 149 | extension UInt32 { 150 | var float: Float { 151 | return Float(self) 152 | } 153 | 154 | var int: Int { 155 | return Int(self) 156 | } 157 | 158 | var cgFloat: CGFloat { 159 | return CGFloat(self) 160 | } 161 | 162 | var string: String { 163 | return String(describing: self) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Extensions/UIColor + Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor + Hex.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 10/10/2022. 6 | // 7 | 8 | import UIKit 9 | import simd 10 | 11 | extension UIColor { 12 | 13 | public convenience init?(hex: String) { 14 | var hexInt: UInt64 = 0 15 | let scanner = Scanner(string: hex) 16 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") 17 | scanner.scanHexInt64(&hexInt) 18 | 19 | let red = CGFloat((hexInt & 0xFF0000) >> 16) / 255.0 20 | let green = CGFloat((hexInt & 0xFF00) >> 8) / 255.0 21 | let blue = CGFloat((hexInt & 0xFF) >> 0) / 255.0 22 | 23 | self.init(red: red, green: green, blue: blue, alpha: 1.0) 24 | } 25 | 26 | static var random: UIColor { 27 | return UIColor(red: .random(in: 0...1), 28 | green: .random(in: 0...1), 29 | blue: .random(in: 0...1), 30 | alpha: 1.0) 31 | } 32 | 33 | var hexString: String { 34 | let (r, g, b, _) = rgbaCGFloat 35 | 36 | let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 37 | 38 | return String(format:"#%06x", rgb) 39 | } 40 | 41 | typealias RGBA = (r: T, g: T, b: T, a: T) 42 | 43 | var rgbaCGFloat: RGBA { 44 | var r: CGFloat = .zero 45 | var g: CGFloat = .zero 46 | var b: CGFloat = .zero 47 | var a: CGFloat = .zero 48 | getRed(&r, green: &g, blue: &b, alpha: &a) 49 | return (r, g, b, a) 50 | } 51 | 52 | var rgbaDouble: RGBA { 53 | let (r,g,b,a) = rgbaCGFloat 54 | return (r.double, g.double, b.double, a.double) 55 | } 56 | 57 | var vectorFloat4: vector_float4 { 58 | let (r,g,b,a) = rgbaDouble 59 | return vector_float4(r.float, g.float, b.float, a.float) 60 | } 61 | 62 | var hsba: (h: CGFloat, s: CGFloat, b: CGFloat, a: CGFloat) { 63 | var h: CGFloat = .zero 64 | var s: CGFloat = .zero 65 | var b: CGFloat = .zero 66 | var a: CGFloat = .zero 67 | getHue(&h, saturation: &s, brightness: &b, alpha: &a) 68 | return (h, s, b, a) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_01_Starter/Draw App/Supporting Files/.DS_Store -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/DrawingApp-BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingApp-BridgingHeader.h 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | #ifndef DrawingApp_BridgingHeader_h 9 | #define DrawingApp_BridgingHeader_h 10 | 11 | #endif /* DrawingApp_BridgingHeader_h */ 12 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/Supporting Files/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | 18 | let window = UIWindow(windowScene: windowScene) 19 | window.rootViewController = ViewController() 20 | self.window = window 21 | window.makeKeyAndVisible() 22 | } 23 | 24 | func sceneDidDisconnect(_ scene: UIScene) { 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | func sceneDidBecomeActive(_ scene: UIScene) { 32 | // Called when the scene has moved from an inactive state to an active state. 33 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 34 | } 35 | 36 | func sceneWillResignActive(_ scene: UIScene) { 37 | // Called when the scene will move from an active state to an inactive state. 38 | // This may occur due to temporary interruptions (ex. an incoming phone call). 39 | } 40 | 41 | func sceneWillEnterForeground(_ scene: UIScene) { 42 | // Called as the scene transitions from the background to the foreground. 43 | // Use this method to undo the changes made on entering the background. 44 | } 45 | 46 | func sceneDidEnterBackground(_ scene: UIScene) { 47 | // Called as the scene transitions from the foreground to the background. 48 | // Use this method to save data, release shared resources, and store enough scene-specific state information 49 | // to restore the scene back to its current state. 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Draw_App_01_Starter/Draw App/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import MetalKit 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Draw_App_02_Final/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/BottomView/Color Picker/ColorPaletteCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPaletteCollectionViewCell.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 20/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class ColorPaletteCollectionViewCell: UICollectionViewCell { 11 | 12 | var item: CollorPickerBottomSheetController.Item? 13 | var imageView: UIImageView? 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | setupViews() 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | super.init(coder: coder) 22 | setupViews() 23 | } 24 | 25 | func setup(with item: CollorPickerBottomSheetController.Item) { 26 | self.item = item 27 | switch item { 28 | case .add: 29 | imageView?.isHidden = false 30 | contentView.backgroundColor = .clear 31 | case .paletteItem(let item): 32 | contentView.backgroundColor = item.color.uiColor 33 | imageView?.isHidden = true 34 | } 35 | 36 | if isSelected { 37 | contentView.layer.borderWidth = 3 38 | contentView.layer.borderColor = UIColor.white.cgColor 39 | } else { 40 | contentView.layer.borderColor = UIColor.clear.cgColor 41 | contentView.layer.borderWidth = 0 42 | } 43 | } 44 | 45 | private func setupViews() { 46 | contentView.layer.cornerRadius = 15 47 | contentView.layer.masksToBounds = true 48 | imageView = UIImageView(frame: bounds) 49 | imageView?.image = UIImage.generateAddIconColorPicker() 50 | contentView.addSubviewPinnedToEdges(imageView!) 51 | } 52 | 53 | override func prepareForReuse() { 54 | super.prepareForReuse() 55 | self.imageView?.isHidden = true 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/BottomView/Color Picker/ColorPickerSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPickerSlider.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 16/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class ColorPickerSlider: UISlider { 11 | 12 | private var currentColor: Color? 13 | 14 | override init(frame: CGRect) { 15 | super.init(frame: frame) 16 | initialSetup() 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | super.init(coder: coder) 21 | initialSetup() 22 | } 23 | 24 | private func initialSetup() { 25 | if let trackImage = UIImage.generateOpacitySliderTrackImage(color: .black) { 26 | self.setMaximumTrackImage(trackImage, for: .normal) 27 | self.setMinimumTrackImage(trackImage, for: .normal) 28 | } 29 | setThumbImage(UIImage(named: "opacity_slider_thumb"), for: .normal) 30 | } 31 | 32 | override func trackRect(forBounds bounds: CGRect) -> CGRect { 33 | var result = super.trackRect(forBounds: bounds) 34 | result.size.height = 36 35 | result.origin = .zero 36 | return result 37 | } 38 | 39 | func updateSelectedColor(color: Color) { 40 | if value.cgFloat != color.alpha { 41 | value = color.alpha.float 42 | } 43 | if let currentColor, currentColor.hex == color.hex { 44 | return 45 | } 46 | currentColor = color 47 | if let trackImage = UIImage.generateOpacitySliderTrackImage(color: color.uiColor) { 48 | self.setMaximumTrackImage(trackImage, for: .normal) 49 | self.setMinimumTrackImage(trackImage, for: .normal) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/BottomView/EditorBottomSegementedControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorBottomSegementedControl.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 16/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class EditorBottomSegementedControl: UISegmentedControl { 11 | 12 | lazy var backgroundBlur: UIVisualEffectView = { 13 | let effect = UIBlurEffect(style: .dark) 14 | let black = UIVisualEffectView(effect: effect) 15 | return black 16 | }() 17 | 18 | override init(items: [Any]?) { 19 | super.init(items: items) 20 | commonInit() 21 | } 22 | 23 | override init(frame: CGRect) { 24 | super.init(frame: frame) 25 | commonInit() 26 | } 27 | 28 | required init?(coder: NSCoder) { 29 | super.init(coder: coder) 30 | commonInit() 31 | } 32 | 33 | private func commonInit() { 34 | layer.masksToBounds = true 35 | clipsToBounds = true 36 | selectedSegmentTintColor = UIColor(hex: "5D5D5D") 37 | tintColor = UIColor(hex: "5D5D5D") 38 | setTitleTextAttributes([.foregroundColor: UIColor.white], for: .normal) 39 | insertSubview(backgroundBlur, at: .zero) 40 | } 41 | 42 | override func layoutSubviews() { 43 | super.layoutSubviews() 44 | layer.masksToBounds = true 45 | layer.cornerRadius = bounds.height / 2 46 | backgroundBlur.frame = bounds 47 | sendSubviewToBack(backgroundBlur) 48 | } 49 | 50 | override func addSubview(_ view: UIView) { 51 | let newBounds = view.layer.bounds.inset(by: UIEdgeInsets(top: 6, left: 7, bottom: 0, right: 0)) 52 | 53 | let path = UIBezierPath(roundedRect: newBounds, cornerRadius: newBounds.height / 2) 54 | let mask = CAShapeLayer() 55 | mask.path = path.cgPath 56 | view.layer.mask = mask 57 | 58 | super.addSubview(view) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/BottomView/EditorBottomSizeEditor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorBottomSizeEditor.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class EditorBottomSizeEditor: UIView { 11 | var brushImageView: UIImageView? 12 | var sizeSlider: SizeSlider? 13 | var brushTypeButton: UIButton? 14 | var backButton: UIButton? 15 | 16 | var selectedBrush: Brush 17 | let viewModel: EditorBottomViewModel 18 | 19 | init(selectedBrush: Brush, viewModel: EditorBottomViewModel, frame: CGRect) { 20 | self.selectedBrush = selectedBrush 21 | self.viewModel = viewModel 22 | super.init(frame: frame) 23 | initialSetup() 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | private func initialSetup() { 31 | brushImageView = UIImageView(frame: CGRect(origin: CGPoint(x: (bounds.width - 40) / 2 , y: .zero), size: CGSize(width: 40, height: 120))) 32 | brushImageView?.contentMode = .scaleAspectFit 33 | brushImageView?.image = selectedBrush.icon() 34 | addSubview(brushImageView!) 35 | 36 | sizeSlider = SizeSlider(frame: CGRect(origin: CGPoint(x: 46.5 , y: brushImageView!.frame.maxY), size: CGSize(width: bounds.width-134, height: 28))) 37 | sizeSlider?.value = selectedBrush.width 38 | sizeSlider?.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged) 39 | addSubview(sizeSlider!) 40 | 41 | } 42 | 43 | @objc private func sliderValueChanged(_ slider: UISlider) { 44 | print("slider value changed: \(slider.value)") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/BottomView/SizeSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SizeSlider.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 26/10/2022. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class SizeSlider: UISlider { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | initialSetup() 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | super.init(coder: coder) 20 | initialSetup() 21 | } 22 | 23 | private func initialSetup() { 24 | if let trackImage = UIImage.generateSizeSlider(isRotated: true) { 25 | self.setMaximumTrackImage(trackImage, for: .normal) 26 | self.setMinimumTrackImage(trackImage, for: .normal) 27 | } 28 | 29 | if let thumbImage = UIImage.generateSizeSliderThumb(size: CGSize(width: 32, height: 32)) { 30 | self.setThumbImage(thumbImage, for: .normal) 31 | } 32 | value = 0.5 33 | } 34 | 35 | override func trackRect(forBounds bounds: CGRect) -> CGRect { 36 | var result = super.trackRect(forBounds: bounds) 37 | result.size.height = 24 38 | result.origin = .zero 39 | return result 40 | } 41 | 42 | func runTransitionAnimation(from segmentedControl: EditorBottomSegementedControl) { 43 | layer.sublayers?.forEach({ $0.isHidden = true }) 44 | } 45 | 46 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 47 | super.touchesBegan(touches, with: event) 48 | 49 | var sliderTransforms = self.transform 50 | sliderTransforms = sliderTransforms.concatenating(CGAffineTransform(translationX: 20, y: .zero)) 51 | UIView.animate(withDuration: 0.2, delay: .zero) { 52 | self.transform = sliderTransforms 53 | } 54 | } 55 | 56 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 57 | super.touchesEnded(touches, with: event) 58 | var sliderTransforms = self.transform 59 | sliderTransforms = sliderTransforms.concatenating(CGAffineTransform(translationX: -20, y: .zero)) 60 | UIView.animate(withDuration: 0.2, delay: .zero) { 61 | self.transform = sliderTransforms 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/DrawingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import MetalKit 9 | import Combine 10 | import UIKit 11 | 12 | class DrawingViewController: UIViewController { 13 | 14 | private var drawingView: DrawingView? { 15 | return self.view as? DrawingView 16 | } 17 | 18 | private var editorViewModel = EditorBottomViewModel() 19 | 20 | private lazy var bottomView: EditorBottomView = { 21 | return EditorBottomView(viewModel: editorViewModel) 22 | }() 23 | 24 | private var renderer: Renderer? 25 | private var bindings = Set() 26 | 27 | override func loadView() { 28 | super.loadView() 29 | self.view = DrawingView(frame: UIScreen.main.bounds, brush: editorViewModel.selectedBrush, device: MTLCreateSystemDefaultDevice()) 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | guard let drawingView else { return } 35 | renderer = Renderer(metalKitView: drawingView) 36 | view.addSubview(bottomView) 37 | 38 | bottomView.pinToSuperviewEdges(exclude: .top, respectingSafeArea: false) 39 | NSLayoutConstraint.activate([ 40 | bottomView.heightAnchor.constraint(equalToConstant: 88 + 33 + 25), 41 | ]) 42 | 43 | editorViewModel.$state.receive(on: DispatchQueue.main) 44 | .sink { [weak self] state in 45 | guard let self = self else { return } 46 | switch state { 47 | case .draw(let brush, let presentationState): 48 | switch presentationState { 49 | case .updated: 50 | self.drawingView?.selectedBrush = brush 51 | case .show: 52 | self.drawingView?.selectedBrush = brush 53 | case .hide: 54 | print("brush: \(brush) hide") 55 | } 56 | case .brushConfiguration(let brush, let presentationState): 57 | switch presentationState { 58 | case .updated: 59 | print("brushConfiguration updated, brush: \(brush)") 60 | case .show: 61 | print("brushConfiguration show, brush: \(brush)") 62 | case .hide: 63 | print("brushConfiguration hide, brush: \(brush)") 64 | } 65 | self.drawingView?.selectedBrush = brush 66 | case let .colorPicker(color, _, presentationState): 67 | switch presentationState { 68 | case .show: 69 | print("color picker show") 70 | CollorPickerBottomSheetController.buildAndPresent( 71 | viewController: self, 72 | viewModel: self.editorViewModel 73 | ) 74 | case .hide: 75 | print("color picker hide") 76 | case .updated: 77 | print("color picker updated") 78 | } 79 | self.drawingView?.selectedBrush.color = color 80 | case .clear(_): 81 | self.drawingView?.clear?() 82 | default: 83 | break 84 | } 85 | }.store(in: &bindings) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/CGImage + PixelColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGImage + PixelColor.swift 3 | // telegram-contest 4 | // 5 | // Created by Bogdan Redkin on 29/10/2022. 6 | // 7 | 8 | import CoreGraphics 9 | import UIKit 10 | 11 | extension CGImage { 12 | 13 | func pixel(x: Int, y: Int) -> (r: Int, g: Int, b: Int)? { 14 | guard let pixelData = dataProvider?.data, 15 | let data = CFDataGetBytePtr(pixelData) else { return nil } 16 | 17 | let pixelInfo = ((width * y) + x ) * 4 18 | 19 | let red = Int(data[pixelInfo]) 20 | let green = Int(data[(pixelInfo + 1)]) 21 | let blue = Int(data[pixelInfo + 2]) 22 | 23 | return (red, green, blue) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/CGPoint + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint + Arithmetic.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | 11 | extension CGPoint { 12 | func interpolate(to: CGPoint, progress: CGFloat) -> CGPoint { 13 | guard progress >= 0.0 && progress <= 1.0 else { return self } 14 | let x = self.x.interpolate(to: to.x, progress: progress) 15 | let y = self.y.interpolate(to: to.y, progress: progress) 16 | return CGPoint(x: x, y: y) 17 | } 18 | 19 | init(vector: vector_uint2) { 20 | self.init(x: CGFloat(vector.x), y: CGFloat(vector.y)) 21 | } 22 | 23 | init(vector: vector_float2) { 24 | self.init(x: vector.x.cgFloat, y: vector.y.cgFloat) 25 | } 26 | 27 | var vectorFloat: vector_float2 { 28 | return vector_float2(x: x.float, y: y.float) 29 | } 30 | 31 | var vectorUint: vector_uint2 { 32 | return vector_uint2(x: x.int32, y: y.int32) 33 | } 34 | 35 | // Arithmetic 36 | 37 | static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 38 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 39 | } 40 | 41 | static func -(lhs: CGPoint, rhs: CGPoint) -> CGVector { 42 | return CGVector(dx: lhs.x - rhs.x, dy: lhs.y - rhs.y) 43 | } 44 | 45 | static func -(lhs: CGPoint, rhs: CGVector) -> CGPoint { 46 | return CGPoint(x: lhs.x - rhs.dx, y: lhs.y - rhs.dy) 47 | } 48 | 49 | static func *(lhs: CGPoint, rhs: CGFloat) -> CGPoint { 50 | return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) 51 | } 52 | 53 | static func *(lhs: CGFloat, rhs: CGPoint) -> CGPoint { 54 | return CGPoint(x: rhs.x * lhs, y: rhs.y * lhs) 55 | } 56 | 57 | func distanceToPoint(otherPoint: CGPoint) -> CGFloat { 58 | return sqrt(pow((otherPoint.x - x), 2) + pow((otherPoint.y - y), 2)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/CGRect + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension CGRect { 11 | 12 | public init(size: CGSize) { 13 | self.init(origin: .zero, size: size) 14 | } 15 | 16 | func scale(_ factor: CGFloat) -> CGRect { 17 | let transform = CGAffineTransform(scaleX: factor, y: factor) 18 | return self.applying(transform) 19 | } 20 | 21 | func interpolate(to: CGRect, progress: CGFloat) -> CGRect { 22 | guard progress >= 0.0, progress <= 1.0 else { return self } 23 | let origin = origin.interpolate(to: to.origin, progress: progress) 24 | let size = size.interpolate(to: to.size, progress: progress) 25 | return CGRect(origin: origin, size: size) 26 | } 27 | 28 | var center: CGPoint { 29 | return CGPoint(x: midX, y: midY) 30 | } 31 | 32 | init(center: CGPoint, size: CGSize) { 33 | self.init(origin: CGPoint(x: center.x - size.width / 2, y: center.y - size.height / 2), size: size) 34 | } 35 | 36 | func withOrigin(_ newOrigin: CGPoint) -> CGRect { 37 | var newRect = self 38 | newRect.origin = newOrigin 39 | return newRect 40 | } 41 | 42 | func withX(_ newX: CGFloat) -> CGRect { 43 | return withOrigin(CGPoint(x: newX, y: minY)) 44 | } 45 | 46 | func withY(_ newY: CGFloat) -> CGRect { 47 | return withOrigin(CGPoint(x: minX, y: newY)) 48 | } 49 | 50 | func withSize(_ newValue: CGSize) -> CGRect { 51 | var newRect = self 52 | newRect.size = newValue 53 | return newRect 54 | } 55 | 56 | func withWidth(_ newWidth: CGFloat) -> CGRect { 57 | return withSize(CGSize(width: newWidth, height: height)) 58 | } 59 | 60 | 61 | func withHeight(_ newHeight: CGFloat) -> CGRect { 62 | return withSize(CGSize(width: width, height: newHeight)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/CGSize + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSize + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension CGSize { 11 | 12 | init(size: CGFloat) { 13 | self.init(width: size, height: size) 14 | } 15 | 16 | func interpolate(to: CGSize, progress: CGFloat) -> CGSize { 17 | guard progress >= 0.0 && progress <= 1.0 else { return self } 18 | 19 | let width = self.width.interpolate(to: to.width, progress: progress) 20 | let height = self.height.interpolate(to: to.height, progress: progress) 21 | return CGSize(width: width, height: height) 22 | } 23 | 24 | func scale(_ factor: CGFloat) -> CGSize { 25 | let transform = CGAffineTransform(scaleX: factor, y: factor) 26 | return self.applying(transform) 27 | } 28 | 29 | static func *(lhs: CGSize, rhs: CGSize) -> CGSize { 30 | return CGSize(width: lhs.width * rhs.width, height: lhs.height * rhs.height) 31 | } 32 | 33 | static func *(lhs: CGSize, rhs: CGFloat) -> CGSize { 34 | return CGSize(width: lhs.width * rhs, height: lhs.height * rhs) 35 | } 36 | 37 | static func *(lhs: CGFloat, rhs: CGSize) -> CGSize { 38 | return CGSize(width: rhs.width * lhs, height: rhs.height * lhs) 39 | } 40 | 41 | static func /(lhs: CGSize, rhs: CGSize) -> CGSize { 42 | return CGSize(width: lhs.width / rhs.width, height: lhs.height / rhs.height) 43 | } 44 | 45 | static func /(lhs: CGSize, rhs: CGFloat) -> CGSize { 46 | return CGSize(width: lhs.width / rhs, height: lhs.height / rhs) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/MTLTexture + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTLTexture + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 20/06/2023. 6 | // 7 | 8 | import Metal 9 | import UIKit 10 | import simd 11 | 12 | extension MTLTexture { 13 | // func clear() { 14 | // let region = MTLRegion( 15 | // origin: MTLOrigin(x: 0, y: 0, z: 0), 16 | // size: MTLSize(width: width, height: height, depth: 1) 17 | // ) 18 | // let bytesPerRow = 4 * width 19 | // let data = Data(capacity: Int(bytesPerRow * height)) 20 | // if let bytes = data.withUnsafeBytes({ $0.baseAddress }) { 21 | // replace(region: region, mipmapLevel: 0, withBytes: bytes, bytesPerRow: bytesPerRow) 22 | // } 23 | // } 24 | } 25 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/Number+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Number+Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Int { 11 | func interpolate(to: Int, progress: CGFloat) -> Int { 12 | guard progress >= 0.0 && progress <= 1.0 else { return self } 13 | return self + Int(CGFloat(to - self) * progress) 14 | } 15 | 16 | var int32: UInt32 { 17 | return UInt32(self) 18 | } 19 | 20 | var float: Float { 21 | return Float(self) 22 | } 23 | 24 | var double: Double { 25 | return Double(self) 26 | } 27 | 28 | var cgFloat: CGFloat { 29 | return CGFloat(self) 30 | } 31 | 32 | var string: String { 33 | return String(describing: self) 34 | } 35 | 36 | static func extract(from string: String) -> Int? { 37 | return Int(string.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) 38 | } 39 | } 40 | 41 | extension Float { 42 | 43 | var int: Int { 44 | return Int(self) 45 | } 46 | 47 | var int32: UInt32 { 48 | return UInt32(self) 49 | } 50 | 51 | var double: Double { 52 | return Double(self) 53 | } 54 | 55 | var cgFloat: CGFloat { 56 | return CGFloat(self) 57 | } 58 | 59 | var string: String { 60 | return String(describing: self) 61 | } 62 | } 63 | 64 | extension Double { 65 | var float: Float { 66 | return Float(self) 67 | } 68 | 69 | var int: Int { 70 | return Int(self) 71 | } 72 | 73 | var int32: UInt32 { 74 | return UInt32(self) 75 | } 76 | 77 | var cgFloat: CGFloat { 78 | return CGFloat(self) 79 | } 80 | 81 | var string: String { 82 | return String(describing: self) 83 | } 84 | } 85 | 86 | let π = CGFloat.pi 87 | 88 | extension CGFloat { 89 | func interpolate(to: CGFloat, progress: CGFloat) -> CGFloat { 90 | guard progress >= 0.0 && progress <= 1.0 else { return self } 91 | return self + (to - self) * progress 92 | } 93 | 94 | var int: Int { 95 | return Int(self) 96 | } 97 | 98 | var int32: UInt32 { 99 | return UInt32(self) 100 | } 101 | 102 | var double: Double { 103 | return Double(self) 104 | } 105 | 106 | var float: Float { 107 | return Float(self) 108 | } 109 | 110 | var string: String { 111 | return String(describing: self) 112 | } 113 | // Converts an angle in degrees to radians. 114 | func degreesToRadians() -> CGFloat { 115 | 116 | return π * self / 180.0 117 | } 118 | 119 | // Converts an angle in radians to degrees. 120 | func radiansToDegrees() -> CGFloat { 121 | return self * 180.0 / π 122 | } 123 | 124 | // Ensures that the float value stays between the given values, inclusive. 125 | func clamped(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 126 | let min = v1 < v2 ? v1 : v2 127 | let max = v1 > v2 ? v1 : v2 128 | return self < min ? min : (self > max ? max : self) 129 | } 130 | 131 | // Ensures that the float value stays between the given values, inclusive. 132 | mutating func clamp(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 133 | self = clamped(v1, v2) 134 | return self 135 | } 136 | 137 | // Returns 1.0 if a floating point value is positive; -1.0 if it is negative. 138 | func sign() -> CGFloat { 139 | return (self >= 0.0) ? 1.0 : -1.0 140 | } 141 | 142 | func rounded(symbolsAfterComma count: Int) -> CGFloat { 143 | let multiplier = pow(10, count.double).cgFloat 144 | let result = (self * multiplier).rounded() / multiplier 145 | return result 146 | } 147 | } 148 | 149 | extension UInt32 { 150 | var float: Float { 151 | return Float(self) 152 | } 153 | 154 | var int: Int { 155 | return Int(self) 156 | } 157 | 158 | var cgFloat: CGFloat { 159 | return CGFloat(self) 160 | } 161 | 162 | var string: String { 163 | return String(describing: self) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/UIApplication + currentWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication + currentWindow.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 06/06/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIApplication { 11 | 12 | var window: UIWindow? { 13 | // Get connected scenes 14 | let scene = UIApplication.shared.connectedScenes 15 | // Keep only active scenes, onscreen and visible to the user 16 | .filter { $0.activationState == .foregroundActive } 17 | // Keep only the first `UIWindowScene` 18 | .first(where: { $0 is UIWindowScene }) 19 | // Get its associated windows 20 | .flatMap { $0 as? UIWindowScene } 21 | 22 | return scene?.keyWindow 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/UIColor + Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor + Hex.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 10/10/2022. 6 | // 7 | 8 | import UIKit 9 | import simd 10 | 11 | extension UIColor { 12 | 13 | public convenience init?(hex: String) { 14 | var hexInt: UInt64 = 0 15 | let scanner = Scanner(string: hex) 16 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") 17 | scanner.scanHexInt64(&hexInt) 18 | 19 | let red = CGFloat((hexInt & 0xFF0000) >> 16) / 255.0 20 | let green = CGFloat((hexInt & 0xFF00) >> 8) / 255.0 21 | let blue = CGFloat((hexInt & 0xFF) >> 0) / 255.0 22 | 23 | self.init(red: red, green: green, blue: blue, alpha: 1.0) 24 | } 25 | 26 | static var random: UIColor { 27 | return UIColor(red: .random(in: 0...1), 28 | green: .random(in: 0...1), 29 | blue: .random(in: 0...1), 30 | alpha: 1.0) 31 | } 32 | 33 | var hexString: String { 34 | let (r, g, b, _) = rgbaCGFloat 35 | 36 | let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 37 | 38 | return String(format:"#%06x", rgb) 39 | } 40 | 41 | typealias RGBA = (r: T, g: T, b: T, a: T) 42 | 43 | var rgbaCGFloat: RGBA { 44 | var r: CGFloat = .zero 45 | var g: CGFloat = .zero 46 | var b: CGFloat = .zero 47 | var a: CGFloat = .zero 48 | getRed(&r, green: &g, blue: &b, alpha: &a) 49 | return (r, g, b, a) 50 | } 51 | 52 | var rgbaDouble: RGBA { 53 | let (r,g,b,a) = rgbaCGFloat 54 | return (r.double, g.double, b.double, a.double) 55 | } 56 | 57 | var vectorFloat4: vector_float4 { 58 | let (r,g,b,a) = rgbaDouble 59 | return vector_float4(r.float, g.float, b.float, a.float) 60 | } 61 | 62 | var hsba: (h: CGFloat, s: CGFloat, b: CGFloat, a: CGFloat) { 63 | var h: CGFloat = .zero 64 | var s: CGFloat = .zero 65 | var b: CGFloat = .zero 66 | var a: CGFloat = .zero 67 | getHue(&h, saturation: &s, brightness: &b, alpha: &a) 68 | return (h, s, b, a) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/UIControl + Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl + Combine.swift 3 | // telegram-contest 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | import UIKit 11 | 12 | final class UIControlSubscription: Subscription where SubscriberType.Input == Control { 13 | private var subscriber: SubscriberType? 14 | private let control: Control 15 | 16 | init(subscriber: SubscriberType, control: Control, event: UIControl.Event) { 17 | self.subscriber = subscriber 18 | self.control = control 19 | control.addTarget(self, action: #selector(self.eventHandler), for: event) 20 | } 21 | 22 | func request(_ demand: Subscribers.Demand) { } 23 | 24 | func cancel() { 25 | self.subscriber = nil 26 | } 27 | 28 | @objc private func eventHandler() { 29 | _ = self.subscriber?.receive(self.control) 30 | } 31 | } 32 | 33 | struct UIControlPublisher: Publisher { 34 | typealias Output = Control 35 | typealias Failure = Never 36 | 37 | let control: Control 38 | let controlEvents: UIControl.Event 39 | 40 | init(control: Control, events: UIControl.Event) { 41 | self.control = control 42 | self.controlEvents = events 43 | } 44 | 45 | func receive(subscriber: S) where S: Subscriber, S.Failure == UIControlPublisher.Failure, S.Input == UIControlPublisher.Output { 46 | let subscription = UIControlSubscription(subscriber: subscriber, control: control, event: controlEvents) 47 | subscriber.receive(subscription: subscription) 48 | } 49 | } 50 | 51 | protocol CombineCompatible {} 52 | extension UIControl: CombineCompatible {} 53 | extension CombineCompatible where Self: UIControl { 54 | func publisher(for events: UIControl.Event) -> UIControlPublisher { 55 | return UIControlPublisher(control: self, events: events) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Extensions/UIView + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView + Helpers.swift 3 | // telegram-contest 4 | // 5 | // Created by Bogdan Redkin on 16/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIView { 11 | 12 | func setAnchorPoint(_ point: CGPoint) { 13 | var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y) 14 | var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y); 15 | 16 | newPoint = newPoint.applying(transform) 17 | oldPoint = oldPoint.applying(transform) 18 | 19 | var position = layer.position 20 | 21 | position.x -= oldPoint.x 22 | position.x += newPoint.x 23 | 24 | position.y -= oldPoint.y 25 | position.y += newPoint.y 26 | 27 | layer.position = position 28 | layer.anchorPoint = point 29 | } 30 | 31 | var rootSuperView: UIView? { 32 | if superview?.superview == nil { 33 | return superview 34 | } else { 35 | return superview?.rootSuperView 36 | } 37 | } 38 | 39 | var recursiveAllSubviews: [UIView] { 40 | subviews + subviews.flatMap { $0.recursiveAllSubviews } 41 | } 42 | 43 | func firstSubview(of type: T.Type) -> T? { 44 | recursiveAllSubviews.first { $0 is T } as? T 45 | } 46 | 47 | func findFirstResponder() -> UIView? { 48 | for subview in subviews { 49 | if subview.isFirstResponder { 50 | return subview 51 | } 52 | 53 | if let recursiveSubView = subview.findFirstResponder() { 54 | return recursiveSubView 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Metal/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Metal/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Metal/DrawingGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingGestureRecognizer.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 25/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class DrawingGestureRecognizer: UIPanGestureRecognizer { 11 | 12 | typealias touchesResponse = (touches: Set, event: UIEvent, velocity: CGPoint) 13 | 14 | var touchesBeganHandler: ((touchesResponse) -> Void)? 15 | var touchesMovedHandler: ((touchesResponse) -> Void)? 16 | var touchesEndedHandler: ((touchesResponse) -> Void)? 17 | var touchesCancelledHandler: ((touchesResponse) -> Void)? 18 | 19 | override func touchesBegan(_ touches: Set, with event: UIEvent) { 20 | let response: touchesResponse = (touches, event, velocity(in: view)) 21 | touchesBeganHandler?(response) 22 | super.touchesBegan(touches, with: event) 23 | } 24 | 25 | override func touchesMoved(_ touches: Set, with event: UIEvent) { 26 | let response: touchesResponse = (touches, event, velocity(in: view)) 27 | touchesMovedHandler?(response) 28 | // print("touches moved: \(touches)") 29 | super.touchesMoved(touches, with: event) 30 | } 31 | 32 | override func touchesEnded(_ touches: Set, with event: UIEvent) { 33 | // guard !touches.filter({ $0.view == self.view }).isEmpty else { return } 34 | let response: touchesResponse = (touches, event, velocity(in: view)) 35 | touchesEndedHandler?(response) 36 | super.touchesEnded(touches, with: event) 37 | } 38 | 39 | override func touchesCancelled(_ touches: Set, with event: UIEvent) { 40 | // guard !touches.filter({ $0.view == self.view }).isEmpty else { return } 41 | let response: touchesResponse = (touches, event, velocity(in: view)) 42 | touchesCancelledHandler?(response) 43 | super.touchesCancelled(touches, with: event) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Metal/Shader.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Shader.metal 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 06/05/2023. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | struct PointVertex { 12 | float4 position [[position]]; 13 | float4 color; 14 | float size [[point_size]]; 15 | }; 16 | 17 | vertex PointVertex pointShaderVertex(constant PointVertex *points [[ buffer(0) ]], 18 | uint vid [[ vertex_id ]]) 19 | { 20 | PointVertex out = points[vid]; 21 | 22 | float2 pos = float2(out.position.x, out.position.y); 23 | out.position = float4(pos, 0, 1); 24 | return out; 25 | }; 26 | 27 | fragment float4 pointShaderFragment(PointVertex point_data [[ stage_in ]], 28 | float2 pointCoord [[ point_coord ]]) 29 | { 30 | float dist = length(pointCoord - float2(0.5)); 31 | if (dist >= 0.5) { 32 | return float4(0); 33 | } 34 | return point_data.color; 35 | }; 36 | 37 | struct TexturePipelineRasterizerData 38 | { 39 | float4 position [[position]]; 40 | float2 texcoord; 41 | }; 42 | 43 | struct TextureVertex 44 | { 45 | float2 position; 46 | float2 texcoord; 47 | }; 48 | 49 | 50 | // Vertex shader which adjusts positions by an aspect ratio and passes texture 51 | // coordinates through to the rasterizer. 52 | vertex TexturePipelineRasterizerData textureVertexShader(const uint vertexID [[ vertex_id ]], 53 | const device TextureVertex *vertices [[ buffer(0) ]]) 54 | { 55 | TexturePipelineRasterizerData out; 56 | 57 | out.position = vector_float4(vertices[vertexID].position.x, vertices[vertexID].position.y, 0.0, 1.0); 58 | 59 | out.texcoord = vertices[vertexID].texcoord; 60 | return out; 61 | } 62 | 63 | fragment float4 textureFragmentShader(TexturePipelineRasterizerData in [[stage_in]], texture2d texture [[texture(0)]]) 64 | { 65 | constexpr sampler textureSampler(mag_filter::linear, min_filter::linear); 66 | float4 colorSample = float4(texture.sample(textureSampler, in.texcoord)); 67 | return colorSample; 68 | } 69 | 70 | kernel void alphaBlend(texture2d outputTexture [[texture(0)]], 71 | texture2d inputTexture [[texture(1)]], 72 | texture2d inputTexture2 [[texture(2)]], 73 | constant float *mixturePercent [[buffer(0)]], 74 | uint2 grid [[thread_position_in_grid]]) { 75 | 76 | const half4 inColor = inputTexture.read(grid); 77 | constexpr sampler quadSampler(mag_filter::linear, min_filter::linear); 78 | 79 | const half4 inColor2 = inputTexture2.sample(quadSampler, float2(float(grid.x) / outputTexture.get_width(), float(grid.y) / outputTexture.get_height())); 80 | const half4 outColor(mix(inColor.rgb, inColor2.rgb, inColor2.a * half(*mixturePercent)), inColor.a); 81 | 82 | outputTexture.write(outColor, grid); 83 | } 84 | 85 | kernel void maskingBlend(texture2d outputTexture [[texture(0)]], 86 | texture2d inputTexture1 [[texture(1)]], 87 | texture2d inputTexture2 [[texture(2)]], 88 | uint2 gid [[thread_position_in_grid]]) 89 | { 90 | if (gid.x < inputTexture1.get_width() && gid.y < inputTexture1.get_height()) { 91 | float4 pixel1 = inputTexture1.read(gid); 92 | float4 pixel2 = inputTexture2.read(gid); 93 | 94 | if (pixel2.a > 0.0) { 95 | pixel1 = float4(pixel1.rgb, 0.0); 96 | } 97 | 98 | outputTexture.write(pixel1, gid); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Models/Brush.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Brush.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 17/10/2022. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | struct Brush { 12 | 13 | enum Style: String { 14 | case pen 15 | case brush 16 | case neon 17 | case pencil 18 | case eraser 19 | } 20 | 21 | let style: Style 22 | var width: Float 23 | var color: Color 24 | 25 | init(style: Style, width: Float? = nil, color: Color = Color(uiColor: .tintColor)) { 26 | self.style = style 27 | self.width = width ?? (style.maxWidth - style.minWidth)/2 28 | self.color = color 29 | } 30 | 31 | } 32 | 33 | extension Brush { 34 | 35 | var isAvailable: Bool { 36 | switch style { 37 | case .eraser, .pen: return true 38 | default: return false 39 | } 40 | } 41 | 42 | static func defaultBrushes() -> [Brush] { 43 | return [ 44 | Brush(style: .pen), 45 | Brush(style: .brush), 46 | Brush(style: .neon), 47 | Brush(style: .pencil), 48 | Brush(style: .eraser) 49 | ] 50 | } 51 | 52 | func icon() -> UIImage? { 53 | return UIImage.generateToolIcon(name: style.rawValue, color: color.uiColor, height: width.cgFloat) 54 | } 55 | 56 | 57 | } 58 | 59 | extension Brush.Style { 60 | var contentScale: Float { 61 | return (UIApplication.shared.window?.rootViewController?.view.contentScaleFactor ?? 1.0).float 62 | } 63 | 64 | var maxWidth: Float { 65 | switch self { 66 | case .eraser: 67 | return 50 * contentScale 68 | default: 69 | return 40 * contentScale 70 | } 71 | } 72 | 73 | var minWidth: Float { 74 | switch self { 75 | case .eraser: 76 | return 25 * contentScale 77 | default: 78 | return 13 * contentScale 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Models/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 20/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | struct Color: Codable { 11 | 12 | var red: CGFloat 13 | var green: CGFloat 14 | var blue: CGFloat 15 | var alpha: CGFloat 16 | 17 | var hex: String { 18 | uiColor.hexString 19 | } 20 | 21 | var uiColor: UIColor { 22 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 23 | } 24 | 25 | var hue: CGFloat { 26 | get { uiColor.hsba.h } 27 | set { 28 | var (h,s,b,a) = uiColor.hsba 29 | h = newValue 30 | let (r, g, blue, _) = UIColor(hue: h, saturation: s, brightness: b, alpha: a).rgbaCGFloat 31 | self.red = r 32 | self.green = g 33 | self.blue = blue 34 | } 35 | } 36 | 37 | var saturation: CGFloat { 38 | get { uiColor.hsba.s } 39 | set { 40 | var (h,s,b,a) = uiColor.hsba 41 | s = newValue 42 | let (r, g, blue, _) = UIColor(hue: h, saturation: s, brightness: b, alpha: a).rgbaCGFloat 43 | self.red = r 44 | self.green = g 45 | self.blue = blue 46 | } 47 | } 48 | 49 | var brightness: CGFloat { 50 | get { uiColor.hsba.b } 51 | set { 52 | var (h, s, b, a) = uiColor.hsba 53 | b = newValue 54 | let (r, g, blue, _) = UIColor(hue: h, saturation: s, brightness: b, alpha: a).rgbaCGFloat 55 | self.red = r 56 | self.green = g 57 | self.blue = blue 58 | } 59 | } 60 | 61 | init(uiColor: UIColor) { 62 | let (r, g, b, a) = uiColor.rgbaCGFloat 63 | red = r 64 | green = g 65 | blue = b 66 | alpha = a 67 | } 68 | 69 | init(hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat = 1) { 70 | let h = max(0, min(1, hue)) 71 | let s = max(0, min(1, saturation)) 72 | let b = max(0, min(1, brightness)) 73 | let a = max(0, min(1, alpha)) 74 | let uiColor = UIColor(hue: h, saturation: s, brightness: b, alpha: a) 75 | self.init(uiColor: uiColor) 76 | } 77 | 78 | init(hue: Int, saturation: Int, brightness: Int, alpha: CGFloat = 1) { 79 | self.init(hue: hue.cgFloat/359, saturation: saturation.cgFloat/100, brightness: brightness.cgFloat/100, alpha: alpha) 80 | } 81 | 82 | func save() { 83 | var array = Color.fetchSavedColors() 84 | array.append(self) 85 | if let encodedArray = try? JSONEncoder().encode(array), 86 | let string = String(data: encodedArray, encoding: .utf8) { 87 | UserDefaults.standard.set(string, forKey: "saved_colors") 88 | } 89 | } 90 | 91 | static func fetchSavedColors() -> [Color] { 92 | if let savedArray = UserDefaults.standard.string(forKey: "saved_colors"), 93 | let data = savedArray.data(using: .utf8), 94 | let decodedArray = try? JSONDecoder().decode([Color].self, from: data) 95 | { 96 | return decodedArray 97 | } else { 98 | return [] 99 | } 100 | } 101 | 102 | static func defaultPalette() -> [Color] { 103 | return [ 104 | UIColor.black, 105 | UIColor.systemBlue, 106 | UIColor.systemRed, 107 | UIColor.systemOrange 108 | ].map { Color(uiColor: $0) } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Models/Point.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Point.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 07/05/2023. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | import UIKit 11 | 12 | struct TextureVertex { 13 | var position: vector_float2 14 | var texcoord: vector_float2 15 | 16 | init(position: vector_float2, texcoord: vector_float2) { 17 | self.position = position 18 | self.texcoord = texcoord 19 | } 20 | 21 | init?(corner: UIRectCorner) { 22 | switch corner { 23 | case .topLeft: 24 | self.init(position: .init(x: -1.0, y: 1.0), texcoord: .init(0.0, 0.0)) 25 | case .topRight: 26 | self.init(position: .init(x: 1.0, y: 1.0), texcoord: .init(1.0, 0.0)) 27 | case .bottomLeft: 28 | self.init(position: .init(x: -1, y: -1), texcoord: .init(0.0, 1.0)) 29 | case .bottomRight: 30 | self.init(position: .init(x: 1, y: -1), texcoord: .init(1.0, 1.0)) 31 | default: 32 | return nil 33 | } 34 | } 35 | } 36 | 37 | struct Point { 38 | var position: vector_float4 39 | var color: vector_float4 40 | var size: Float 41 | 42 | init(position: vector_float4, color: vector_float4, size: Float) { 43 | self.position = position 44 | self.color = color 45 | self.size = size 46 | } 47 | 48 | init(x: CGFloat, y: CGFloat, color: UIColor, size: CGFloat) { 49 | self.init(position: vector_float4(Float(x), Float(y), 0, 1), color: color.vectorFloat4, size: Float(size)) 50 | } 51 | 52 | init(location: CGPoint, parentSize: CGSize, color: UIColor, size: CGFloat) { 53 | let roundedLocation = CGPoint(x: location.x.int.cgFloat, y: location.y.int.cgFloat) 54 | let xK = (1.0 / (parentSize.width / roundedLocation.x)).rounded(symbolsAfterComma: 5) 55 | let yK = (1.0 / (parentSize.height / roundedLocation.y)).rounded(symbolsAfterComma: 5) 56 | let x = xK == 0.5 ? 0.0 : (xK < 0.5 ? -(1 - xK / 0.5) : xK / 0.5 - 1) 57 | let y = yK == 0.5 ? 0.0 : (yK > 0.5 ? (1 - yK / 0.5) : 1 - (yK / 0.5)) 58 | self.init(x: x, y: y, color: color, size: size) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | 18 | let window = UIWindow(windowScene: windowScene) 19 | window.overrideUserInterfaceStyle = .light 20 | window.rootViewController = DrawingViewController() 21 | self.window = window 22 | window.makeKeyAndVisible() 23 | } 24 | 25 | func sceneDidDisconnect(_ scene: UIScene) { 26 | // Called as the scene is being released by the system. 27 | // This occurs shortly after the scene enters the background, or when its session is discarded. 28 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 29 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | func sceneDidBecomeActive(_ scene: UIScene) { 33 | 34 | } 35 | 36 | func sceneWillResignActive(_ scene: UIScene) { 37 | // Called when the scene will move from an active state to an inactive state. 38 | // This may occur due to temporary interruptions (ex. an incoming phone call). 39 | } 40 | 41 | func sceneWillEnterForeground(_ scene: UIScene) { 42 | // Called as the scene transitions from the background to the foreground. 43 | // Use this method to undo the changes made on entering the background. 44 | } 45 | 46 | func sceneDidEnterBackground(_ scene: UIScene) { 47 | // Called as the scene transitions from the foreground to the background. 48 | // Use this method to save data, release shared resources, and store enough scene-specific state information 49 | // to restore the scene back to its current state. 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/add.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "add.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/add.imageset/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/add.imageset/add.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/arrowTip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "arrowTip.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/arrowTip.imageset/arrowTip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/arrowTip.imageset/arrowTip.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "back.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/back.imageset/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/back.imageset/back.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/blurEraser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blurEraser.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/blurEraser.imageset/blurEraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/blurEraser.imageset/blurEraser.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/blurTip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blurTip.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/blurTip.imageset/blurTip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/blurTip.imageset/blurTip.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/brush.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "brush.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/brush.imageset/brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/brush.imageset/brush.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/brush_tip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "brush.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/brush_tip.imageset/brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/brush_tip.imageset/brush.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/cancel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cancel.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/cancel.imageset/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/cancel.imageset/cancel.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/close.imageset/Close.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E2 << /ca 0.600000 >> 5 | /E1 << /ca 0.240000 >> 6 | >> >> 7 | endobj 8 | 9 | 2 0 obj 10 | << /Length 3 0 R >> 11 | stream 12 | /DeviceRGB CS 13 | /DeviceRGB cs 14 | q 15 | /E1 gs 16 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 17 | 0.462745 0.462745 0.501961 scn 18 | 30.000000 15.000000 m 19 | 30.000000 6.715729 23.284271 0.000000 15.000000 0.000000 c 20 | 6.715729 0.000000 0.000000 6.715729 0.000000 15.000000 c 21 | 0.000000 23.284271 6.715729 30.000000 15.000000 30.000000 c 22 | 23.284271 30.000000 30.000000 23.284271 30.000000 15.000000 c 23 | h 24 | f 25 | n 26 | Q 27 | q 28 | /E2 gs 29 | 1.000000 0.000000 -0.000000 1.000000 9.500000 9.304749 cm 30 | 0.921569 0.921569 0.960784 scn 31 | 0.292893 9.488144 m 32 | -0.097631 9.878669 -0.097631 10.511834 0.292893 10.902358 c 33 | 0.683417 11.292882 1.316583 11.292882 1.707107 10.902358 c 34 | 5.500000 7.109465 l 35 | 9.292893 10.902358 l 36 | 9.683417 11.292882 10.316583 11.292882 10.707107 10.902358 c 37 | 11.097631 10.511834 11.097631 9.878669 10.707107 9.488144 c 38 | 6.914213 5.695251 l 39 | 10.707107 1.902358 l 40 | 11.097631 1.511834 11.097631 0.878669 10.707107 0.488145 c 41 | 10.316583 0.097620 9.683417 0.097620 9.292893 0.488145 c 42 | 5.500000 4.281038 l 43 | 1.707107 0.488145 l 44 | 1.316583 0.097620 0.683418 0.097620 0.292893 0.488145 c 45 | -0.097631 0.878669 -0.097631 1.511834 0.292893 1.902358 c 46 | 4.085787 5.695251 l 47 | 0.292893 9.488144 l 48 | h 49 | f* 50 | n 51 | Q 52 | 53 | endstream 54 | endobj 55 | 56 | 3 0 obj 57 | 1153 58 | endobj 59 | 60 | 4 0 obj 61 | << /Annots [] 62 | /Type /Page 63 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 64 | /Resources 1 0 R 65 | /Contents 2 0 R 66 | /Parent 5 0 R 67 | >> 68 | endobj 69 | 70 | 5 0 obj 71 | << /Kids [ 4 0 R ] 72 | /Count 1 73 | /Type /Pages 74 | >> 75 | endobj 76 | 77 | 6 0 obj 78 | << /Pages 5 0 R 79 | /Type /Catalog 80 | >> 81 | endobj 82 | 83 | xref 84 | 0 7 85 | 0000000000 65535 f 86 | 0000000010 00000 n 87 | 0000000132 00000 n 88 | 0000001341 00000 n 89 | 0000001364 00000 n 90 | 0000001537 00000 n 91 | 0000001611 00000 n 92 | trailer 93 | << /ID [ (some) (id) ] 94 | /Root 6 0 R 95 | /Size 7 96 | >> 97 | startxref 98 | 1670 99 | %%EOF -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Close.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/color_picker.imageset/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/color_picker.imageset/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/color_picker.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "color_picker_gradient.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/color_picker.imageset/color_picker_gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/color_picker.imageset/color_picker_gradient.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/download.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "download.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/download.imageset/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/download.imageset/download.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/eraser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "eraser.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/eraser.imageset/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/eraser.imageset/eraser.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/eyedropper.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "eyedropper.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/eyedropper.imageset/eyedropper.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /Length 2 0 R >> 5 | stream 6 | 1.243652 0 0.112305 -0.165527 1.158203 1.150879 d1 7 | 8 | endstream 9 | endobj 10 | 11 | 2 0 obj 12 | 51 13 | endobj 14 | 15 | 3 0 obj 16 | [ 1.243652 ] 17 | endobj 18 | 19 | 4 0 obj 20 | << /Length 5 0 R >> 21 | stream 22 | /CIDInit /ProcSet findresource begin 23 | 12 dict begin 24 | begincmap 25 | /CIDSystemInfo 26 | << /Registry (FigmaPDF) 27 | /Ordering (FigmaPDF) 28 | /Supplement 0 29 | >> def 30 | /CMapName /A-B-C def 31 | /CMapType 2 def 32 | 1 begincodespacerange 33 | <00> 34 | endcodespacerange 35 | 1 beginbfchar 36 | <00> 37 | endbfchar 38 | endcmap 39 | CMapName currentdict /CMap defineresource pop 40 | end 41 | end 42 | endstream 43 | endobj 44 | 45 | 5 0 obj 46 | 336 47 | endobj 48 | 49 | 6 0 obj 50 | << /Subtype /Type3 51 | /CharProcs << /C0 1 0 R >> 52 | /Encoding << /Type /Encoding 53 | /Differences [ 0 /C0 ] 54 | >> 55 | /Widths 3 0 R 56 | /FontBBox [ 0.000000 0.000000 0.000000 0.000000 ] 57 | /FontMatrix [ 1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 ] 58 | /Type /Font 59 | /ToUnicode 4 0 R 60 | /FirstChar 0 61 | /LastChar 0 62 | /Resources << >> 63 | >> 64 | endobj 65 | 66 | 7 0 obj 67 | << /Font << /F1 6 0 R >> >> 68 | endobj 69 | 70 | 8 0 obj 71 | << /Length 9 0 R >> 72 | stream 73 | /DeviceRGB CS 74 | /DeviceRGB cs 75 | q 76 | 1.000000 0.000000 -0.000000 1.000000 -5.824219 5.285156 cm 77 | 1.000000 1.000000 1.000000 scn 78 | 3.802734 -2.305664 m 79 | h 80 | 19.605469 3.723633 m 81 | 19.851562 3.996094 l 82 | 20.282227 4.444336 20.308594 4.971680 19.842773 5.428711 c 83 | 19.579102 5.683594 l 84 | 20.923828 6.887695 22.417969 7.054688 23.340820 7.995117 c 85 | 24.650391 9.313477 24.228516 11.159180 23.305664 12.082031 c 86 | 22.391602 13.013672 20.563477 13.409180 19.218750 12.117188 c 87 | 18.269531 11.203125 18.111328 9.700195 16.907227 8.355469 c 88 | 16.652344 8.619141 l 89 | 16.204102 9.076172 15.667969 9.058594 15.219727 8.627930 c 90 | 14.956055 8.381836 l 91 | 14.411133 7.863281 14.516602 7.406250 14.973633 6.940430 c 92 | 15.351562 6.571289 l 93 | 8.707031 -0.073242 l 94 | 5.991211 -2.789062 7.300781 -2.367188 5.824219 -4.450195 c 95 | 6.615234 -5.285156 l 96 | 8.627930 -3.817383 8.355469 -5.258789 11.115234 -2.507812 c 97 | 17.786133 4.127930 l 98 | 18.164062 3.750000 l 99 | 18.629883 3.284180 19.095703 3.187500 19.605469 3.723633 c 100 | h 101 | 8.531250 -2.736328 m 102 | 8.206055 -2.384766 8.276367 -2.050781 8.619141 -1.699219 c 103 | 16.107422 5.815430 l 104 | 17.056641 4.857422 l 105 | 9.541992 -2.648438 l 106 | 9.234375 -2.964844 8.830078 -3.061523 8.531250 -2.736328 c 107 | h 108 | f 109 | n 110 | Q 111 | q 112 | 1.000000 0.000000 -0.000000 1.000000 -5.824219 5.285156 cm 113 | BT 114 | 18.000000 0.000000 0.000000 18.000000 3.802734 -2.305664 Tm 115 | /F1 1.000000 Tf 116 | [ (\000) ] TJ 117 | ET 118 | Q 119 | 120 | endstream 121 | endobj 122 | 123 | 9 0 obj 124 | 1321 125 | endobj 126 | 127 | 10 0 obj 128 | << /Annots [] 129 | /Type /Page 130 | /MediaBox [ 0.000000 0.000000 18.826172 18.694336 ] 131 | /Resources 7 0 R 132 | /Contents 8 0 R 133 | /Parent 11 0 R 134 | >> 135 | endobj 136 | 137 | 11 0 obj 138 | << /Kids [ 10 0 R ] 139 | /Count 1 140 | /Type /Pages 141 | >> 142 | endobj 143 | 144 | 12 0 obj 145 | << /Pages 11 0 R 146 | /Type /Catalog 147 | >> 148 | endobj 149 | 150 | xref 151 | 0 13 152 | 0000000000 65535 f 153 | 0000000010 00000 n 154 | 0000000117 00000 n 155 | 0000000138 00000 n 156 | 0000000169 00000 n 157 | 0000000561 00000 n 158 | 0000000583 00000 n 159 | 0000000995 00000 n 160 | 0000001041 00000 n 161 | 0000002418 00000 n 162 | 0000002441 00000 n 163 | 0000002616 00000 n 164 | 0000002692 00000 n 165 | trailer 166 | << /ID [ (some) (id) ] 167 | /Root 12 0 R 168 | /Size 13 169 | >> 170 | startxref 171 | 2753 172 | %%EOF -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/neon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "neon.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/neon.imageset/neon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/neon.imageset/neon.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/neon_tip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "neon.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/neon_tip.imageset/neon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/neon_tip.imageset/neon.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/objectEraser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "objectEraser.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/objectEraser.imageset/objectEraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/objectEraser.imageset/objectEraser.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/opacity_slider_background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "VStack.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/opacity_slider_thumb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Ellipse 26.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/opacity_slider_thumb.imageset/Ellipse 26.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/opacity_slider_thumb.imageset/Ellipse 26.pdf -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pen.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pen.imageset/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pen.imageset/pen.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pen_tip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pen.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pen_tip.imageset/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pen_tip.imageset/pen.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pencil.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pencil.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pencil.imageset/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pencil.imageset/pencil.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pencil_tip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pencil.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pencil_tip.imageset/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/pencil_tip.imageset/pencil.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/roundTip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "roundTip.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/roundTip.imageset/roundTip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/roundTip.imageset/roundTip.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeArrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "shapeArrow.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeArrow.imageset/shapeArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeArrow.imageset/shapeArrow.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeBubble.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "shapeBubble.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeBubble.imageset/shapeBubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeBubble.imageset/shapeBubble.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeEllipse.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "shapeEllipse.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeEllipse.imageset/shapeEllipse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeEllipse.imageset/shapeEllipse.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeRectangle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "shapeRectangle.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeRectangle.imageset/shapeRectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeRectangle.imageset/shapeRectangle.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeStar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "shapeStar.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeStar.imageset/shapeStar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/shapeStar.imageset/shapeStar.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/size_slslider.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Rectangle 53@2x.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/size_slslider.imageset/Rectangle 53@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/size_slslider.imageset/Rectangle 53@2x.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/undo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "undo.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/undo.imageset/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/undo.imageset/undo.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/xmarkTip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "xmarkTip.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/xmarkTip.imageset/xmarkTip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/xmarkTip.imageset/xmarkTip.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/zoomOut.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "zoomOut.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/zoomOut.imageset/zoomOut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Final/Draw App/Supporting Files/Assets.xcassets/zoomOut.imageset/zoomOut.png -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/DrawingApp-BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingApp-BridgingHeader.h 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | #ifndef DrawingApp_BridgingHeader_h 9 | #define DrawingApp_BridgingHeader_h 10 | 11 | #endif /* DrawingApp_BridgingHeader_h */ 12 | -------------------------------------------------------------------------------- /Draw_App_02_Final/Draw App/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/BottomView/Color Picker/ColorPaletteCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPaletteCollectionViewCell.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 20/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class ColorPaletteCollectionViewCell: UICollectionViewCell { 11 | 12 | var item: CollorPickerBottomSheetController.Item? 13 | var imageView: UIImageView? 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | setupViews() 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | super.init(coder: coder) 22 | setupViews() 23 | } 24 | 25 | func setup(with item: CollorPickerBottomSheetController.Item) { 26 | self.item = item 27 | switch item { 28 | case .add: 29 | imageView?.isHidden = false 30 | contentView.backgroundColor = .clear 31 | case .paletteItem(let item): 32 | contentView.backgroundColor = item.color.uiColor 33 | imageView?.isHidden = true 34 | } 35 | 36 | if isSelected { 37 | contentView.layer.borderWidth = 3 38 | contentView.layer.borderColor = UIColor.white.cgColor 39 | } else { 40 | contentView.layer.borderColor = UIColor.clear.cgColor 41 | contentView.layer.borderWidth = 0 42 | } 43 | } 44 | 45 | private func setupViews() { 46 | contentView.layer.cornerRadius = 15 47 | contentView.layer.masksToBounds = true 48 | imageView = UIImageView(frame: bounds) 49 | imageView?.image = UIImage.generateAddIconColorPicker() 50 | contentView.addSubviewPinnedToEdges(imageView!) 51 | } 52 | 53 | override func prepareForReuse() { 54 | super.prepareForReuse() 55 | self.imageView?.isHidden = true 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/BottomView/Color Picker/ColorPickerSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPickerSlider.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 16/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class ColorPickerSlider: UISlider { 11 | 12 | private var currentColor: Color? 13 | 14 | override init(frame: CGRect) { 15 | super.init(frame: frame) 16 | initialSetup() 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | super.init(coder: coder) 21 | initialSetup() 22 | } 23 | 24 | private func initialSetup() { 25 | if let trackImage = UIImage.generateOpacitySliderTrackImage(color: .black) { 26 | self.setMaximumTrackImage(trackImage, for: .normal) 27 | self.setMinimumTrackImage(trackImage, for: .normal) 28 | } 29 | setThumbImage(UIImage(named: "opacity_slider_thumb"), for: .normal) 30 | } 31 | 32 | override func trackRect(forBounds bounds: CGRect) -> CGRect { 33 | var result = super.trackRect(forBounds: bounds) 34 | result.size.height = 36 35 | result.origin = .zero 36 | return result 37 | } 38 | 39 | func updateSelectedColor(color: Color) { 40 | if value.cgFloat != color.alpha { 41 | value = color.alpha.float 42 | } 43 | if let currentColor, currentColor.hex == color.hex { 44 | return 45 | } 46 | currentColor = color 47 | if let trackImage = UIImage.generateOpacitySliderTrackImage(color: color.uiColor) { 48 | self.setMaximumTrackImage(trackImage, for: .normal) 49 | self.setMinimumTrackImage(trackImage, for: .normal) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/BottomView/EditorBottomSegementedControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorBottomSegementedControl.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 16/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class EditorBottomSegementedControl: UISegmentedControl { 11 | 12 | lazy var backgroundBlur: UIVisualEffectView = { 13 | let effect = UIBlurEffect(style: .dark) 14 | let black = UIVisualEffectView(effect: effect) 15 | return black 16 | }() 17 | 18 | override init(items: [Any]?) { 19 | super.init(items: items) 20 | commonInit() 21 | } 22 | 23 | override init(frame: CGRect) { 24 | super.init(frame: frame) 25 | commonInit() 26 | } 27 | 28 | required init?(coder: NSCoder) { 29 | super.init(coder: coder) 30 | commonInit() 31 | } 32 | 33 | private func commonInit() { 34 | layer.masksToBounds = true 35 | clipsToBounds = true 36 | selectedSegmentTintColor = UIColor(hex: "5D5D5D") 37 | tintColor = UIColor(hex: "5D5D5D") 38 | setTitleTextAttributes([.foregroundColor: UIColor.white], for: .normal) 39 | insertSubview(backgroundBlur, at: .zero) 40 | } 41 | 42 | override func layoutSubviews() { 43 | super.layoutSubviews() 44 | layer.masksToBounds = true 45 | layer.cornerRadius = bounds.height / 2 46 | backgroundBlur.frame = bounds 47 | sendSubviewToBack(backgroundBlur) 48 | } 49 | 50 | override func addSubview(_ view: UIView) { 51 | let newBounds = view.layer.bounds.inset(by: UIEdgeInsets(top: 6, left: 7, bottom: 0, right: 0)) 52 | 53 | let path = UIBezierPath(roundedRect: newBounds, cornerRadius: newBounds.height / 2) 54 | let mask = CAShapeLayer() 55 | mask.path = path.cgPath 56 | view.layer.mask = mask 57 | 58 | super.addSubview(view) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/BottomView/EditorBottomSizeEditor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorBottomSizeEditor.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class EditorBottomSizeEditor: UIView { 11 | var brushImageView: UIImageView? 12 | var sizeSlider: SizeSlider? 13 | var brushTypeButton: UIButton? 14 | var backButton: UIButton? 15 | 16 | var selectedBrush: Brush 17 | let viewModel: EditorBottomViewModel 18 | 19 | init(selectedBrush: Brush, viewModel: EditorBottomViewModel, frame: CGRect) { 20 | self.selectedBrush = selectedBrush 21 | self.viewModel = viewModel 22 | super.init(frame: frame) 23 | initialSetup() 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | private func initialSetup() { 31 | brushImageView = UIImageView(frame: CGRect(origin: CGPoint(x: (bounds.width - 40) / 2 , y: .zero), size: CGSize(width: 40, height: 120))) 32 | brushImageView?.contentMode = .scaleAspectFit 33 | brushImageView?.image = selectedBrush.icon() 34 | addSubview(brushImageView!) 35 | 36 | sizeSlider = SizeSlider(frame: CGRect(origin: CGPoint(x: 46.5 , y: brushImageView!.frame.maxY), size: CGSize(width: bounds.width-134, height: 28))) 37 | sizeSlider?.value = selectedBrush.width 38 | sizeSlider?.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged) 39 | addSubview(sizeSlider!) 40 | 41 | } 42 | 43 | @objc private func sliderValueChanged(_ slider: UISlider) { 44 | print("slider value changed: \(slider.value)") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/BottomView/SizeSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SizeSlider.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 26/10/2022. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class SizeSlider: UISlider { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | initialSetup() 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | super.init(coder: coder) 20 | initialSetup() 21 | } 22 | 23 | private func initialSetup() { 24 | if let trackImage = UIImage.generateSizeSlider(isRotated: true) { 25 | self.setMaximumTrackImage(trackImage, for: .normal) 26 | self.setMinimumTrackImage(trackImage, for: .normal) 27 | } 28 | 29 | if let thumbImage = UIImage.generateSizeSliderThumb(size: CGSize(width: 32, height: 32)) { 30 | self.setThumbImage(thumbImage, for: .normal) 31 | } 32 | value = 0.5 33 | } 34 | 35 | override func trackRect(forBounds bounds: CGRect) -> CGRect { 36 | var result = super.trackRect(forBounds: bounds) 37 | result.size.height = 24 38 | result.origin = .zero 39 | return result 40 | } 41 | 42 | func runTransitionAnimation(from segmentedControl: EditorBottomSegementedControl) { 43 | layer.sublayers?.forEach({ $0.isHidden = true }) 44 | } 45 | 46 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 47 | super.touchesBegan(touches, with: event) 48 | 49 | var sliderTransforms = self.transform 50 | sliderTransforms = sliderTransforms.concatenating(CGAffineTransform(translationX: 20, y: .zero)) 51 | UIView.animate(withDuration: 0.2, delay: .zero) { 52 | self.transform = sliderTransforms 53 | } 54 | } 55 | 56 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 57 | super.touchesEnded(touches, with: event) 58 | var sliderTransforms = self.transform 59 | sliderTransforms = sliderTransforms.concatenating(CGAffineTransform(translationX: -20, y: .zero)) 60 | UIView.animate(withDuration: 0.2, delay: .zero) { 61 | self.transform = sliderTransforms 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/DrawingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingViewController.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import MetalKit 9 | import UIKit 10 | 11 | class DrawingViewController: UIViewController { 12 | 13 | private var drawingView: DrawingView? { 14 | return self.view as? DrawingView 15 | } 16 | 17 | private var editorViewModel = EditorBottomViewModel() 18 | 19 | private lazy var bottomView: EditorBottomView = { 20 | return EditorBottomView(viewModel: editorViewModel) 21 | }() 22 | 23 | private var renderer: Renderer? 24 | 25 | override func loadView() { 26 | super.loadView() 27 | self.view = DrawingView(frame: UIScreen.main.bounds, brush: editorViewModel.selectedBrush, device: MTLCreateSystemDefaultDevice()) 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | guard let drawingView else { return } 33 | renderer = Renderer(metalKitView: drawingView) 34 | view.addSubview(bottomView) 35 | 36 | editorViewModel.brushUpdated = { [weak self] brush in 37 | self?.drawingView?.selectedBrush = brush 38 | } 39 | bottomView.pinToSuperviewEdges(exclude: .top, respectingSafeArea: false) 40 | NSLayoutConstraint.activate([ 41 | bottomView.heightAnchor.constraint(equalToConstant: 88 + 33 + 25), 42 | ]) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/CGImage + PixelColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGImage + PixelColor.swift 3 | // telegram-contest 4 | // 5 | // Created by Bogdan Redkin on 29/10/2022. 6 | // 7 | 8 | import CoreGraphics 9 | import UIKit 10 | 11 | extension CGImage { 12 | 13 | func pixel(x: Int, y: Int) -> (r: Int, g: Int, b: Int)? { 14 | guard let pixelData = dataProvider?.data, 15 | let data = CFDataGetBytePtr(pixelData) else { return nil } 16 | 17 | let pixelInfo = ((width * y) + x ) * 4 18 | 19 | let red = Int(data[pixelInfo]) 20 | let green = Int(data[(pixelInfo + 1)]) 21 | let blue = Int(data[pixelInfo + 2]) 22 | 23 | return (red, green, blue) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/CGPoint + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint + Arithmetic.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | 11 | extension CGPoint { 12 | func interpolate(to: CGPoint, progress: CGFloat) -> CGPoint { 13 | guard progress >= 0.0 && progress <= 1.0 else { return self } 14 | let x = self.x.interpolate(to: to.x, progress: progress) 15 | let y = self.y.interpolate(to: to.y, progress: progress) 16 | return CGPoint(x: x, y: y) 17 | } 18 | 19 | init(vector: vector_uint2) { 20 | self.init(x: CGFloat(vector.x), y: CGFloat(vector.y)) 21 | } 22 | 23 | init(vector: vector_float2) { 24 | self.init(x: vector.x.cgFloat, y: vector.y.cgFloat) 25 | } 26 | 27 | var vectorFloat: vector_float2 { 28 | return vector_float2(x: x.float, y: y.float) 29 | } 30 | 31 | var vectorUint: vector_uint2 { 32 | return vector_uint2(x: x.int32, y: y.int32) 33 | } 34 | 35 | // Arithmetic 36 | 37 | static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 38 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 39 | } 40 | 41 | static func -(lhs: CGPoint, rhs: CGPoint) -> CGVector { 42 | return CGVector(dx: lhs.x - rhs.x, dy: lhs.y - rhs.y) 43 | } 44 | 45 | static func -(lhs: CGPoint, rhs: CGVector) -> CGPoint { 46 | return CGPoint(x: lhs.x - rhs.dx, y: lhs.y - rhs.dy) 47 | } 48 | 49 | static func *(lhs: CGPoint, rhs: CGFloat) -> CGPoint { 50 | return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) 51 | } 52 | 53 | static func *(lhs: CGFloat, rhs: CGPoint) -> CGPoint { 54 | return CGPoint(x: rhs.x * lhs, y: rhs.y * lhs) 55 | } 56 | 57 | func distanceToPoint(otherPoint: CGPoint) -> CGFloat { 58 | return sqrt(pow((otherPoint.x - x), 2) + pow((otherPoint.y - y), 2)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/CGRect + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension CGRect { 11 | 12 | public init(size: CGSize) { 13 | self.init(origin: .zero, size: size) 14 | } 15 | 16 | func scale(_ factor: CGFloat) -> CGRect { 17 | let transform = CGAffineTransform(scaleX: factor, y: factor) 18 | return self.applying(transform) 19 | } 20 | 21 | func interpolate(to: CGRect, progress: CGFloat) -> CGRect { 22 | guard progress >= 0.0, progress <= 1.0 else { return self } 23 | let origin = origin.interpolate(to: to.origin, progress: progress) 24 | let size = size.interpolate(to: to.size, progress: progress) 25 | return CGRect(origin: origin, size: size) 26 | } 27 | 28 | var center: CGPoint { 29 | return CGPoint(x: midX, y: midY) 30 | } 31 | 32 | init(center: CGPoint, size: CGSize) { 33 | self.init(origin: CGPoint(x: center.x - size.width / 2, y: center.y - size.height / 2), size: size) 34 | } 35 | 36 | func withOrigin(_ newOrigin: CGPoint) -> CGRect { 37 | var newRect = self 38 | newRect.origin = newOrigin 39 | return newRect 40 | } 41 | 42 | func withX(_ newX: CGFloat) -> CGRect { 43 | return withOrigin(CGPoint(x: newX, y: minY)) 44 | } 45 | 46 | func withY(_ newY: CGFloat) -> CGRect { 47 | return withOrigin(CGPoint(x: minX, y: newY)) 48 | } 49 | 50 | func withSize(_ newValue: CGSize) -> CGRect { 51 | var newRect = self 52 | newRect.size = newValue 53 | return newRect 54 | } 55 | 56 | func withWidth(_ newWidth: CGFloat) -> CGRect { 57 | return withSize(CGSize(width: newWidth, height: height)) 58 | } 59 | 60 | 61 | func withHeight(_ newHeight: CGFloat) -> CGRect { 62 | return withSize(CGSize(width: width, height: newHeight)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/CGSize + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSize + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension CGSize { 11 | 12 | init(size: CGFloat) { 13 | self.init(width: size, height: size) 14 | } 15 | 16 | func interpolate(to: CGSize, progress: CGFloat) -> CGSize { 17 | guard progress >= 0.0 && progress <= 1.0 else { return self } 18 | 19 | let width = self.width.interpolate(to: to.width, progress: progress) 20 | let height = self.height.interpolate(to: to.height, progress: progress) 21 | return CGSize(width: width, height: height) 22 | } 23 | 24 | func scale(_ factor: CGFloat) -> CGSize { 25 | let transform = CGAffineTransform(scaleX: factor, y: factor) 26 | return self.applying(transform) 27 | } 28 | 29 | static func *(lhs: CGSize, rhs: CGSize) -> CGSize { 30 | return CGSize(width: lhs.width * rhs.width, height: lhs.height * rhs.height) 31 | } 32 | 33 | static func *(lhs: CGSize, rhs: CGFloat) -> CGSize { 34 | return CGSize(width: lhs.width * rhs, height: lhs.height * rhs) 35 | } 36 | 37 | static func *(lhs: CGFloat, rhs: CGSize) -> CGSize { 38 | return CGSize(width: rhs.width * lhs, height: rhs.height * lhs) 39 | } 40 | 41 | static func /(lhs: CGSize, rhs: CGSize) -> CGSize { 42 | return CGSize(width: lhs.width / rhs.width, height: lhs.height / rhs.height) 43 | } 44 | 45 | static func /(lhs: CGSize, rhs: CGFloat) -> CGSize { 46 | return CGSize(width: lhs.width / rhs, height: lhs.height / rhs) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/MTLTexture + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTLTexture + Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 20/06/2023. 6 | // 7 | 8 | import Metal 9 | import UIKit 10 | import simd 11 | 12 | extension MTLTexture { 13 | // func clear() { 14 | // let region = MTLRegion( 15 | // origin: MTLOrigin(x: 0, y: 0, z: 0), 16 | // size: MTLSize(width: width, height: height, depth: 1) 17 | // ) 18 | // let bytesPerRow = 4 * width 19 | // let data = Data(capacity: Int(bytesPerRow * height)) 20 | // if let bytes = data.withUnsafeBytes({ $0.baseAddress }) { 21 | // replace(region: region, mipmapLevel: 0, withBytes: bytes, bytesPerRow: bytesPerRow) 22 | // } 23 | // } 24 | } 25 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/Number+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Number+Helpers.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Int { 11 | func interpolate(to: Int, progress: CGFloat) -> Int { 12 | guard progress >= 0.0 && progress <= 1.0 else { return self } 13 | return self + Int(CGFloat(to - self) * progress) 14 | } 15 | 16 | var int32: UInt32 { 17 | return UInt32(self) 18 | } 19 | 20 | var float: Float { 21 | return Float(self) 22 | } 23 | 24 | var double: Double { 25 | return Double(self) 26 | } 27 | 28 | var cgFloat: CGFloat { 29 | return CGFloat(self) 30 | } 31 | 32 | var string: String { 33 | return String(describing: self) 34 | } 35 | 36 | static func extract(from string: String) -> Int? { 37 | return Int(string.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) 38 | } 39 | } 40 | 41 | extension Float { 42 | 43 | var int: Int { 44 | return Int(self) 45 | } 46 | 47 | var int32: UInt32 { 48 | return UInt32(self) 49 | } 50 | 51 | var double: Double { 52 | return Double(self) 53 | } 54 | 55 | var cgFloat: CGFloat { 56 | return CGFloat(self) 57 | } 58 | 59 | var string: String { 60 | return String(describing: self) 61 | } 62 | } 63 | 64 | extension Double { 65 | var float: Float { 66 | return Float(self) 67 | } 68 | 69 | var int: Int { 70 | return Int(self) 71 | } 72 | 73 | var int32: UInt32 { 74 | return UInt32(self) 75 | } 76 | 77 | var cgFloat: CGFloat { 78 | return CGFloat(self) 79 | } 80 | 81 | var string: String { 82 | return String(describing: self) 83 | } 84 | } 85 | 86 | let π = CGFloat.pi 87 | 88 | extension CGFloat { 89 | func interpolate(to: CGFloat, progress: CGFloat) -> CGFloat { 90 | guard progress >= 0.0 && progress <= 1.0 else { return self } 91 | return self + (to - self) * progress 92 | } 93 | 94 | var int: Int { 95 | return Int(self) 96 | } 97 | 98 | var int32: UInt32 { 99 | return UInt32(self) 100 | } 101 | 102 | var double: Double { 103 | return Double(self) 104 | } 105 | 106 | var float: Float { 107 | return Float(self) 108 | } 109 | 110 | var string: String { 111 | return String(describing: self) 112 | } 113 | // Converts an angle in degrees to radians. 114 | func degreesToRadians() -> CGFloat { 115 | 116 | return π * self / 180.0 117 | } 118 | 119 | // Converts an angle in radians to degrees. 120 | func radiansToDegrees() -> CGFloat { 121 | return self * 180.0 / π 122 | } 123 | 124 | // Ensures that the float value stays between the given values, inclusive. 125 | func clamped(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 126 | let min = v1 < v2 ? v1 : v2 127 | let max = v1 > v2 ? v1 : v2 128 | return self < min ? min : (self > max ? max : self) 129 | } 130 | 131 | // Ensures that the float value stays between the given values, inclusive. 132 | mutating func clamp(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 133 | self = clamped(v1, v2) 134 | return self 135 | } 136 | 137 | // Returns 1.0 if a floating point value is positive; -1.0 if it is negative. 138 | func sign() -> CGFloat { 139 | return (self >= 0.0) ? 1.0 : -1.0 140 | } 141 | 142 | func rounded(symbolsAfterComma count: Int) -> CGFloat { 143 | let multiplier = pow(10, count.double).cgFloat 144 | let result = (self * multiplier).rounded() / multiplier 145 | return result 146 | } 147 | } 148 | 149 | extension UInt32 { 150 | var float: Float { 151 | return Float(self) 152 | } 153 | 154 | var int: Int { 155 | return Int(self) 156 | } 157 | 158 | var cgFloat: CGFloat { 159 | return CGFloat(self) 160 | } 161 | 162 | var string: String { 163 | return String(describing: self) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/UIApplication + currentWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication + currentWindow.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 06/06/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIApplication { 11 | 12 | var window: UIWindow? { 13 | // Get connected scenes 14 | let scene = UIApplication.shared.connectedScenes 15 | // Keep only active scenes, onscreen and visible to the user 16 | .filter { $0.activationState == .foregroundActive } 17 | // Keep only the first `UIWindowScene` 18 | .first(where: { $0 is UIWindowScene }) 19 | // Get its associated windows 20 | .flatMap { $0 as? UIWindowScene } 21 | 22 | return scene?.keyWindow 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/UIColor + Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor + Hex.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 10/10/2022. 6 | // 7 | 8 | import UIKit 9 | import simd 10 | 11 | extension UIColor { 12 | 13 | public convenience init?(hex: String) { 14 | var hexInt: UInt64 = 0 15 | let scanner = Scanner(string: hex) 16 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") 17 | scanner.scanHexInt64(&hexInt) 18 | 19 | let red = CGFloat((hexInt & 0xFF0000) >> 16) / 255.0 20 | let green = CGFloat((hexInt & 0xFF00) >> 8) / 255.0 21 | let blue = CGFloat((hexInt & 0xFF) >> 0) / 255.0 22 | 23 | self.init(red: red, green: green, blue: blue, alpha: 1.0) 24 | } 25 | 26 | static var random: UIColor { 27 | return UIColor(red: .random(in: 0...1), 28 | green: .random(in: 0...1), 29 | blue: .random(in: 0...1), 30 | alpha: 1.0) 31 | } 32 | 33 | var hexString: String { 34 | let (r, g, b, _) = rgbaCGFloat 35 | 36 | let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 37 | 38 | return String(format:"#%06x", rgb) 39 | } 40 | 41 | typealias RGBA = (r: T, g: T, b: T, a: T) 42 | 43 | var rgbaCGFloat: RGBA { 44 | var r: CGFloat = .zero 45 | var g: CGFloat = .zero 46 | var b: CGFloat = .zero 47 | var a: CGFloat = .zero 48 | getRed(&r, green: &g, blue: &b, alpha: &a) 49 | return (r, g, b, a) 50 | } 51 | 52 | var rgbaDouble: RGBA { 53 | let (r,g,b,a) = rgbaCGFloat 54 | return (r.double, g.double, b.double, a.double) 55 | } 56 | 57 | var vectorFloat4: vector_float4 { 58 | let (r,g,b,a) = rgbaDouble 59 | return vector_float4(r.float, g.float, b.float, a.float) 60 | } 61 | 62 | var hsba: (h: CGFloat, s: CGFloat, b: CGFloat, a: CGFloat) { 63 | var h: CGFloat = .zero 64 | var s: CGFloat = .zero 65 | var b: CGFloat = .zero 66 | var a: CGFloat = .zero 67 | getHue(&h, saturation: &s, brightness: &b, alpha: &a) 68 | return (h, s, b, a) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/UIControl + Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl + Combine.swift 3 | // telegram-contest 4 | // 5 | // Created by Bogdan Redkin on 11/10/2022. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | import UIKit 11 | 12 | final class UIControlSubscription: Subscription where SubscriberType.Input == Control { 13 | private var subscriber: SubscriberType? 14 | private let control: Control 15 | 16 | init(subscriber: SubscriberType, control: Control, event: UIControl.Event) { 17 | self.subscriber = subscriber 18 | self.control = control 19 | control.addTarget(self, action: #selector(self.eventHandler), for: event) 20 | } 21 | 22 | func request(_ demand: Subscribers.Demand) { } 23 | 24 | func cancel() { 25 | self.subscriber = nil 26 | } 27 | 28 | @objc private func eventHandler() { 29 | _ = self.subscriber?.receive(self.control) 30 | } 31 | } 32 | 33 | struct UIControlPublisher: Publisher { 34 | typealias Output = Control 35 | typealias Failure = Never 36 | 37 | let control: Control 38 | let controlEvents: UIControl.Event 39 | 40 | init(control: Control, events: UIControl.Event) { 41 | self.control = control 42 | self.controlEvents = events 43 | } 44 | 45 | func receive(subscriber: S) where S: Subscriber, S.Failure == UIControlPublisher.Failure, S.Input == UIControlPublisher.Output { 46 | let subscription = UIControlSubscription(subscriber: subscriber, control: control, event: controlEvents) 47 | subscriber.receive(subscription: subscription) 48 | } 49 | } 50 | 51 | protocol CombineCompatible {} 52 | extension UIControl: CombineCompatible {} 53 | extension CombineCompatible where Self: UIControl { 54 | func publisher(for events: UIControl.Event) -> UIControlPublisher { 55 | return UIControlPublisher(control: self, events: events) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Extensions/UIView + Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView + Helpers.swift 3 | // telegram-contest 4 | // 5 | // Created by Bogdan Redkin on 16/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIView { 11 | 12 | func setAnchorPoint(_ point: CGPoint) { 13 | var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y) 14 | var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y); 15 | 16 | newPoint = newPoint.applying(transform) 17 | oldPoint = oldPoint.applying(transform) 18 | 19 | var position = layer.position 20 | 21 | position.x -= oldPoint.x 22 | position.x += newPoint.x 23 | 24 | position.y -= oldPoint.y 25 | position.y += newPoint.y 26 | 27 | layer.position = position 28 | layer.anchorPoint = point 29 | } 30 | 31 | var rootSuperView: UIView? { 32 | if superview?.superview == nil { 33 | return superview 34 | } else { 35 | return superview?.rootSuperView 36 | } 37 | } 38 | 39 | var recursiveAllSubviews: [UIView] { 40 | subviews + subviews.flatMap { $0.recursiveAllSubviews } 41 | } 42 | 43 | func firstSubview(of type: T.Type) -> T? { 44 | recursiveAllSubviews.first { $0 is T } as? T 45 | } 46 | 47 | func findFirstResponder() -> UIView? { 48 | for subview in subviews { 49 | if subview.isFirstResponder { 50 | return subview 51 | } 52 | 53 | if let recursiveSubView = subview.findFirstResponder() { 54 | return recursiveSubView 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Metal/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Metal/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Metal/DrawingGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingGestureRecognizer.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 25/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class DrawingGestureRecognizer: UIPanGestureRecognizer { 11 | 12 | typealias touchesResponse = (touches: Set, event: UIEvent, velocity: CGPoint) 13 | 14 | var touchesBeganHandler: ((touchesResponse) -> Void)? 15 | var touchesMovedHandler: ((touchesResponse) -> Void)? 16 | var touchesEndedHandler: ((touchesResponse) -> Void)? 17 | var touchesCancelledHandler: ((touchesResponse) -> Void)? 18 | 19 | override func touchesBegan(_ touches: Set, with event: UIEvent) { 20 | let response: touchesResponse = (touches, event, velocity(in: view)) 21 | touchesBeganHandler?(response) 22 | super.touchesBegan(touches, with: event) 23 | } 24 | 25 | override func touchesMoved(_ touches: Set, with event: UIEvent) { 26 | let response: touchesResponse = (touches, event, velocity(in: view)) 27 | touchesMovedHandler?(response) 28 | super.touchesMoved(touches, with: event) 29 | } 30 | 31 | override func touchesEnded(_ touches: Set, with event: UIEvent) { 32 | let response: touchesResponse = (touches, event, velocity(in: view)) 33 | touchesEndedHandler?(response) 34 | super.touchesEnded(touches, with: event) 35 | } 36 | 37 | override func touchesCancelled(_ touches: Set, with event: UIEvent) { 38 | let response: touchesResponse = (touches, event, velocity(in: view)) 39 | touchesCancelledHandler?(response) 40 | super.touchesCancelled(touches, with: event) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Metal/Shader.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Shader.metal 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 06/05/2023. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | struct PointVertex { 12 | float4 position [[position]]; 13 | float4 color; 14 | float size [[point_size]]; 15 | }; 16 | 17 | vertex PointVertex pointShaderVertex(constant PointVertex *points [[ buffer(0) ]], 18 | uint vid [[ vertex_id ]]) 19 | { 20 | PointVertex out = points[vid]; 21 | 22 | float2 pos = float2(out.position.x, out.position.y); 23 | out.position = float4(pos, 0, 1); 24 | return out; 25 | }; 26 | 27 | fragment half4 pointShaderFragment(PointVertex point_data [[ stage_in ]], 28 | float2 pointCoord [[ point_coord ]]) 29 | { 30 | float dist = length(pointCoord - float2(0.5)); 31 | float4 out_color = point_data.color; 32 | out_color.a = 1.0 - smoothstep(0.4, 0.5, dist); 33 | return half4(out_color); 34 | }; 35 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Models/Brush.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Brush.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 17/10/2022. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | struct Brush { 12 | 13 | enum Style: String { 14 | case pen 15 | case brush 16 | case neon 17 | case pencil 18 | case eraser 19 | } 20 | 21 | let style: Style 22 | var width: Float 23 | var color: Color 24 | 25 | init(style: Style, width: Float? = nil, color: Color = Color(uiColor: .tintColor)) { 26 | self.style = style 27 | self.width = width ?? (style.maxWidth - style.minWidth)/2 28 | self.color = color 29 | } 30 | 31 | } 32 | 33 | extension Brush { 34 | 35 | var isAvailable: Bool { 36 | switch style { 37 | case .eraser, .pen: return true 38 | default: return false 39 | } 40 | } 41 | 42 | static func defaultBrushes() -> [Brush] { 43 | return [ 44 | Brush(style: .pen), 45 | Brush(style: .brush), 46 | Brush(style: .neon), 47 | Brush(style: .pencil), 48 | Brush(style: .eraser) 49 | ] 50 | } 51 | 52 | func icon() -> UIImage? { 53 | return UIImage.generateToolIcon(name: style.rawValue, color: color.uiColor, height: width.cgFloat) 54 | } 55 | 56 | 57 | } 58 | 59 | extension Brush.Style { 60 | var contentScale: Float { 61 | return (UIApplication.shared.window?.rootViewController?.view.contentScaleFactor ?? 1.0).float 62 | } 63 | 64 | var maxWidth: Float { 65 | switch self { 66 | case .eraser: 67 | return 50 * contentScale 68 | default: 69 | return 40 * contentScale 70 | } 71 | } 72 | 73 | var minWidth: Float { 74 | switch self { 75 | case .eraser: 76 | return 25 * contentScale 77 | default: 78 | return 13 * contentScale 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Models/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 20/10/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | struct Color: Codable { 11 | 12 | var red: CGFloat 13 | var green: CGFloat 14 | var blue: CGFloat 15 | var alpha: CGFloat 16 | 17 | var hex: String { 18 | uiColor.hexString 19 | } 20 | 21 | var uiColor: UIColor { 22 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 23 | } 24 | 25 | var hue: CGFloat { 26 | get { uiColor.hsba.h } 27 | set { 28 | var (h,s,b,a) = uiColor.hsba 29 | h = newValue 30 | let (r, g, blue, _) = UIColor(hue: h, saturation: s, brightness: b, alpha: a).rgbaCGFloat 31 | self.red = r 32 | self.green = g 33 | self.blue = blue 34 | } 35 | } 36 | 37 | var saturation: CGFloat { 38 | get { uiColor.hsba.s } 39 | set { 40 | var (h,s,b,a) = uiColor.hsba 41 | s = newValue 42 | let (r, g, blue, _) = UIColor(hue: h, saturation: s, brightness: b, alpha: a).rgbaCGFloat 43 | self.red = r 44 | self.green = g 45 | self.blue = blue 46 | } 47 | } 48 | 49 | var brightness: CGFloat { 50 | get { uiColor.hsba.b } 51 | set { 52 | var (h, s, b, a) = uiColor.hsba 53 | b = newValue 54 | let (r, g, blue, _) = UIColor(hue: h, saturation: s, brightness: b, alpha: a).rgbaCGFloat 55 | self.red = r 56 | self.green = g 57 | self.blue = blue 58 | } 59 | } 60 | 61 | init(uiColor: UIColor) { 62 | let (r, g, b, a) = uiColor.rgbaCGFloat 63 | red = r 64 | green = g 65 | blue = b 66 | alpha = a 67 | } 68 | 69 | init(hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat = 1) { 70 | let h = max(0, min(1, hue)) 71 | let s = max(0, min(1, saturation)) 72 | let b = max(0, min(1, brightness)) 73 | let a = max(0, min(1, alpha)) 74 | let uiColor = UIColor(hue: h, saturation: s, brightness: b, alpha: a) 75 | self.init(uiColor: uiColor) 76 | } 77 | 78 | init(hue: Int, saturation: Int, brightness: Int, alpha: CGFloat = 1) { 79 | self.init(hue: hue.cgFloat/359, saturation: saturation.cgFloat/100, brightness: brightness.cgFloat/100, alpha: alpha) 80 | } 81 | 82 | func save() { 83 | var array = Color.fetchSavedColors() 84 | array.append(self) 85 | if let encodedArray = try? JSONEncoder().encode(array), 86 | let string = String(data: encodedArray, encoding: .utf8) { 87 | UserDefaults.standard.set(string, forKey: "saved_colors") 88 | } 89 | } 90 | 91 | static func fetchSavedColors() -> [Color] { 92 | if let savedArray = UserDefaults.standard.string(forKey: "saved_colors"), 93 | let data = savedArray.data(using: .utf8), 94 | let decodedArray = try? JSONDecoder().decode([Color].self, from: data) 95 | { 96 | return decodedArray 97 | } else { 98 | return [] 99 | } 100 | } 101 | 102 | static func defaultPalette() -> [Color] { 103 | return [ 104 | UIColor.black, 105 | UIColor.systemBlue, 106 | UIColor.systemRed, 107 | UIColor.systemOrange 108 | ].map { Color(uiColor: $0) } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Models/Point.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Point.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 07/05/2023. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | import UIKit 11 | 12 | struct TextureVertex { 13 | var position: vector_float2 14 | var texcoord: vector_float2 15 | } 16 | 17 | struct Point { 18 | var position: vector_float4 19 | var color: vector_float4 20 | var size: Float 21 | 22 | init(position: vector_float4, color: vector_float4, size: Float) { 23 | self.position = position 24 | self.color = color 25 | self.size = size 26 | } 27 | 28 | init(x: CGFloat, y: CGFloat, color: UIColor, size: CGFloat) { 29 | self.init(position: vector_float4(Float(x), Float(y), 0, 1), color: color.vectorFloat4, size: Float(size)) 30 | } 31 | 32 | init(location: CGPoint, parentSize: CGSize, color: UIColor, size: CGFloat) { 33 | let roundedLocation = CGPoint(x: location.x.int.cgFloat, y: location.y.int.cgFloat) 34 | let xK = (1.0 / (parentSize.width / roundedLocation.x)).rounded(symbolsAfterComma: 5) 35 | let yK = (1.0 / (parentSize.height / roundedLocation.y)).rounded(symbolsAfterComma: 5) 36 | let x = xK == 0.5 ? 0.0 : (xK < 0.5 ? -(1 - xK / 0.5) : xK / 0.5 - 1) 37 | let y = yK == 0.5 ? 0.0 : (yK > 0.5 ? (1 - yK / 0.5) : 1 - (yK / 0.5)) 38 | self.init(x: x, y: y, color: color, size: size) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | 18 | let window = UIWindow(windowScene: windowScene) 19 | window.rootViewController = DrawingViewController() 20 | self.window = window 21 | window.makeKeyAndVisible() 22 | } 23 | 24 | func sceneDidDisconnect(_ scene: UIScene) { 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | func sceneDidBecomeActive(_ scene: UIScene) { 32 | // Called when the scene has moved from an inactive state to an active state. 33 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 34 | } 35 | 36 | func sceneWillResignActive(_ scene: UIScene) { 37 | // Called when the scene will move from an active state to an inactive state. 38 | // This may occur due to temporary interruptions (ex. an incoming phone call). 39 | } 40 | 41 | func sceneWillEnterForeground(_ scene: UIScene) { 42 | // Called as the scene transitions from the background to the foreground. 43 | // Use this method to undo the changes made on entering the background. 44 | } 45 | 46 | func sceneDidEnterBackground(_ scene: UIScene) { 47 | // Called as the scene transitions from the foreground to the background. 48 | // Use this method to save data, release shared resources, and store enough scene-specific state information 49 | // to restore the scene back to its current state. 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/.DS_Store -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurEraser 1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blurEraser.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurEraser 1.imageset/blurEraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurEraser 1.imageset/blurEraser.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurEraser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blurEraser.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurEraser.imageset/blurEraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurEraser.imageset/blurEraser.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurTip 1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blurTip.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurTip 1.imageset/blurTip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurTip 1.imageset/blurTip.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurTip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blurTip.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurTip.imageset/blurTip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/blurTip.imageset/blurTip.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush 1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "brush.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush 1.imageset/brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush 1.imageset/brush.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "brush.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush.imageset/brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush.imageset/brush.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush_tip 1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "brush.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush_tip 1.imageset/brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush_tip 1.imageset/brush.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush_tip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "brush.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush_tip.imageset/brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/brush_tip.imageset/brush.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/close 1.imageset/Close.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E2 << /ca 0.600000 >> 5 | /E1 << /ca 0.240000 >> 6 | >> >> 7 | endobj 8 | 9 | 2 0 obj 10 | << /Length 3 0 R >> 11 | stream 12 | /DeviceRGB CS 13 | /DeviceRGB cs 14 | q 15 | /E1 gs 16 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 17 | 0.462745 0.462745 0.501961 scn 18 | 30.000000 15.000000 m 19 | 30.000000 6.715729 23.284271 0.000000 15.000000 0.000000 c 20 | 6.715729 0.000000 0.000000 6.715729 0.000000 15.000000 c 21 | 0.000000 23.284271 6.715729 30.000000 15.000000 30.000000 c 22 | 23.284271 30.000000 30.000000 23.284271 30.000000 15.000000 c 23 | h 24 | f 25 | n 26 | Q 27 | q 28 | /E2 gs 29 | 1.000000 0.000000 -0.000000 1.000000 9.500000 9.304749 cm 30 | 0.921569 0.921569 0.960784 scn 31 | 0.292893 9.488144 m 32 | -0.097631 9.878669 -0.097631 10.511834 0.292893 10.902358 c 33 | 0.683417 11.292882 1.316583 11.292882 1.707107 10.902358 c 34 | 5.500000 7.109465 l 35 | 9.292893 10.902358 l 36 | 9.683417 11.292882 10.316583 11.292882 10.707107 10.902358 c 37 | 11.097631 10.511834 11.097631 9.878669 10.707107 9.488144 c 38 | 6.914213 5.695251 l 39 | 10.707107 1.902358 l 40 | 11.097631 1.511834 11.097631 0.878669 10.707107 0.488145 c 41 | 10.316583 0.097620 9.683417 0.097620 9.292893 0.488145 c 42 | 5.500000 4.281038 l 43 | 1.707107 0.488145 l 44 | 1.316583 0.097620 0.683418 0.097620 0.292893 0.488145 c 45 | -0.097631 0.878669 -0.097631 1.511834 0.292893 1.902358 c 46 | 4.085787 5.695251 l 47 | 0.292893 9.488144 l 48 | h 49 | f* 50 | n 51 | Q 52 | 53 | endstream 54 | endobj 55 | 56 | 3 0 obj 57 | 1153 58 | endobj 59 | 60 | 4 0 obj 61 | << /Annots [] 62 | /Type /Page 63 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 64 | /Resources 1 0 R 65 | /Contents 2 0 R 66 | /Parent 5 0 R 67 | >> 68 | endobj 69 | 70 | 5 0 obj 71 | << /Kids [ 4 0 R ] 72 | /Count 1 73 | /Type /Pages 74 | >> 75 | endobj 76 | 77 | 6 0 obj 78 | << /Pages 5 0 R 79 | /Type /Catalog 80 | >> 81 | endobj 82 | 83 | xref 84 | 0 7 85 | 0000000000 65535 f 86 | 0000000010 00000 n 87 | 0000000132 00000 n 88 | 0000001341 00000 n 89 | 0000001364 00000 n 90 | 0000001537 00000 n 91 | 0000001611 00000 n 92 | trailer 93 | << /ID [ (some) (id) ] 94 | /Root 6 0 R 95 | /Size 7 96 | >> 97 | startxref 98 | 1670 99 | %%EOF -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/close 1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Close.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/close.imageset/Close.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E2 << /ca 0.600000 >> 5 | /E1 << /ca 0.240000 >> 6 | >> >> 7 | endobj 8 | 9 | 2 0 obj 10 | << /Length 3 0 R >> 11 | stream 12 | /DeviceRGB CS 13 | /DeviceRGB cs 14 | q 15 | /E1 gs 16 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 17 | 0.462745 0.462745 0.501961 scn 18 | 30.000000 15.000000 m 19 | 30.000000 6.715729 23.284271 0.000000 15.000000 0.000000 c 20 | 6.715729 0.000000 0.000000 6.715729 0.000000 15.000000 c 21 | 0.000000 23.284271 6.715729 30.000000 15.000000 30.000000 c 22 | 23.284271 30.000000 30.000000 23.284271 30.000000 15.000000 c 23 | h 24 | f 25 | n 26 | Q 27 | q 28 | /E2 gs 29 | 1.000000 0.000000 -0.000000 1.000000 9.500000 9.304749 cm 30 | 0.921569 0.921569 0.960784 scn 31 | 0.292893 9.488144 m 32 | -0.097631 9.878669 -0.097631 10.511834 0.292893 10.902358 c 33 | 0.683417 11.292882 1.316583 11.292882 1.707107 10.902358 c 34 | 5.500000 7.109465 l 35 | 9.292893 10.902358 l 36 | 9.683417 11.292882 10.316583 11.292882 10.707107 10.902358 c 37 | 11.097631 10.511834 11.097631 9.878669 10.707107 9.488144 c 38 | 6.914213 5.695251 l 39 | 10.707107 1.902358 l 40 | 11.097631 1.511834 11.097631 0.878669 10.707107 0.488145 c 41 | 10.316583 0.097620 9.683417 0.097620 9.292893 0.488145 c 42 | 5.500000 4.281038 l 43 | 1.707107 0.488145 l 44 | 1.316583 0.097620 0.683418 0.097620 0.292893 0.488145 c 45 | -0.097631 0.878669 -0.097631 1.511834 0.292893 1.902358 c 46 | 4.085787 5.695251 l 47 | 0.292893 9.488144 l 48 | h 49 | f* 50 | n 51 | Q 52 | 53 | endstream 54 | endobj 55 | 56 | 3 0 obj 57 | 1153 58 | endobj 59 | 60 | 4 0 obj 61 | << /Annots [] 62 | /Type /Page 63 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 64 | /Resources 1 0 R 65 | /Contents 2 0 R 66 | /Parent 5 0 R 67 | >> 68 | endobj 69 | 70 | 5 0 obj 71 | << /Kids [ 4 0 R ] 72 | /Count 1 73 | /Type /Pages 74 | >> 75 | endobj 76 | 77 | 6 0 obj 78 | << /Pages 5 0 R 79 | /Type /Catalog 80 | >> 81 | endobj 82 | 83 | xref 84 | 0 7 85 | 0000000000 65535 f 86 | 0000000010 00000 n 87 | 0000000132 00000 n 88 | 0000001341 00000 n 89 | 0000001364 00000 n 90 | 0000001537 00000 n 91 | 0000001611 00000 n 92 | trailer 93 | << /ID [ (some) (id) ] 94 | /Root 6 0 R 95 | /Size 7 96 | >> 97 | startxref 98 | 1670 99 | %%EOF -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Close.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/color_picker.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "color_picker_gradient.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/color_picker.imageset/color_picker_gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/color_picker.imageset/color_picker_gradient.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/eraser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "eraser.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/eraser.imageset/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/eraser.imageset/eraser.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/neon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "neon.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/neon.imageset/neon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/neon.imageset/neon.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/neon_tip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "neon.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/neon_tip.imageset/neon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/neon_tip.imageset/neon.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/objectEraser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "objectEraser.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/objectEraser.imageset/objectEraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/objectEraser.imageset/objectEraser.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/opacity_slider_background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "VStack.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/opacity_slider_thumb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Ellipse 26.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/opacity_slider_thumb.imageset/Ellipse 26.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/opacity_slider_thumb.imageset/Ellipse 26.pdf -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pen.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pen.imageset/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pen.imageset/pen.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pen_tip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pen.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pen_tip.imageset/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pen_tip.imageset/pen.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pencil.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pencil.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pencil.imageset/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pencil.imageset/pencil.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pencil_tip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pencil.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pencil_tip.imageset/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/pencil_tip.imageset/pencil.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/roundTip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "roundTip.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/roundTip.imageset/roundTip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/roundTip.imageset/roundTip.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/size_slslider.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Rectangle 53@2x.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/size_slslider.imageset/Rectangle 53@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldammit/iOS-Metal-Drawing-tutorial/c58d3902adb3471215c17992519bce2df2216487/Draw_App_02_Starter/Draw App/Supporting Files/Assets.xcassets/size_slslider.imageset/Rectangle 53@2x.png -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/DrawingApp-BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // DrawingApp-BridgingHeader.h 3 | // Draw App 4 | // 5 | // Created by Bogdan Redkin on 29/04/2023. 6 | // 7 | 8 | #ifndef DrawingApp_BridgingHeader_h 9 | #define DrawingApp_BridgingHeader_h 10 | 11 | #endif /* DrawingApp_BridgingHeader_h */ 12 | -------------------------------------------------------------------------------- /Draw_App_02_Starter/Draw App/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Bogdan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS-Metal-Drawing-tutorial --------------------------------------------------------------------------------