├── .gitignore ├── Binary Quantized Embeddings Search ├── README.md └── rag.py ├── Blur Effects ObjC ├── Blur Effects.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Blur Effects │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Grass.imageset │ │ ├── Contents.json │ │ └── Grass.jpeg │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── CAGradientLayer Example ObjC ├── CAGradientLayer Example ObjC.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── CAGradientLayer Example ObjC │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── KHGradientView.h │ ├── KHGradientView.m │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── CAGradientLayer Example Swift ├── CAGradientLayer Example Swift.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── CAGradientLayer Example Swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── KHGradientView.swift │ ├── SceneDelegate.swift │ └── ViewController.swift ├── External Shadow ObjC ├── External Shadow.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── External Shadow │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Checkerboard.imageset │ │ ├── Checkerboard.png │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── External Shadow Swift ├── External Shadow Swift.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── External Shadow Swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Checkerboard.imageset │ │ ├── Checkerboard.png │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── ViewController.swift ├── Illustrations └── Illustrations │ ├── Illustrations.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── Illustrations │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── arrow.clockwise.custom.imageset │ │ ├── Contents.json │ │ └── arrow.clockwise.custom.svg │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Demos │ ├── Anchor Points │ │ ├── AnchorPointDemoViewController.swift │ │ └── KHVisualiseView.swift │ └── DemoViewController.swift │ ├── Illustrations.entitlements │ ├── Info.plist │ ├── MP4 │ ├── MP4Exporter.swift │ └── MP4Recorder.swift │ ├── SceneDelegate.swift │ └── ViewController.swift ├── LICENSE ├── ObjC Live Preview ├── Live Preview │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── KHGradientView.h │ ├── KHGradientView.m │ ├── ObjC Live Preview-Bridging-Header.h │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── SwiftUIView.swift │ ├── ViewController.h │ ├── ViewController.m │ └── main.m └── ObjC Live Preview.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Photo Picker ObjC ├── Photo Picker ObjC.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Photo Picker ObjC │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── Photo Picker Swift ├── Photo Picker Swift.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Photo Picker Swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── ViewController.swift ├── README.md ├── Read Write Image Metadata ObjC ├── Read Write Image Metadata ObjC.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Read Write Image Metadata ObjC │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Read Write Image Metadata ObjC.entitlements │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── Read Write Image Metadata Swift ├── Read Write Image Metadata Swift.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Read Write Image Metadata Swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Read Write Image Metadata Swift.entitlements │ ├── SceneDelegate.swift │ └── ViewController.swift ├── macCatalyst-titlebar-tabbar-objc ├── Titlebar-TabBar.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── kylehowells.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Titlebar-TabBar │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── first.imageset │ │ ├── Contents.json │ │ └── first.pdf │ └── second.imageset │ │ ├── Contents.json │ │ └── second.pdf │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── FirstViewController.h │ ├── FirstViewController.m │ ├── Info.plist │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── SecondViewController.h │ ├── SecondViewController.m │ ├── Titlebar-TabBar.entitlements │ └── main.m └── macCatalyst-titlebar-tabbar-swift ├── Titlebar-TabBar.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── kylehowells.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── Titlebar-TabBar ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── first.imageset │ ├── Contents.json │ └── first.pdf └── second.imageset │ ├── Contents.json │ └── second.pdf ├── Base.lproj └── LaunchScreen.storyboard ├── FirstViewController.swift ├── Info.plist ├── SceneDelegate.swift ├── SecondViewController.swift └── Titlebar-TabBar.entitlements /.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 | xcuserdata/ 8 | xcuserdata 9 | 10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 11 | *.xcscmblueprint 12 | *.xccheckout 13 | 14 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 15 | build/ 16 | DerivedData/ 17 | *.moved-aside 18 | *.pbxuser 19 | !default.pbxuser 20 | *.mode1v3 21 | !default.mode1v3 22 | *.mode2v3 23 | !default.mode2v3 24 | *.perspectivev3 25 | !default.perspectivev3 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | 30 | ## App packaging 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | *.xcuserstate 36 | *.xcuserdatad 37 | .DS_Store 38 | Binary Quantized Embeddings Search/rag.db 39 | -------------------------------------------------------------------------------- /Binary Quantized Embeddings Search/README.md: -------------------------------------------------------------------------------- 1 | # Demo Semantic Text Search Using Binary Embeddings 2 | 3 | This project demonstrates semantic text search using binary quantized embeddings for efficient storage and retrieval. 4 | 5 | Blog post: [Binary Quantized Embeddings - How to use binary quantized embeddings for semantic text search](https://ikyle.me/blog/2025/binary-quantized-embeddings) 6 | 7 | ## Command Line Interface Usage 8 | 9 | ### Loading Text Segments 10 | 11 | ```bash 12 | # Load with auto-generated ID 13 | uv run rag.py load --text "This is a sample text to be stored in the database" 14 | 15 | # Load with custom ID and metadata 16 | uv run rag.py load --id "custom-id" --text "Another sample with a custom ID" --metadata '{"source": "example", "date": "2023-07-01"}' 17 | ``` 18 | 19 | ### Searching for Similar Text 20 | 21 | ```bash 22 | # Basic search with default limit (5 results) 23 | python rag.py search "Find text similar to this query" 24 | 25 | # Search with custom result limit 26 | python rag.py search "Another search query" --limit 10 27 | ``` 28 | 29 | ### Removing Segments 30 | 31 | ```bash 32 | # Remove a segment by ID 33 | python rag.py remove --id "custom-id" 34 | ``` 35 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Blur Effects 4 | // 5 | // Created by Kyle Howells on 01/04/2022. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Blur Effects 4 | // 5 | // Created by Kyle Howells on 01/04/2022. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | 12 | @end 13 | 14 | @implementation AppDelegate 15 | 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | 23 | #pragma mark - UISceneSession lifecycle 24 | 25 | 26 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 27 | // Called when a new scene session is being created. 28 | // Use this method to select a configuration to create the new scene with. 29 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 30 | } 31 | 32 | 33 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 34 | // Called when the user discards a scene session. 35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/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 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/Assets.xcassets/Grass.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Grass.jpeg", 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 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/Assets.xcassets/Grass.imageset/Grass.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylehowells/ikyle.me-code-examples/502052a2e5da56c6fdb78148ea5ac7e132efab32/Blur Effects ObjC/Blur Effects/Assets.xcassets/Grass.imageset/Grass.jpeg -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/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 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/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 | SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // Blur Effects 4 | // 5 | // Created by Kyle Howells on 01/04/2022. 6 | // 7 | 8 | #import 9 | 10 | @interface SceneDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow * window; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // Blur Effects 4 | // 5 | // Created by Kyle Howells on 01/04/2022. 6 | // 7 | 8 | #import "SceneDelegate.h" 9 | #import "ViewController.h" 10 | 11 | @interface SceneDelegate () 12 | 13 | @end 14 | 15 | 16 | @implementation SceneDelegate 17 | 18 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 19 | self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene*)scene]; 20 | self.window.rootViewController = [[ViewController alloc] init]; 21 | [self.window makeKeyAndVisible]; 22 | } 23 | 24 | - (void)sceneDidDisconnect:(UIScene *)scene { 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 | 32 | - (void)sceneDidBecomeActive:(UIScene *)scene { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | 38 | - (void)sceneWillResignActive:(UIScene *)scene { 39 | // Called when the scene will move from an active state to an inactive state. 40 | // This may occur due to temporary interruptions (ex. an incoming phone call). 41 | } 42 | 43 | 44 | - (void)sceneWillEnterForeground:(UIScene *)scene { 45 | // Called as the scene transitions from the background to the foreground. 46 | // Use this method to undo the changes made on entering the background. 47 | } 48 | 49 | 50 | - (void)sceneDidEnterBackground:(UIScene *)scene { 51 | // Called as the scene transitions from the foreground to the background. 52 | // Use this method to save data, release shared resources, and store enough scene-specific state information 53 | // to restore the scene back to its current state. 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Blur Effects 4 | // 5 | // Created by Kyle Howells on 01/04/2022. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Blur Effects ObjC/Blur Effects/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Blur Effects 4 | // 5 | // Created by Kyle Howells on 01/04/2022. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) { 12 | NSString * appDelegateClassName; 13 | @autoreleasepool { 14 | // Setup code that might create autoreleased objects goes here. 15 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 16 | } 17 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 18 | } 19 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CAGradientLayer Example ObjC 4 | // 5 | // Created by Kyle Howells on 14/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CAGradientLayer Example ObjC 4 | // 5 | // Created by Kyle Howells on 14/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | #pragma mark - UISceneSession lifecycle 25 | 26 | 27 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 28 | // Called when a new scene session is being created. 29 | // Use this method to select a configuration to create the new scene with. 30 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 31 | } 32 | 33 | 34 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 35 | // Called when the user discards a scene session. 36 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 37 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 38 | } 39 | 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/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 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/KHGradientView.h: -------------------------------------------------------------------------------- 1 | // 2 | // KHGradientView.h 3 | // 4 | // Created by Kyle Howells on 09/06/2020. 5 | // Copyright © 2020 Kyle Howells. All rights reserved. 6 | // 7 | 8 | #import 9 | @import QuartzCore; 10 | 11 | @interface KHGradientView : UIView 12 | @property(nonatomic, readonly, strong) CAGradientLayer *layer; 13 | //-(CAGradientLayer*)gradientLayer; 14 | @end 15 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/KHGradientView.m: -------------------------------------------------------------------------------- 1 | // 2 | // KHGradientView.m 3 | // 4 | // Created by Kyle Howells on 09/06/2020. 5 | // Copyright © 2020 Kyle Howells. All rights reserved. 6 | // 7 | 8 | #import "KHGradientView.h" 9 | 10 | @implementation KHGradientView 11 | @dynamic layer; 12 | 13 | +(Class)layerClass{ 14 | return [CAGradientLayer class]; 15 | } 16 | 17 | //-(CAGradientLayer*)gradientLayer{ 18 | // return (CAGradientLayer*)[self layer]; 19 | //} 20 | @end 21 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // CAGradientLayer Example ObjC 4 | // 5 | // Created by Kyle Howells on 14/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SceneDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow * window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // CAGradientLayer Example ObjC 4 | // 5 | // Created by Kyle Howells on 14/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "SceneDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface SceneDelegate () 13 | @end 14 | 15 | @implementation SceneDelegate 16 | 17 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 18 | self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene*)scene]; 19 | self.window.rootViewController = [[ViewController alloc] init]; 20 | [self.window makeKeyAndVisible]; 21 | } 22 | 23 | 24 | - (void)sceneDidDisconnect:(UIScene *)scene { 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 neccessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | 32 | - (void)sceneDidBecomeActive:(UIScene *)scene { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | 38 | - (void)sceneWillResignActive:(UIScene *)scene { 39 | // Called when the scene will move from an active state to an inactive state. 40 | // This may occur due to temporary interruptions (ex. an incoming phone call). 41 | } 42 | 43 | 44 | - (void)sceneWillEnterForeground:(UIScene *)scene { 45 | // Called as the scene transitions from the background to the foreground. 46 | // Use this method to undo the changes made on entering the background. 47 | } 48 | 49 | 50 | - (void)sceneDidEnterBackground:(UIScene *)scene { 51 | // Called as the scene transitions from the foreground to the background. 52 | // Use this method to save data, release shared resources, and store enough scene-specific state information 53 | // to restore the scene back to its current state. 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // CAGradientLayer Example ObjC 4 | // 5 | // Created by Kyle Howells on 14/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /CAGradientLayer Example ObjC/CAGradientLayer Example ObjC/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CAGradientLayer Example ObjC 4 | // 5 | // Created by Kyle Howells on 14/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | NSString * appDelegateClassName; 14 | @autoreleasepool { 15 | // Setup code that might create autoreleased objects goes here. 16 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 17 | } 18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 19 | } 20 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CAGradientLayer Example Swift 4 | // 5 | // Created by Kyle Howells on 15/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift/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 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift/KHGradientView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KHGradientView.swift 3 | // 4 | // Created by Kyle Howells on 15/06/2020. 5 | // Copyright © 2020 Kyle Howells. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | class KHGradientView: UIView { 11 | 12 | override open class var layerClass: AnyClass { 13 | return CAGradientLayer.classForCoder() 14 | } 15 | 16 | var gradientLayer : CAGradientLayer { 17 | return self.layer as! CAGradientLayer 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // CAGradientLayer Example Swift 4 | // 5 | // Created by Kyle Howells on 15/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | 18 | self.window = UIWindow(windowScene: windowScene) 19 | self.window?.rootViewController = ViewController() 20 | self.window?.makeKeyAndVisible() 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /CAGradientLayer Example Swift/CAGradientLayer Example Swift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CAGradientLayer Example Swift 4 | // 5 | // Created by Kyle Howells on 15/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | // 14 | // Linear Gradient 15 | // 16 | lazy var linGradientView:KHGradientView = { 17 | let v = KHGradientView() 18 | v.gradientLayer.type = CAGradientLayerType.axial 19 | v.gradientLayer.colors = 20 | [ 21 | UIColor(red: 48.0/255.0, green: 35.0/255.0, blue: 174.0/255.0, alpha: 1.0).cgColor, 22 | UIColor(red: 200.0/255.0, green: 109.0/255.0, blue: 215.0/255.0, alpha: 1.0).cgColor 23 | ] 24 | v.gradientLayer.startPoint = CGPoint.zero 25 | v.gradientLayer.endPoint = CGPoint(x: 1, y: 1) 26 | return v 27 | }() 28 | 29 | 30 | // 31 | // Radial Gradient 32 | // 33 | lazy var radGradientView:KHGradientView = { 34 | let v = KHGradientView() 35 | v.gradientLayer.type = CAGradientLayerType.radial 36 | v.gradientLayer.colors = 37 | [ 38 | UIColor(red: 0.0/255.0, green: 101.0/255.0, blue: 255.0/255.0, alpha: 1.0).cgColor, 39 | UIColor(red: 0.0/255.0, green: 40.0/255.0, blue: 101.0/255.0, alpha: 1.0).cgColor 40 | ] 41 | v.gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5) 42 | v.gradientLayer.endPoint = CGPoint(x: 0, y: 1) 43 | return v 44 | }() 45 | 46 | 47 | // 48 | // Angluar Gradient 49 | // 50 | lazy var angGradientView:KHGradientView = { 51 | let v = KHGradientView() 52 | v.gradientLayer.type = CAGradientLayerType.conic 53 | v.gradientLayer.colors = 54 | [ 55 | UIColor(red: 245.0/255.0, green: 81.0/255.0, blue: 95.0/255.0, alpha: 1.0).cgColor, 56 | UIColor(red: 159.0/255.0, green: 4.0/255.0, blue: 27.0/255.0, alpha: 1.0).cgColor 57 | ] 58 | v.gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5) 59 | v.gradientLayer.endPoint = CGPoint(x: 1, y: 0.5) 60 | return v 61 | }() 62 | 63 | override func viewDidLoad() { 64 | super.viewDidLoad() 65 | // Do any additional setup after loading the view. 66 | 67 | self.view.addSubview(self.linGradientView) 68 | self.view.addSubview(self.radGradientView) 69 | self.view.addSubview(self.angGradientView) 70 | } 71 | 72 | override var prefersStatusBarHidden: Bool { 73 | return true 74 | } 75 | 76 | override func viewDidLayoutSubviews() { 77 | super.viewDidLayoutSubviews() 78 | let size = self.view.bounds.size 79 | 80 | if size.height > size.width { 81 | // Vertical layout 82 | 83 | let height = size.height / 3 84 | 85 | self.linGradientView.frame = { 86 | var frame = CGRect() 87 | frame.size.height = height 88 | frame.size.width = size.width 89 | return frame 90 | }() 91 | 92 | self.radGradientView.frame = { 93 | var frame = CGRect() 94 | frame.origin.y = height 95 | frame.size.height = height 96 | frame.size.width = size.width 97 | return frame 98 | }() 99 | 100 | self.angGradientView.frame = { 101 | var frame = CGRect() 102 | frame.origin.y = size.height - height 103 | frame.size.height = height 104 | frame.size.width = size.width 105 | return frame 106 | }() 107 | 108 | } 109 | else { 110 | // Horizontal layout 111 | 112 | let width = size.width / 3 113 | 114 | self.linGradientView.frame = { 115 | var frame = CGRect() 116 | frame.size.height = size.height 117 | frame.size.width = width 118 | return frame 119 | }() 120 | 121 | self.radGradientView.frame = { 122 | var frame = CGRect() 123 | frame.origin.x = width 124 | frame.size.height = size.height 125 | frame.size.width = width 126 | return frame 127 | }() 128 | 129 | self.angGradientView.frame = { 130 | var frame = CGRect() 131 | frame.origin.x = size.width - width 132 | frame.size.height = size.height 133 | frame.size.width = width 134 | return frame 135 | }() 136 | } 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // External Shadow 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // External Shadow 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | #pragma mark - UISceneSession lifecycle 25 | 26 | 27 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 28 | // Called when a new scene session is being created. 29 | // Use this method to select a configuration to create the new scene with. 30 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 31 | } 32 | 33 | 34 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 35 | // Called when the user discards a scene session. 36 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 37 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 38 | } 39 | 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/Assets.xcassets/Checkerboard.imageset/Checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylehowells/ikyle.me-code-examples/502052a2e5da56c6fdb78148ea5ac7e132efab32/External Shadow ObjC/External Shadow/Assets.xcassets/Checkerboard.imageset/Checkerboard.png -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/Assets.xcassets/Checkerboard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Checkerboard.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 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/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 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // External Shadow 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SceneDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow * window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // External Shadow 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "SceneDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface SceneDelegate () 13 | @end 14 | 15 | @implementation SceneDelegate 16 | 17 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 18 | self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene*)scene]; 19 | self.window.rootViewController = [[ViewController alloc] init]; 20 | [self.window makeKeyAndVisible]; 21 | } 22 | 23 | 24 | - (void)sceneDidDisconnect:(UIScene *)scene { 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 neccessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | 32 | - (void)sceneDidBecomeActive:(UIScene *)scene { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | 38 | - (void)sceneWillResignActive:(UIScene *)scene { 39 | // Called when the scene will move from an active state to an inactive state. 40 | // This may occur due to temporary interruptions (ex. an incoming phone call). 41 | } 42 | 43 | 44 | - (void)sceneWillEnterForeground:(UIScene *)scene { 45 | // Called as the scene transitions from the background to the foreground. 46 | // Use this method to undo the changes made on entering the background. 47 | } 48 | 49 | 50 | - (void)sceneDidEnterBackground:(UIScene *)scene { 51 | // Called as the scene transitions from the foreground to the background. 52 | // Use this method to save data, release shared resources, and store enough scene-specific state information 53 | // to restore the scene back to its current state. 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // External Shadow 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // External Shadow 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | @end 13 | 14 | @implementation ViewController{ 15 | UIView *shadowView; 16 | } 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | 21 | self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"Checkerboard"]]; 22 | 23 | // 24 | // Create Shadow View 25 | // 26 | shadowView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; 27 | shadowView.backgroundColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:0.5]; 28 | shadowView.center = self.view.center; 29 | shadowView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin; 30 | [self.view addSubview:shadowView]; 31 | shadowView.layer.cornerRadius = 10; 32 | 33 | // 34 | // Add a shadow to the view 35 | // 36 | shadowView.layer.shadowColor = [UIColor blackColor].CGColor; 37 | shadowView.layer.shadowOffset = CGSizeMake(0, 1); 38 | shadowView.layer.shadowRadius = 20; 39 | shadowView.layer.shadowOpacity = 1; 40 | // Set the shadow path 41 | shadowView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:({ 42 | CGRect frame = shadowView.bounds; 43 | frame.origin.x = 0; 44 | frame; 45 | }) cornerRadius:shadowView.layer.cornerRadius].CGPath; 46 | 47 | 48 | // 49 | // Setup a mask to match the view 50 | // 51 | CAShapeLayer *maskLayer = [CAShapeLayer layer]; 52 | maskLayer.frame = shadowView.bounds; 53 | maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:shadowView.bounds cornerRadius:shadowView.layer.cornerRadius].CGPath; 54 | 55 | // 56 | // Make the mask area bigger than the view, so the shadow itself does not get clipped by the mask 57 | // 58 | CGFloat shadowBorder = (shadowView.layer.shadowRadius * 2) + 5;// 50.0; 59 | maskLayer.frame = CGRectInset( maskLayer.frame, -shadowBorder, -shadowBorder ); // Make bigger 60 | maskLayer.frame = CGRectOffset( maskLayer.frame, shadowBorder/2.0, shadowBorder/2.0 ); // Move top and left 61 | 62 | // Allow for cut outs in the shape 63 | maskLayer.fillRule = kCAFillRuleEvenOdd; 64 | 65 | // 66 | // Create new path 67 | // 68 | CGMutablePathRef pathMasking = CGPathCreateMutable(); 69 | // Add the outer view frame 70 | CGPathAddPath(pathMasking, NULL, [UIBezierPath bezierPathWithRect:maskLayer.frame].CGPath); 71 | // Translate into the shape back to the smaller original view's frame start point 72 | CGAffineTransform catShiftBorder = CGAffineTransformMakeTranslation( shadowBorder/2.0, shadowBorder/2.0); 73 | // Now add the original path for the cut out the shape of the original view 74 | CGPathAddPath(pathMasking, NULL, CGPathCreateCopyByTransformingPath(maskLayer.path, &catShiftBorder ) ); 75 | // Set this big rect with a small cutout rect as the mask 76 | maskLayer.path = pathMasking; 77 | 78 | // 79 | // Set as a mask on the view with the shadow 80 | // 81 | shadowView.layer.mask = maskLayer; 82 | 83 | 84 | UIView *contentView = [[UIView alloc] initWithFrame:shadowView.frame]; 85 | contentView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin; 86 | contentView.backgroundColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:0.5]; 87 | contentView.layer.cornerRadius = 10; 88 | [self.view addSubview:contentView]; 89 | } 90 | 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /External Shadow ObjC/External Shadow/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // External Shadow 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | NSString * appDelegateClassName; 14 | @autoreleasepool { 15 | // Setup code that might create autoreleased objects goes here. 16 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 17 | } 18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 19 | } 20 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // External Shadow Swift 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/Assets.xcassets/Checkerboard.imageset/Checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylehowells/ikyle.me-code-examples/502052a2e5da56c6fdb78148ea5ac7e132efab32/External Shadow Swift/External Shadow Swift/Assets.xcassets/Checkerboard.imageset/Checkerboard.png -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/Assets.xcassets/Checkerboard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Checkerboard.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 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/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 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // External Shadow Swift 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 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 neccessarily 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 | -------------------------------------------------------------------------------- /External Shadow Swift/External Shadow Swift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // External Shadow Swift 4 | // 5 | // Created by Kyle Howells on 16/06/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | view.backgroundColor = UIColor(patternImage: UIImage(named: "Checkerboard")!) 17 | 18 | 19 | // 20 | // Create Shadow View 21 | // 22 | let shadowView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) 23 | shadowView.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 0.5) 24 | shadowView.center = view.center 25 | shadowView.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleLeftMargin] 26 | view.addSubview(shadowView) 27 | shadowView.layer.cornerRadius = 10 28 | 29 | 30 | // 31 | // Add a shadow to the view 32 | // 33 | shadowView.layer.shadowColor = UIColor.black.cgColor 34 | shadowView.layer.shadowOffset = CGSize(width:0, height:1) 35 | shadowView.layer.shadowRadius = 20 36 | shadowView.layer.shadowOpacity = 1 37 | // Set the shadow path 38 | shadowView.layer.shadowPath = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: shadowView.layer.cornerRadius).cgPath 39 | 40 | 41 | // 42 | // Setup a mask to match the view 43 | // 44 | let maskLayer = CAShapeLayer() 45 | maskLayer.frame = shadowView.bounds 46 | maskLayer.path = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: shadowView.layer.cornerRadius).cgPath 47 | 48 | // 49 | // Make the mask area bigger than the view, so the shadow itself does not get clipped by the mask 50 | // 51 | let shadowBorder:CGFloat = (shadowView.layer.shadowRadius * 2) + 5; 52 | maskLayer.frame = maskLayer.frame.insetBy(dx: -shadowBorder, dy: -shadowBorder) // Make bigger 53 | maskLayer.frame = maskLayer.frame.offsetBy(dx: shadowBorder/2, dy: shadowBorder/2) // Move top and left 54 | 55 | // Allow for cut outs in the shape 56 | maskLayer.fillRule = .evenOdd 57 | 58 | 59 | // 60 | // Create new path 61 | // 62 | let pathMasking = CGMutablePath() 63 | // Add the outer view frame 64 | pathMasking.addPath(UIBezierPath(rect: maskLayer.frame).cgPath) 65 | // Translate into the shape back to the smaller original view's frame start point 66 | var catShiftBorder = CGAffineTransform(translationX: shadowBorder/2, y: shadowBorder/2) 67 | // Now add the original path for the cut out the shape of the original view 68 | pathMasking.addPath(maskLayer.path!.copy(using: &catShiftBorder)!) 69 | // Set this big rect with a small cutout rect as the mask 70 | maskLayer.path = pathMasking; 71 | 72 | 73 | // 74 | // Set as a mask on the view with the shadow 75 | // 76 | shadowView.layer.mask = maskLayer; 77 | 78 | 79 | // 80 | // Content view to use 81 | // 82 | let contentView = UIView(frame: shadowView.frame) 83 | contentView.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 0.5) 84 | contentView.center = shadowView.center 85 | contentView.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleLeftMargin] 86 | view.addSubview(contentView) 87 | contentView.layer.cornerRadius = shadowView.layer.cornerRadius 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Illustrations 4 | // 5 | // Created by Kyle Howells on 22/04/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 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 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/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 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/Assets.xcassets/arrow.clockwise.custom.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "arrow.clockwise.custom.svg", 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 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/Assets.xcassets/arrow.clockwise.custom.imageset/arrow.clockwise.custom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Path 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/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 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/Demos/Anchor Points/KHVisualiseView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KHVisualiseView.swift 3 | // Illustrations 4 | // 5 | // Created by Kyle Howells on 22/04/2022. 6 | // 7 | 8 | import UIKit 9 | import CoreGraphics 10 | 11 | class KHVisualiseView: UIView { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | 16 | self.backgroundView.layer.borderWidth = 4 17 | 18 | self.addSubview(self.backgroundView) 19 | self.addSubview(self.titleLabel) 20 | self.addSubview(self.anchorPointView) 21 | self.addSubview(self.rotationImageView) 22 | 23 | // Using defer to make the didSet run 24 | defer { 25 | self.primaryColor = UIColor(red: 252.0/255.0, green: 185.0/255.0, blue: 45.0/255.0, alpha: 1) 26 | } 27 | } 28 | 29 | @available(*, unavailable) 30 | required init?(coder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | var primaryColor:UIColor = UIColor(red: 252.0/255.0, green: 185.0/255.0, blue: 45.0/255.0, alpha: 1) { 35 | didSet { 36 | self.backgroundView.backgroundColor = self.primaryColor.withAlphaComponent(0.5) 37 | self.backgroundView.layer.borderColor = self.primaryColor.cgColor 38 | } 39 | } 40 | 41 | 42 | // MARK: - Background 43 | 44 | let backgroundView: UIView = { 45 | let view = UIView() 46 | return view 47 | }() 48 | 49 | 50 | // MARK: - Title 51 | 52 | let titleLabel: UILabel = { 53 | let l: UILabel = UILabel() 54 | l.alpha = 0.5 55 | l.textColor = UIColor.black 56 | l.font = UIFont.systemFont(ofSize: 15, weight: .medium) 57 | l.adjustsFontSizeToFitWidth = true 58 | l.allowsDefaultTighteningForTruncation = true 59 | //l.showsExpansionTextWhenTruncated = true 60 | return l 61 | }() 62 | 63 | enum Position { 64 | case topLeft 65 | case topCenter 66 | case topRight 67 | 68 | case middleLeft 69 | case middleCenter 70 | case middleRight 71 | 72 | case bottomLeft 73 | case bottomCenter 74 | case bottomRight 75 | } 76 | var titlePosition:Position = .topLeft { 77 | didSet { 78 | self.setNeedsLayout() 79 | } 80 | } 81 | 82 | 83 | let anchorPointView:UIView = { 84 | let view = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 12)) 85 | view.alpha = 0 86 | view.backgroundColor = UIColor(red: 70.0/255.0, green: 165.0/255.0, blue: 226.0/255.0, alpha: 0.5) 87 | 88 | view.layer.borderColor = UIColor(red: 70.0/255.0, green: 165.0/255.0, blue: 226.0/255.0, alpha: 1.0).cgColor 89 | view.layer.borderWidth = 3 90 | 91 | view.layer.cornerRadius = view.bounds.width * 0.5 92 | return view 93 | }() 94 | 95 | 96 | let rotationImageView:UIImageView = { 97 | let imv = UIImageView(image: UIImage(named: "arrow.clockwise.custom")) 98 | imv.tintColor = UIColor.black 99 | imv.frame = CGRect(x: 0, y: 0, width: 40, height: 40) 100 | imv.contentMode = UIView.ContentMode.scaleAspectFit 101 | imv.layer.anchorPoint = CGPoint(x: 0.52, y: 0.56) 102 | imv.alpha = 0 103 | return imv 104 | }() 105 | 106 | 107 | // MARK: - Layout 108 | 109 | override func layoutSubviews() { 110 | super.layoutSubviews() 111 | 112 | let size: CGSize = self.bounds.size 113 | 114 | self.backgroundView.frame = self.bounds 115 | 116 | let borderWidth = self.backgroundView.layer.borderWidth 117 | 118 | self.titleLabel.frame = { 119 | let labelSize = self.titleLabel.sizeThatFits(size) 120 | 121 | var frame = CGRect() 122 | frame.size = labelSize 123 | frame.size.width = min(frame.width, size.width - (borderWidth * 2)) 124 | frame.size.height = min(frame.height, size.height - (borderWidth * 2)) 125 | 126 | switch self.titlePosition { 127 | case .topLeft: 128 | frame.origin.x = (borderWidth + 1) 129 | frame.origin.y = (borderWidth + 1) 130 | 131 | case .topCenter: 132 | frame.origin.x = (size.width - frame.width) * 0.5 133 | frame.origin.y = (borderWidth + 1) 134 | 135 | case .topRight: 136 | frame.origin.x = (size.width - (frame.width + (borderWidth + 1))) 137 | frame.origin.y = (borderWidth + 1) 138 | 139 | case .middleLeft: 140 | frame.origin.x = (borderWidth + 1) 141 | frame.origin.y = (size.height - frame.height) * 0.5 142 | 143 | case .middleCenter: 144 | frame.origin.x = (size.width - frame.width) * 0.5 145 | frame.origin.y = (size.height - frame.height) * 0.5 146 | 147 | case .middleRight: 148 | frame.origin.x = (size.width - (frame.width + (borderWidth + 1))) 149 | frame.origin.y = (size.height - frame.height) * 0.5 150 | 151 | case .bottomLeft: 152 | frame.origin.x = (borderWidth + 1) 153 | frame.origin.y = (size.height - (frame.height + (borderWidth + 1))) 154 | 155 | case .bottomCenter: 156 | frame.origin.x = (size.width - frame.width) * 0.5 157 | frame.origin.y = (size.height - (frame.height + (borderWidth + 1))) 158 | 159 | case .bottomRight: 160 | frame.origin.x = (size.width - (frame.width + (borderWidth + 1))) 161 | frame.origin.y = (size.height - (frame.height + (borderWidth + 1))) 162 | } 163 | 164 | return frame 165 | }() 166 | 167 | self.anchorPointView.center = CGPoint( 168 | x: size.width * self.layer.anchorPoint.x, 169 | y: size.height * self.layer.anchorPoint.y 170 | ) 171 | self.rotationImageView.center = self.anchorPointView.center 172 | 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/Demos/DemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoViewController.swift 3 | // Illustrations 4 | // 5 | // Created by Kyle Howells on 22/04/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class DemoViewController: UIViewController { 11 | 12 | /// Display at exactly this size for internal animations to make sense 13 | @objc class var displaySize:CGSize { 14 | return CGSize(width: 1280, height: 720) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/Illustrations.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/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 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/MP4/MP4Recorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MP4Recorder.swift 3 | // Code Examples 4 | // 5 | // Created by Kyle Howells on 03/07/2022. 6 | // 7 | 8 | 9 | import UIKit 10 | import AVFoundation 11 | import CoreGraphics 12 | 13 | 14 | // MARK: - MP4Recorder 15 | 16 | /// Screen records and write the result to an MP4 17 | class MP4Recorder: NSObject { 18 | 19 | private let videoExporter: MP4Exporter 20 | 21 | private lazy var displayLink: CADisplayLink = { [unowned self] in 22 | let displayLink = CADisplayLink(target: self, selector: #selector(self.update)) 23 | displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60, maximum: 60) 24 | return displayLink 25 | }() 26 | 27 | let renderFrame: ()->UIImage 28 | 29 | 30 | /// <#Description#> 31 | /// - Parameters: 32 | /// - videoSize: <#videoSize description#> 33 | /// - outputURL: <#outputURL description#> 34 | /// - renderFrame: <#renderFrame description#> 35 | init?(videoSize: CGSize, outputURL: URL, renderFrame: @escaping ()->UIImage) { 36 | guard let exporter = MP4Exporter(videoSize: videoSize, outputURL: outputURL) else { return nil } 37 | 38 | self.videoExporter = exporter 39 | 40 | self.renderFrame = renderFrame 41 | 42 | super.init() 43 | 44 | self.displayLink.add(to: .current, forMode: .common) 45 | 46 | guard self.videoExporter.addImage(image: renderFrame(), withPresentationTime: CMTime.zero) else { return nil } 47 | } 48 | 49 | 50 | // MARK: - Render Frame 51 | 52 | private var firstFrameTime:CFTimeInterval? = nil 53 | 54 | @objc private func update() 55 | { 56 | let timestamp = self.displayLink.timestamp 57 | 58 | if let firstFrameTime: CFTimeInterval = self.firstFrameTime { 59 | let image:UIImage = self.renderFrame() 60 | 61 | let timeDiff: CFTimeInterval = (timestamp - firstFrameTime) 62 | 63 | let presentationTime = CMTime(seconds: Double(timeDiff), preferredTimescale: 10000) 64 | 65 | if self.videoExporter.addImage(image: image, withPresentationTime: presentationTime) == false { 66 | print("ERROR: Failed to append frame") 67 | } 68 | } 69 | 70 | if self.firstFrameTime == nil && timestamp > 0 { 71 | self.firstFrameTime = timestamp 72 | } 73 | } 74 | 75 | 76 | // MARK: - Stop Video 77 | 78 | /// <#Description#> 79 | /// - Parameter completion: <#completion description#> 80 | func stopRecording(completion: @escaping ()->()) { 81 | self.displayLink.invalidate() 82 | 83 | self.videoExporter.stopRecording(completion: { 84 | print("Finished Screen Recording") 85 | }) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Illustrations/Illustrations/Illustrations/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Illustrations 4 | // 5 | // Created by Kyle Howells on 22/04/2022. 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 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | guard let windowScene = (scene as? UIWindowScene) else { return } 18 | 19 | let window = UIWindow(windowScene: windowScene) 20 | window.rootViewController = ViewController() 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 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | func sceneWillResignActive(_ scene: UIScene) { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | func sceneWillEnterForeground(_ scene: UIScene) { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | func sceneDidEnterBackground(_ scene: UIScene) { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | 16 | @implementation AppDelegate 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | #pragma mark - UISceneSession lifecycle 25 | 26 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 27 | // Called when a new scene session is being created. 28 | // Use this method to select a configuration to create the new scene with. 29 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 30 | } 31 | 32 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 33 | // Called when the user discards a scene session. 34 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 35 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/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 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/KHGradientView.h: -------------------------------------------------------------------------------- 1 | // 2 | // KHGradientView.h 3 | // ObjC Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | @import QuartzCore; 11 | 12 | @interface KHGradientView : UIView 13 | @property(nonatomic, readonly, strong) CAGradientLayer *layer; 14 | @end 15 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/KHGradientView.m: -------------------------------------------------------------------------------- 1 | // 2 | // KHGradientView.m 3 | // ObjC Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "KHGradientView.h" 10 | 11 | @implementation KHGradientView 12 | @dynamic layer; 13 | 14 | +(Class)layerClass{ 15 | return [CAGradientLayer class]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/ObjC Live Preview-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "ViewController.h" 6 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SceneDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow * window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "SceneDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface SceneDelegate () 13 | 14 | @end 15 | 16 | 17 | @implementation SceneDelegate 18 | 19 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 20 | self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene*)scene]; 21 | self.window.rootViewController = [[ViewController alloc] init]; 22 | [self.window makeKeyAndVisible]; 23 | } 24 | 25 | - (void)sceneDidDisconnect:(UIScene *)scene { 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 neccessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | - (void)sceneDidBecomeActive:(UIScene *)scene { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | - (void)sceneWillResignActive:(UIScene *)scene { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | - (void)sceneWillEnterForeground:(UIScene *)scene { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | - (void)sceneDidEnterBackground:(UIScene *)scene { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/SwiftUIView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // ObjC Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct VCRepresentable: UIViewControllerRepresentable { 12 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) { 13 | // leave this empty 14 | } 15 | 16 | @available(iOS 13.0.0, *) 17 | func makeUIViewController(context: Context) -> UIViewController { 18 | return VCClass() 19 | } 20 | } 21 | 22 | struct SwiftUIView_Previews: PreviewProvider { 23 | static var previews: some View { 24 | VCRepresentable() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "KHGradientView.h" 11 | 12 | @interface ViewController () 13 | 14 | @end 15 | 16 | 17 | @implementation ViewController{ 18 | KHGradientView *backgroundView; 19 | UILabel *label; 20 | } 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | self.view.backgroundColor = [UIColor blackColor]; 25 | 26 | backgroundView = [[KHGradientView alloc] init]; 27 | backgroundView.layer.colors = 28 | @[ 29 | (id)[UIColor colorWithRed: 48.0/255.0 green: 35.0/255.0 blue: 174.0/255.0 alpha: 1.0].CGColor, 30 | (id)[UIColor colorWithRed: 200.0/255.0 green: 109.0/255.0 blue: 215.0/255.0 alpha: 1.0].CGColor 31 | ]; 32 | [self.view addSubview:backgroundView]; 33 | 34 | label = [[UILabel alloc] init]; 35 | label.font = [UIFont systemFontOfSize:20 weight:UIFontWeightBold]; 36 | label.text = @" Hello World "; 37 | label.textAlignment = NSTextAlignmentCenter; 38 | label.textColor = [UIColor blackColor]; 39 | label.backgroundColor = [UIColor whiteColor]; 40 | label.layer.cornerRadius = 8; 41 | label.layer.masksToBounds = YES; 42 | [self.view addSubview:label]; 43 | } 44 | 45 | 46 | - (void)viewDidLayoutSubviews{ 47 | [super viewDidLayoutSubviews]; 48 | const CGSize size = self.view.bounds.size; 49 | 50 | backgroundView.frame = self.view.bounds; 51 | 52 | [label sizeToFit]; 53 | label.frame = CGRectInset(label.frame, -10, -10); 54 | label.center = CGPointMake(size.width * 0.5, size.height * 0.5); 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /ObjC Live Preview/Live Preview/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Live Preview 4 | // 5 | // Created by Kyle Howells on 01/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | NSString * appDelegateClassName; 14 | @autoreleasepool { 15 | // Setup code that might create autoreleased objects goes here. 16 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 17 | } 18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 19 | } 20 | -------------------------------------------------------------------------------- /ObjC Live Preview/ObjC Live Preview.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ObjC Live Preview/ObjC Live Preview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Photo Picker ObjC 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Photo Picker ObjC 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | 12 | @end 13 | 14 | @implementation AppDelegate 15 | 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | 23 | #pragma mark - UISceneSession lifecycle 24 | 25 | 26 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 27 | // Called when a new scene session is being created. 28 | // Use this method to select a configuration to create the new scene with. 29 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 30 | } 31 | 32 | 33 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 34 | // Called when the user discards a scene session. 35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/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 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/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 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | SceneDelegate 36 | 37 | 38 | 39 | 40 | NSPhotoLibraryUsageDescription 41 | Test 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // Photo Picker ObjC 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | #import 9 | 10 | @interface SceneDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow * window; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // Photo Picker ObjC 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | #import "SceneDelegate.h" 9 | #import "ViewController.h" 10 | 11 | @interface SceneDelegate () 12 | 13 | @end 14 | 15 | 16 | @implementation SceneDelegate 17 | 18 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 19 | self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene*)scene]; 20 | self.window.rootViewController = [[ViewController alloc] init]; 21 | [self.window makeKeyAndVisible]; 22 | } 23 | 24 | 25 | - (void)sceneDidDisconnect:(UIScene *)scene { 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 | 33 | - (void)sceneDidBecomeActive:(UIScene *)scene { 34 | // Called when the scene has moved from an inactive state to an active state. 35 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 36 | } 37 | 38 | 39 | - (void)sceneWillResignActive:(UIScene *)scene { 40 | // Called when the scene will move from an active state to an inactive state. 41 | // This may occur due to temporary interruptions (ex. an incoming phone call). 42 | } 43 | 44 | 45 | - (void)sceneWillEnterForeground:(UIScene *)scene { 46 | // Called as the scene transitions from the background to the foreground. 47 | // Use this method to undo the changes made on entering the background. 48 | } 49 | 50 | 51 | - (void)sceneDidEnterBackground:(UIScene *)scene { 52 | // Called as the scene transitions from the foreground to the background. 53 | // Use this method to save data, release shared resources, and store enough scene-specific state information 54 | // to restore the scene back to its current state. 55 | } 56 | 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Photo Picker ObjC 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Photo Picker ObjC 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | #import "ViewController.h" 9 | #import 10 | #import 11 | @import MobileCoreServices; 12 | 13 | @interface ViewController () 14 | 15 | @end 16 | 17 | @implementation ViewController{ 18 | UIScrollView *scrollView; 19 | NSMutableArray * imageViews; 20 | UIButton *selectButton; 21 | } 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | 26 | self.view.backgroundColor = [UIColor whiteColor]; 27 | 28 | imageViews = [NSMutableArray array]; 29 | 30 | // Create ScrollView 31 | scrollView = [[UIScrollView alloc] init]; 32 | scrollView.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1]; 33 | [self.view addSubview:scrollView]; 34 | 35 | // Select Photos Button 36 | selectButton = [UIButton buttonWithType:UIButtonTypeSystem]; 37 | [selectButton setTitle:@"Select Photos" forState:UIControlStateNormal]; 38 | [selectButton addTarget:self action:@selector(selectPressed:) forControlEvents:UIControlEventTouchUpInside]; 39 | [selectButton sizeToFit]; 40 | [self.view addSubview:selectButton]; 41 | } 42 | 43 | -(void)viewDidLayoutSubviews{ 44 | [super viewDidLayoutSubviews]; 45 | const CGSize size = self.view.bounds.size; 46 | const UIEdgeInsets safeArea = self.view.safeAreaInsets; 47 | 48 | selectButton.frame = ({ 49 | CGRect frame = CGRectZero; 50 | frame.size.width = MIN(size.width - 20, 250); 51 | frame.size.height = 40; 52 | frame.origin.y = size.height - (frame.size.height + 20 + safeArea.bottom); 53 | frame.origin.x = (size.width - frame.size.width) * 0.5; 54 | frame; 55 | }); 56 | 57 | scrollView.frame = ({ 58 | CGRect frame = CGRectZero; 59 | frame.origin.y = safeArea.top + 10; 60 | frame.size.height = (selectButton.frame.origin.y - 20) - frame.origin.y; 61 | frame.size.width = size.width - 40; 62 | frame.origin.x = (size.width - frame.size.width) * 0.5; 63 | frame; 64 | }); 65 | 66 | CGFloat y = 10; 67 | for (NSInteger i = 0; i < imageViews.count; i++) { 68 | UIImageView *imageView = imageViews[i]; 69 | imageView.frame = ({ 70 | CGRect frame = CGRectZero; 71 | frame.origin.y = y; 72 | frame.size.width = MIN(scrollView.bounds.size.width - 20, 300); 73 | frame.origin.x = (scrollView.bounds.size.width - frame.size.width) * 0.5; 74 | frame.size.height = MIN(frame.size.width * 0.75, 250); 75 | y += frame.size.height + 10; 76 | frame; 77 | }); 78 | } 79 | scrollView.contentSize = CGSizeMake(0, y); 80 | } 81 | 82 | #pragma mark Helpers 83 | 84 | -(UIImageView*)newImageViewForImage:(UIImage*)image{ 85 | UIImageView *imageView = [[UIImageView alloc] init]; 86 | imageView.contentMode = UIViewContentModeScaleAspectFit; 87 | imageView.backgroundColor = [UIColor blackColor]; 88 | imageView.image = image; 89 | return imageView; 90 | } 91 | 92 | -(void)clearImageViews{ 93 | for (UIImageView *imageView in imageViews) { 94 | [imageView removeFromSuperview]; 95 | } 96 | [imageViews removeAllObjects]; 97 | } 98 | 99 | #pragma mark - PHPicker 100 | 101 | -(void)selectPressed:(id)sender{ 102 | PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init]; 103 | config.selectionLimit = 3; 104 | config.filter = [PHPickerFilter imagesFilter]; 105 | 106 | PHPickerViewController *pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config]; 107 | pickerViewController.delegate = self; 108 | [self presentViewController:pickerViewController animated:YES completion:nil]; 109 | } 110 | 111 | -(void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results{ 112 | NSLog(@"-picker:%@ didFinishPicking:%@", picker, results); 113 | 114 | [self clearImageViews]; 115 | [picker dismissViewControllerAnimated:YES completion:nil]; 116 | 117 | for (PHPickerResult *result in results) { 118 | NSLog(@"result: %@", result); 119 | 120 | NSLog(@"%@", result.assetIdentifier); 121 | NSLog(@"%@", result.itemProvider); 122 | 123 | // Get UIImage 124 | [result.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id _Nullable object, NSError * _Nullable error) { 125 | NSLog(@"object: %@, error: %@", object, error); 126 | 127 | if ([object isKindOfClass:[UIImage class]]) { 128 | dispatch_async(dispatch_get_main_queue(), ^{ 129 | UIImageView *imageView = [self newImageViewForImage:(UIImage*)object]; 130 | [self->imageViews addObject:imageView]; 131 | [self->scrollView addSubview:imageView]; 132 | [self.view setNeedsLayout]; 133 | }); 134 | } 135 | }]; 136 | 137 | // Get file 138 | // [result.itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(id item, NSError *error) { 139 | // NSLog(@"%@", item); 140 | // NSLog(@"%@", [item class]); 141 | // 142 | // if ([item isKindOfClass:[NSURL class]]) { 143 | // NSError *error = nil; 144 | // NSData *data = [NSData dataWithContentsOfURL:item options:0 error:&error]; 145 | // NSLog(@"%@", data); 146 | // NSLog(@"%@", error); 147 | // } 148 | // }]; 149 | } 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /Photo Picker ObjC/Photo Picker ObjC/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Photo Picker ObjC 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) { 12 | NSString * appDelegateClassName; 13 | @autoreleasepool { 14 | // Setup code that might create autoreleased objects goes here. 15 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 16 | } 17 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 18 | } 19 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Photo Picker Swift 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | @UIApplicationMain 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 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift/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 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift/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 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UIApplicationSupportsIndirectInputEvents 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Photo Picker Swift 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 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 | -------------------------------------------------------------------------------- /Photo Picker Swift/Photo Picker Swift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Photo Picker Swift 4 | // 5 | // Created by Kyle Howells on 23/06/2020. 6 | // 7 | 8 | import UIKit 9 | import PhotosUI 10 | 11 | class ViewController: UIViewController, PHPickerViewControllerDelegate { 12 | 13 | // MARK: - PHPickerViewController 14 | 15 | @objc func pickPhotos() 16 | { 17 | var config = PHPickerConfiguration() 18 | config.selectionLimit = 3 19 | config.filter = PHPickerFilter.images 20 | 21 | let pickerViewController = PHPickerViewController(configuration: config) 22 | pickerViewController.delegate = self 23 | self.present(pickerViewController, animated: true, completion: nil) 24 | } 25 | 26 | // MARK: PHPickerViewControllerDelegate 27 | 28 | func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { 29 | picker.dismiss(animated: true, completion: nil) 30 | print(picker) 31 | print(results) 32 | 33 | for result in results { 34 | print(result.assetIdentifier) 35 | print(result.itemProvider) 36 | 37 | result.itemProvider.loadObject(ofClass: UIImage.self, completionHandler: { (object, error) in 38 | print(object) 39 | print(error) 40 | if let image = object as? UIImage { 41 | DispatchQueue.main.async { 42 | let imv = self.newImageView(image: image) 43 | self.imageViews.append(imv) 44 | self.scrollView.addSubview(imv) 45 | self.view.setNeedsLayout() 46 | } 47 | } 48 | }) 49 | } 50 | } 51 | 52 | 53 | 54 | // MARK: - View Setup 55 | 56 | lazy var scrollView:UIScrollView = { 57 | let s = UIScrollView() 58 | s.backgroundColor = UIColor(white: 0.98, alpha: 1) 59 | return s 60 | }() 61 | 62 | lazy var button:UIButton = { 63 | let b = UIButton(type: .system) 64 | b.setTitle("Select Photos", for: .normal) 65 | b.addTarget(self, action: #selector(pickPhotos), for: .touchUpInside) 66 | b.sizeToFit() 67 | return b 68 | }() 69 | 70 | var imageViews = [UIImageView]() 71 | 72 | func newImageView(image:UIImage?) -> UIImageView { 73 | let imv = UIImageView() 74 | imv.backgroundColor = .black 75 | imv.image = image 76 | return imv 77 | } 78 | 79 | override func viewDidLoad() { 80 | super.viewDidLoad() 81 | 82 | self.view.backgroundColor = UIColor.white 83 | 84 | self.view.addSubview(self.scrollView) 85 | self.view.addSubview(self.button) 86 | } 87 | 88 | override func viewDidLayoutSubviews() { 89 | super.viewDidLayoutSubviews() 90 | let size = self.view.bounds.size 91 | let safeArea = self.view.safeAreaInsets 92 | let padding:CGFloat = 10 93 | 94 | button.frame = { 95 | var f = CGRect.zero 96 | f.size.width = min(size.width - (padding * 2), 250) 97 | f.size.height = 40 98 | f.origin.x = (size.width - f.width) * 0.5 99 | f.origin.y = size.height - (safeArea.bottom + padding + f.size.height) 100 | return f 101 | }() 102 | 103 | scrollView.frame = { 104 | var f = CGRect.zero 105 | f.origin.y = safeArea.top + padding 106 | f.size.width = size.width - (padding * 2) 107 | f.size.height = (button.frame.minY - 20) - f.origin.y 108 | f.origin.x = (size.width - f.width) * 0.5 109 | return f 110 | }() 111 | 112 | var y:CGFloat = 10 113 | for imageView in imageViews { 114 | imageView.frame = { 115 | var f = CGRect.zero 116 | f.origin.y = y 117 | f.size.width = min(scrollView.bounds.width - (padding * 2), 300) 118 | f.size.height = min(f.width * 0.75, 250) 119 | f.origin.x = (scrollView.bounds.width - f.size.width) * 0.5 120 | y += f.size.height + padding 121 | return f 122 | }() 123 | } 124 | scrollView.contentSize = CGSize(width: 0, height: y) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ikyle.me-code-examples 2 | Smaller code examples from my blog posts on ikyle.me 3 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Read Write Image Metadata ObjC 4 | // 5 | // Created by Kyle Howells on 13/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Read Write Image Metadata ObjC 4 | // 5 | // Created by Kyle Howells on 13/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | #pragma mark - UISceneSession lifecycle 25 | 26 | 27 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 28 | // Called when a new scene session is being created. 29 | // Use this method to select a configuration to create the new scene with. 30 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 31 | } 32 | 33 | 34 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 35 | // Called when the user discards a scene session. 36 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 37 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 38 | } 39 | 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/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 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/Read Write Image Metadata ObjC.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // Read Write Image Metadata ObjC 4 | // 5 | // Created by Kyle Howells on 13/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SceneDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow * window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // Read Write Image Metadata ObjC 4 | // 5 | // Created by Kyle Howells on 13/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "SceneDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface SceneDelegate () 13 | 14 | @end 15 | 16 | 17 | @implementation SceneDelegate 18 | 19 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 20 | self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene*)scene]; 21 | self.window.rootViewController = [[ViewController alloc] init]; 22 | [self.window makeKeyAndVisible]; 23 | } 24 | 25 | 26 | - (void)sceneDidDisconnect:(UIScene *)scene { 27 | // Called as the scene is being released by the system. 28 | // This occurs shortly after the scene enters the background, or when its session is discarded. 29 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 30 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 31 | } 32 | 33 | 34 | - (void)sceneDidBecomeActive:(UIScene *)scene { 35 | // Called when the scene has moved from an inactive state to an active state. 36 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 37 | } 38 | 39 | 40 | - (void)sceneWillResignActive:(UIScene *)scene { 41 | // Called when the scene will move from an active state to an inactive state. 42 | // This may occur due to temporary interruptions (ex. an incoming phone call). 43 | } 44 | 45 | 46 | - (void)sceneWillEnterForeground:(UIScene *)scene { 47 | // Called as the scene transitions from the background to the foreground. 48 | // Use this method to undo the changes made on entering the background. 49 | } 50 | 51 | 52 | - (void)sceneDidEnterBackground:(UIScene *)scene { 53 | // Called as the scene transitions from the foreground to the background. 54 | // Use this method to save data, release shared resources, and store enough scene-specific state information 55 | // to restore the scene back to its current state. 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Read Write Image Metadata ObjC 4 | // 5 | // Created by Kyle Howells on 13/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Read Write Image Metadata ObjC/Read Write Image Metadata ObjC/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Read Write Image Metadata ObjC 4 | // 5 | // Created by Kyle Howells on 13/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | NSString * appDelegateClassName; 14 | @autoreleasepool { 15 | // Setup code that might create autoreleased objects goes here. 16 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 17 | } 18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 19 | } 20 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Read Write Image Metadata Swift 4 | // 5 | // Created by Kyle Howells on 10/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift/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 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift/Read Write Image Metadata Swift.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Read Write Image Metadata Swift/Read Write Image Metadata Swift/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Read Write Image Metadata Swift 4 | // 5 | // Created by Kyle Howells on 10/07/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | guard let windowScene = (scene as? UIWindowScene) else { return } 18 | 19 | let window = UIWindow(windowScene: windowScene) 20 | window.rootViewController = ViewController() 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 neccessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | func sceneDidBecomeActive(_ scene: UIScene) { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | func sceneWillResignActive(_ scene: UIScene) { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | func sceneWillEnterForeground(_ scene: UIScene) { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | func sceneDidEnterBackground(_ scene: UIScene) { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar.xcodeproj/xcuserdata/kylehowells.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Titlebar-TabBar.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | #pragma mark - UISceneSession lifecycle 25 | 26 | 27 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 28 | // Called when a new scene session is being created. 29 | // Use this method to select a configuration to create the new scene with. 30 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 31 | } 32 | 33 | 34 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 35 | // Called when the user discards a scene session. 36 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 37 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 38 | } 39 | 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Assets.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "first.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Assets.xcassets/first.imageset/first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylehowells/ikyle.me-code-examples/502052a2e5da56c6fdb78148ea5ac7e132efab32/macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Assets.xcassets/first.imageset/first.pdf -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Assets.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "second.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Assets.xcassets/second.imageset/second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylehowells/ikyle.me-code-examples/502052a2e5da56c6fdb78148ea5ac7e132efab32/macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Assets.xcassets/second.imageset/second.pdf -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/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 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/FirstViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirstViewController.h 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FirstViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/FirstViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirstViewController.m 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "FirstViewController.h" 10 | 11 | @interface FirstViewController () 12 | 13 | @end 14 | 15 | @implementation FirstViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | 21 | self.title = @"First"; 22 | 23 | self.view.backgroundColor = [UIColor whiteColor]; 24 | 25 | UILabel *label = [[UILabel alloc] init]; 26 | label.textColor = [UIColor lightGrayColor]; 27 | label.text = @"First View"; 28 | [label sizeToFit]; 29 | [self.view addSubview:label]; 30 | label.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; 31 | label.center = self.view.center; 32 | } 33 | 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UIStatusBarTintParameters 47 | 48 | UINavigationBar 49 | 50 | Style 51 | UIBarStyleDefault 52 | Translucent 53 | 54 | 55 | 56 | UISupportedInterfaceOrientations 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationLandscapeLeft 60 | UIInterfaceOrientationLandscapeRight 61 | 62 | UISupportedInterfaceOrientations~ipad 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationPortraitUpsideDown 66 | UIInterfaceOrientationLandscapeLeft 67 | UIInterfaceOrientationLandscapeRight 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SceneDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow * window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "SceneDelegate.h" 10 | #import "FirstViewController.h" 11 | #import "SecondViewController.h" 12 | 13 | #if TARGET_OS_MACCATALYST 14 | #import 15 | 16 | @interface SceneDelegate () 17 | #else 18 | @interface SceneDelegate () 19 | #endif 20 | @property (nonatomic, strong) UITabBarController *tabbarController; 21 | @end 22 | 23 | 24 | 25 | @implementation SceneDelegate 26 | 27 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions 28 | { 29 | self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene*)scene]; 30 | _tabbarController = [[UITabBarController alloc] init]; 31 | _tabbarController.viewControllers = @[ [[FirstViewController alloc] init], [[SecondViewController alloc] init] ]; 32 | 33 | #if TARGET_OS_MACCATALYST 34 | NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"]; 35 | toolbar.centeredItemIdentifier = @"mainTabsToolbarItem"; 36 | toolbar.delegate = self; 37 | 38 | [(UIWindowScene*)scene titlebar].toolbar = toolbar; 39 | [(UIWindowScene*)scene titlebar].titleVisibility = UITitlebarTitleVisibilityHidden; 40 | 41 | _tabbarController.tabBar.hidden = YES; 42 | #endif 43 | 44 | self.window.rootViewController = _tabbarController; 45 | [self.window makeKeyAndVisible]; 46 | } 47 | 48 | 49 | - (void)sceneDidDisconnect:(UIScene *)scene { 50 | // Called as the scene is being released by the system. 51 | // This occurs shortly after the scene enters the background, or when its session is discarded. 52 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 53 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 54 | } 55 | 56 | 57 | - (void)sceneDidBecomeActive:(UIScene *)scene { 58 | // Called when the scene has moved from an inactive state to an active state. 59 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 60 | } 61 | 62 | 63 | - (void)sceneWillResignActive:(UIScene *)scene { 64 | // Called when the scene will move from an active state to an inactive state. 65 | // This may occur due to temporary interruptions (ex. an incoming phone call). 66 | } 67 | 68 | 69 | - (void)sceneWillEnterForeground:(UIScene *)scene { 70 | // Called as the scene transitions from the background to the foreground. 71 | // Use this method to undo the changes made on entering the background. 72 | } 73 | 74 | 75 | - (void)sceneDidEnterBackground:(UIScene *)scene { 76 | // Called as the scene transitions from the foreground to the background. 77 | // Use this method to save data, release shared resources, and store enough scene-specific state information 78 | // to restore the scene back to its current state. 79 | } 80 | 81 | 82 | #pragma mark - NSToolbarDelegate 83 | 84 | #if TARGET_OS_MACCATALYST 85 | 86 | - (nullable NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSToolbarItemIdentifier)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag 87 | { 88 | if ([itemIdentifier isEqualToString:@"mainTabsToolbarItem"]) { 89 | NSToolbarItemGroup *group = [NSToolbarItemGroup groupWithItemIdentifier:itemIdentifier 90 | titles:@[@"First", @"Second"] 91 | selectionMode:NSToolbarItemGroupSelectionModeSelectOne 92 | labels:@[@"First view", @"Second view"] 93 | target:self 94 | action:@selector(toolbarGroupSelectionChanged:)]; 95 | [group setSelectedIndex:0]; 96 | return group; 97 | } 98 | 99 | return nil; 100 | } 101 | 102 | /* Returns the ordered list of items to be shown in the toolbar by default. If during initialization, no overriding values are found in the user defaults, or if the user chooses to revert to the default items this set will be used. */ 103 | - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar{ 104 | return @[@"mainTabsToolbarItem"]; 105 | } 106 | 107 | /* Returns the list of all allowed items by identifier. By default, the toolbar does not assume any items are allowed, even the separator. So, every allowed item must be explicitly listed. The set of allowed items is used to construct the customization palette. The order of items does not necessarily guarantee the order of appearance in the palette. At minimum, you should return the default item list.*/ 108 | - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar{ 109 | return [self toolbarDefaultItemIdentifiers:toolbar]; 110 | } 111 | 112 | 113 | -(void)toolbarGroupSelectionChanged:(NSToolbarItemGroup*)sender{ 114 | self.tabbarController.selectedIndex = sender.selectedIndex; 115 | } 116 | #endif 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/SecondViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.h 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SecondViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/SecondViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.m 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import "SecondViewController.h" 10 | 11 | @interface SecondViewController () 12 | 13 | @end 14 | 15 | @implementation SecondViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | 21 | self.title = @"Second"; 22 | 23 | self.view.backgroundColor = [UIColor whiteColor]; 24 | 25 | UILabel *label = [[UILabel alloc] init]; 26 | label.textColor = [UIColor lightGrayColor]; 27 | label.text = @"Second View"; 28 | [label sizeToFit]; 29 | [self.view addSubview:label]; 30 | label.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; 31 | label.center = self.view.center; 32 | } 33 | 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/Titlebar-TabBar.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-objc/Titlebar-TabBar/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | NSString * appDelegateClassName; 14 | @autoreleasepool { 15 | // Setup code that might create autoreleased objects goes here. 16 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 17 | } 18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 19 | } 20 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar.xcodeproj/xcuserdata/kylehowells.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Titlebar-TabBar.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 18 | 19 | // MARK: UISceneSession Lifecycle 20 | 21 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 22 | // Called when a new scene session is being created. 23 | // Use this method to select a configuration to create the new scene with. 24 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 25 | } 26 | 27 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 28 | // Called when the user discards a scene session. 29 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 30 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Assets.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "first.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Assets.xcassets/first.imageset/first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylehowells/ikyle.me-code-examples/502052a2e5da56c6fdb78148ea5ac7e132efab32/macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Assets.xcassets/first.imageset/first.pdf -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Assets.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "second.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Assets.xcassets/second.imageset/second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylehowells/ikyle.me-code-examples/502052a2e5da56c6fdb78148ea5ac7e132efab32/macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Assets.xcassets/second.imageset/second.pdf -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/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 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/FirstViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirstViewController.swift 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FirstViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view. 16 | self.title = "First" 17 | 18 | self.view.backgroundColor = .white 19 | 20 | let label = UILabel() 21 | label.text = "First View" 22 | label.textColor = UIColor.lightGray 23 | label.sizeToFit() 24 | self.view.addSubview(label) 25 | label.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleLeftMargin] 26 | label.center = self.view.center 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UIStatusBarTintParameters 47 | 48 | UINavigationBar 49 | 50 | Style 51 | UIBarStyleDefault 52 | Translucent 53 | 54 | 55 | 56 | UISupportedInterfaceOrientations 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationLandscapeLeft 60 | UIInterfaceOrientationLandscapeRight 61 | 62 | UISupportedInterfaceOrientations~ipad 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationPortraitUpsideDown 66 | UIInterfaceOrientationLandscapeLeft 67 | UIInterfaceOrientationLandscapeRight 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate, NSToolbarDelegate { 12 | 13 | var window: UIWindow? 14 | var tabBarController: UITabBarController! 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | guard let windowScene = (scene as? UIWindowScene) else { return } 18 | 19 | let window = UIWindow(windowScene: windowScene) 20 | 21 | tabBarController = UITabBarController() 22 | tabBarController.viewControllers = [FirstViewController(), SecondViewController()] 23 | window.rootViewController = tabBarController 24 | self.window = window 25 | window.makeKeyAndVisible() 26 | 27 | 28 | #if targetEnvironment(macCatalyst) 29 | let toolbar = NSToolbar(identifier: "mainToolbar") 30 | toolbar.centeredItemIdentifier = NSToolbarItem.Identifier(rawValue: "mainTabsToolbarItem") 31 | toolbar.delegate = self 32 | 33 | windowScene.titlebar?.titleVisibility = .hidden 34 | windowScene.titlebar?.toolbar = toolbar 35 | 36 | tabBarController.tabBar.isHidden = true 37 | #endif 38 | } 39 | 40 | func sceneDidDisconnect(_ scene: UIScene) { 41 | // Called as the scene is being released by the system. 42 | // This occurs shortly after the scene enters the background, or when its session is discarded. 43 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 44 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 45 | } 46 | 47 | func sceneDidBecomeActive(_ scene: UIScene) { 48 | // Called when the scene has moved from an inactive state to an active state. 49 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 50 | } 51 | 52 | func sceneWillResignActive(_ scene: UIScene) { 53 | // Called when the scene will move from an active state to an inactive state. 54 | // This may occur due to temporary interruptions (ex. an incoming phone call). 55 | } 56 | 57 | func sceneWillEnterForeground(_ scene: UIScene) { 58 | // Called as the scene transitions from the background to the foreground. 59 | // Use this method to undo the changes made on entering the background. 60 | } 61 | 62 | func sceneDidEnterBackground(_ scene: UIScene) { 63 | // Called as the scene transitions from the foreground to the background. 64 | // Use this method to save data, release shared resources, and store enough scene-specific state information 65 | // to restore the scene back to its current state. 66 | } 67 | 68 | 69 | // MARK: - NSToolbarDelegate 70 | 71 | func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? 72 | { 73 | if (itemIdentifier.rawValue == "mainTabsToolbarItem") 74 | { 75 | let group = NSToolbarItemGroup.init(itemIdentifier: itemIdentifier, 76 | titles: ["First", "Second"], 77 | selectionMode: .selectOne, 78 | labels: ["First view", "Second view"], 79 | target: self, action: #selector(toolbarGroupSelectionChanged)) 80 | group.setSelected(true, at: 0) 81 | return group 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 88 | return [NSToolbarItem.Identifier(rawValue: "mainTabsToolbarItem")] 89 | } 90 | 91 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 92 | return self.toolbarDefaultItemIdentifiers(toolbar) 93 | } 94 | 95 | 96 | @objc func toolbarGroupSelectionChanged(_ sender: NSToolbarItemGroup) { 97 | print("toolbarGroupSelectionChanged( \(sender.selectedIndex) )") 98 | tabBarController.selectedIndex = sender.selectedIndex 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/SecondViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.swift 3 | // Titlebar-TabBar 4 | // 5 | // Created by Kyle Howells on 31/05/2020. 6 | // Copyright © 2020 Kyle Howells. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SecondViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view. 16 | self.title = "Second" 17 | 18 | self.view.backgroundColor = .white 19 | 20 | let label = UILabel() 21 | label.text = "Second View" 22 | label.textColor = UIColor.lightGray 23 | label.sizeToFit() 24 | self.view.addSubview(label) 25 | label.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleLeftMargin] 26 | label.center = self.view.center 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /macCatalyst-titlebar-tabbar-swift/Titlebar-TabBar/Titlebar-TabBar.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------