├── .github └── workflows │ ├── build-and-test.yml │ └── swiftlint-check.yml ├── .gitignore ├── .logo ├── SightLogo │ ├── SightLogo.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── SightLogo │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── HomeView.swift │ │ ├── Info.plist │ │ ├── LogoSettingsView.swift │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ └── SceneDelegate.swift │ └── SightLogoTests │ │ ├── Info.plist │ │ └── SightLogoTests.swift └── logo.png ├── .swiftlint.yml ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── Sight │ ├── Element.swift │ ├── Region.swift │ └── SIMD2.swift └── Tests └── SightTests ├── Element └── ElementTests.swift ├── Region └── RegionTests.swift └── XCTestManifests.swift /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | 7 | jobs: 8 | build_and_test: 9 | runs-on: macOS-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v1 14 | - name: Build 15 | run: swift build -v 16 | - name: Run tests 17 | run: swift test -v -------------------------------------------------------------------------------- /.github/workflows/swiftlint-check.yml: -------------------------------------------------------------------------------- 1 | name: Swiftlint Check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | 7 | jobs: 8 | swiftlint_check: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v1 14 | - name: GitHub Action for SwiftLint 15 | uses: norio-nomura/action-swiftlint@3.0.1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | .swiftpm/ 7 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 64558113241C8FB800EFFCB6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64558112241C8FB800EFFCB6 /* AppDelegate.swift */; }; 11 | 64558115241C8FB800EFFCB6 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64558114241C8FB800EFFCB6 /* SceneDelegate.swift */; }; 12 | 64558119241C8FBA00EFFCB6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 64558118241C8FBA00EFFCB6 /* Assets.xcassets */; }; 13 | 6455811C241C8FBA00EFFCB6 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6455811B241C8FBA00EFFCB6 /* Preview Assets.xcassets */; }; 14 | 6455811F241C8FBA00EFFCB6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6455811D241C8FBA00EFFCB6 /* LaunchScreen.storyboard */; }; 15 | 6455812A241C8FBA00EFFCB6 /* SightLogoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64558129241C8FBA00EFFCB6 /* SightLogoTests.swift */; }; 16 | 64558135241C8FEB00EFFCB6 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64558134241C8FEB00EFFCB6 /* HomeView.swift */; }; 17 | 64558137241C901000EFFCB6 /* LogoSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64558136241C901000EFFCB6 /* LogoSettingsView.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | 64558126241C8FBA00EFFCB6 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 64558107241C8FB800EFFCB6 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 6455810E241C8FB800EFFCB6; 26 | remoteInfo = SightLogo; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 6455810F241C8FB800EFFCB6 /* SightLogo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SightLogo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 64558112241C8FB800EFFCB6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | 64558114241C8FB800EFFCB6 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 34 | 64558118241C8FBA00EFFCB6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35 | 6455811B241C8FBA00EFFCB6 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 36 | 6455811E241C8FBA00EFFCB6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 64558120241C8FBA00EFFCB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 64558125241C8FBA00EFFCB6 /* SightLogoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SightLogoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 64558129241C8FBA00EFFCB6 /* SightLogoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SightLogoTests.swift; sourceTree = ""; }; 40 | 6455812B241C8FBA00EFFCB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 64558134241C8FEB00EFFCB6 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 42 | 64558136241C901000EFFCB6 /* LogoSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoSettingsView.swift; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 6455810C241C8FB800EFFCB6 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | 64558122241C8FBA00EFFCB6 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXFrameworksBuildPhase section */ 61 | 62 | /* Begin PBXGroup section */ 63 | 64558106241C8FB800EFFCB6 = { 64 | isa = PBXGroup; 65 | children = ( 66 | 64558111241C8FB800EFFCB6 /* SightLogo */, 67 | 64558128241C8FBA00EFFCB6 /* SightLogoTests */, 68 | 64558110241C8FB800EFFCB6 /* Products */, 69 | ); 70 | sourceTree = ""; 71 | }; 72 | 64558110241C8FB800EFFCB6 /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 6455810F241C8FB800EFFCB6 /* SightLogo.app */, 76 | 64558125241C8FBA00EFFCB6 /* SightLogoTests.xctest */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | 64558111241C8FB800EFFCB6 /* SightLogo */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 64558112241C8FB800EFFCB6 /* AppDelegate.swift */, 85 | 64558114241C8FB800EFFCB6 /* SceneDelegate.swift */, 86 | 64558134241C8FEB00EFFCB6 /* HomeView.swift */, 87 | 64558136241C901000EFFCB6 /* LogoSettingsView.swift */, 88 | 64558118241C8FBA00EFFCB6 /* Assets.xcassets */, 89 | 6455811D241C8FBA00EFFCB6 /* LaunchScreen.storyboard */, 90 | 64558120241C8FBA00EFFCB6 /* Info.plist */, 91 | 6455811A241C8FBA00EFFCB6 /* Preview Content */, 92 | ); 93 | path = SightLogo; 94 | sourceTree = ""; 95 | }; 96 | 6455811A241C8FBA00EFFCB6 /* Preview Content */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 6455811B241C8FBA00EFFCB6 /* Preview Assets.xcassets */, 100 | ); 101 | path = "Preview Content"; 102 | sourceTree = ""; 103 | }; 104 | 64558128241C8FBA00EFFCB6 /* SightLogoTests */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 64558129241C8FBA00EFFCB6 /* SightLogoTests.swift */, 108 | 6455812B241C8FBA00EFFCB6 /* Info.plist */, 109 | ); 110 | path = SightLogoTests; 111 | sourceTree = ""; 112 | }; 113 | /* End PBXGroup section */ 114 | 115 | /* Begin PBXNativeTarget section */ 116 | 6455810E241C8FB800EFFCB6 /* SightLogo */ = { 117 | isa = PBXNativeTarget; 118 | buildConfigurationList = 6455812E241C8FBB00EFFCB6 /* Build configuration list for PBXNativeTarget "SightLogo" */; 119 | buildPhases = ( 120 | 6455810B241C8FB800EFFCB6 /* Sources */, 121 | 6455810C241C8FB800EFFCB6 /* Frameworks */, 122 | 6455810D241C8FB800EFFCB6 /* Resources */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = SightLogo; 129 | productName = SightLogo; 130 | productReference = 6455810F241C8FB800EFFCB6 /* SightLogo.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | 64558124241C8FBA00EFFCB6 /* SightLogoTests */ = { 134 | isa = PBXNativeTarget; 135 | buildConfigurationList = 64558131241C8FBB00EFFCB6 /* Build configuration list for PBXNativeTarget "SightLogoTests" */; 136 | buildPhases = ( 137 | 64558121241C8FBA00EFFCB6 /* Sources */, 138 | 64558122241C8FBA00EFFCB6 /* Frameworks */, 139 | 64558123241C8FBA00EFFCB6 /* Resources */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | 64558127241C8FBA00EFFCB6 /* PBXTargetDependency */, 145 | ); 146 | name = SightLogoTests; 147 | productName = SightLogoTests; 148 | productReference = 64558125241C8FBA00EFFCB6 /* SightLogoTests.xctest */; 149 | productType = "com.apple.product-type.bundle.unit-test"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | 64558107241C8FB800EFFCB6 /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastSwiftUpdateCheck = 1130; 158 | LastUpgradeCheck = 1130; 159 | ORGANIZATIONNAME = "Federico Zanetello"; 160 | TargetAttributes = { 161 | 6455810E241C8FB800EFFCB6 = { 162 | CreatedOnToolsVersion = 11.3.1; 163 | }; 164 | 64558124241C8FBA00EFFCB6 = { 165 | CreatedOnToolsVersion = 11.3.1; 166 | TestTargetID = 6455810E241C8FB800EFFCB6; 167 | }; 168 | }; 169 | }; 170 | buildConfigurationList = 6455810A241C8FB800EFFCB6 /* Build configuration list for PBXProject "SightLogo" */; 171 | compatibilityVersion = "Xcode 9.3"; 172 | developmentRegion = en; 173 | hasScannedForEncodings = 0; 174 | knownRegions = ( 175 | en, 176 | Base, 177 | ); 178 | mainGroup = 64558106241C8FB800EFFCB6; 179 | productRefGroup = 64558110241C8FB800EFFCB6 /* Products */; 180 | projectDirPath = ""; 181 | projectRoot = ""; 182 | targets = ( 183 | 6455810E241C8FB800EFFCB6 /* SightLogo */, 184 | 64558124241C8FBA00EFFCB6 /* SightLogoTests */, 185 | ); 186 | }; 187 | /* End PBXProject section */ 188 | 189 | /* Begin PBXResourcesBuildPhase section */ 190 | 6455810D241C8FB800EFFCB6 /* Resources */ = { 191 | isa = PBXResourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | 6455811F241C8FBA00EFFCB6 /* LaunchScreen.storyboard in Resources */, 195 | 6455811C241C8FBA00EFFCB6 /* Preview Assets.xcassets in Resources */, 196 | 64558119241C8FBA00EFFCB6 /* Assets.xcassets in Resources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | 64558123241C8FBA00EFFCB6 /* Resources */ = { 201 | isa = PBXResourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | }; 207 | /* End PBXResourcesBuildPhase section */ 208 | 209 | /* Begin PBXSourcesBuildPhase section */ 210 | 6455810B241C8FB800EFFCB6 /* Sources */ = { 211 | isa = PBXSourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | 64558135241C8FEB00EFFCB6 /* HomeView.swift in Sources */, 215 | 64558113241C8FB800EFFCB6 /* AppDelegate.swift in Sources */, 216 | 64558137241C901000EFFCB6 /* LogoSettingsView.swift in Sources */, 217 | 64558115241C8FB800EFFCB6 /* SceneDelegate.swift in Sources */, 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | 64558121241C8FBA00EFFCB6 /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | 6455812A241C8FBA00EFFCB6 /* SightLogoTests.swift in Sources */, 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | /* End PBXSourcesBuildPhase section */ 230 | 231 | /* Begin PBXTargetDependency section */ 232 | 64558127241C8FBA00EFFCB6 /* PBXTargetDependency */ = { 233 | isa = PBXTargetDependency; 234 | target = 6455810E241C8FB800EFFCB6 /* SightLogo */; 235 | targetProxy = 64558126241C8FBA00EFFCB6 /* PBXContainerItemProxy */; 236 | }; 237 | /* End PBXTargetDependency section */ 238 | 239 | /* Begin PBXVariantGroup section */ 240 | 6455811D241C8FBA00EFFCB6 /* LaunchScreen.storyboard */ = { 241 | isa = PBXVariantGroup; 242 | children = ( 243 | 6455811E241C8FBA00EFFCB6 /* Base */, 244 | ); 245 | name = LaunchScreen.storyboard; 246 | sourceTree = ""; 247 | }; 248 | /* End PBXVariantGroup section */ 249 | 250 | /* Begin XCBuildConfiguration section */ 251 | 6455812C241C8FBB00EFFCB6 /* Debug */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_ENABLE_OBJC_WEAK = YES; 262 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_COMMA = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 267 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 268 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 275 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | COPY_PHASE_STRIP = NO; 284 | DEBUG_INFORMATION_FORMAT = dwarf; 285 | ENABLE_STRICT_OBJC_MSGSEND = YES; 286 | ENABLE_TESTABILITY = YES; 287 | GCC_C_LANGUAGE_STANDARD = gnu11; 288 | GCC_DYNAMIC_NO_PIC = NO; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_OPTIMIZATION_LEVEL = 0; 291 | GCC_PREPROCESSOR_DEFINITIONS = ( 292 | "DEBUG=1", 293 | "$(inherited)", 294 | ); 295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 297 | GCC_WARN_UNDECLARED_SELECTOR = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 299 | GCC_WARN_UNUSED_FUNCTION = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 302 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 303 | MTL_FAST_MATH = YES; 304 | ONLY_ACTIVE_ARCH = YES; 305 | SDKROOT = iphoneos; 306 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 307 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 308 | }; 309 | name = Debug; 310 | }; 311 | 6455812D241C8FBB00EFFCB6 /* Release */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ALWAYS_SEARCH_USER_PATHS = NO; 315 | CLANG_ANALYZER_NONNULL = YES; 316 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 317 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 318 | CLANG_CXX_LIBRARY = "libc++"; 319 | CLANG_ENABLE_MODULES = YES; 320 | CLANG_ENABLE_OBJC_ARC = YES; 321 | CLANG_ENABLE_OBJC_WEAK = YES; 322 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 323 | CLANG_WARN_BOOL_CONVERSION = YES; 324 | CLANG_WARN_COMMA = YES; 325 | CLANG_WARN_CONSTANT_CONVERSION = YES; 326 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 327 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 328 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 329 | CLANG_WARN_EMPTY_BODY = YES; 330 | CLANG_WARN_ENUM_CONVERSION = YES; 331 | CLANG_WARN_INFINITE_RECURSION = YES; 332 | CLANG_WARN_INT_CONVERSION = YES; 333 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 334 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 335 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 338 | CLANG_WARN_STRICT_PROTOTYPES = YES; 339 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 340 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | COPY_PHASE_STRIP = NO; 344 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 345 | ENABLE_NS_ASSERTIONS = NO; 346 | ENABLE_STRICT_OBJC_MSGSEND = YES; 347 | GCC_C_LANGUAGE_STANDARD = gnu11; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 351 | GCC_WARN_UNDECLARED_SELECTOR = YES; 352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 353 | GCC_WARN_UNUSED_FUNCTION = YES; 354 | GCC_WARN_UNUSED_VARIABLE = YES; 355 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 356 | MTL_ENABLE_DEBUG_INFO = NO; 357 | MTL_FAST_MATH = YES; 358 | SDKROOT = iphoneos; 359 | SWIFT_COMPILATION_MODE = wholemodule; 360 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 361 | VALIDATE_PRODUCT = YES; 362 | }; 363 | name = Release; 364 | }; 365 | 6455812F241C8FBB00EFFCB6 /* Debug */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 369 | CODE_SIGN_STYLE = Automatic; 370 | DEVELOPMENT_ASSET_PATHS = "\"SightLogo/Preview Content\""; 371 | DEVELOPMENT_TEAM = 5HP4VS26CA; 372 | ENABLE_PREVIEWS = YES; 373 | INFOPLIST_FILE = SightLogo/Info.plist; 374 | LD_RUNPATH_SEARCH_PATHS = ( 375 | "$(inherited)", 376 | "@executable_path/Frameworks", 377 | ); 378 | PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.SightLogo; 379 | PRODUCT_NAME = "$(TARGET_NAME)"; 380 | SWIFT_VERSION = 5.0; 381 | TARGETED_DEVICE_FAMILY = "1,2"; 382 | }; 383 | name = Debug; 384 | }; 385 | 64558130241C8FBB00EFFCB6 /* Release */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 389 | CODE_SIGN_STYLE = Automatic; 390 | DEVELOPMENT_ASSET_PATHS = "\"SightLogo/Preview Content\""; 391 | DEVELOPMENT_TEAM = 5HP4VS26CA; 392 | ENABLE_PREVIEWS = YES; 393 | INFOPLIST_FILE = SightLogo/Info.plist; 394 | LD_RUNPATH_SEARCH_PATHS = ( 395 | "$(inherited)", 396 | "@executable_path/Frameworks", 397 | ); 398 | PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.SightLogo; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | SWIFT_VERSION = 5.0; 401 | TARGETED_DEVICE_FAMILY = "1,2"; 402 | }; 403 | name = Release; 404 | }; 405 | 64558132241C8FBB00EFFCB6 /* Debug */ = { 406 | isa = XCBuildConfiguration; 407 | buildSettings = { 408 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 409 | BUNDLE_LOADER = "$(TEST_HOST)"; 410 | CODE_SIGN_STYLE = Automatic; 411 | DEVELOPMENT_TEAM = 5HP4VS26CA; 412 | INFOPLIST_FILE = SightLogoTests/Info.plist; 413 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 414 | LD_RUNPATH_SEARCH_PATHS = ( 415 | "$(inherited)", 416 | "@executable_path/Frameworks", 417 | "@loader_path/Frameworks", 418 | ); 419 | PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.SightLogoTests; 420 | PRODUCT_NAME = "$(TARGET_NAME)"; 421 | SWIFT_VERSION = 5.0; 422 | TARGETED_DEVICE_FAMILY = "1,2"; 423 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SightLogo.app/SightLogo"; 424 | }; 425 | name = Debug; 426 | }; 427 | 64558133241C8FBB00EFFCB6 /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 431 | BUNDLE_LOADER = "$(TEST_HOST)"; 432 | CODE_SIGN_STYLE = Automatic; 433 | DEVELOPMENT_TEAM = 5HP4VS26CA; 434 | INFOPLIST_FILE = SightLogoTests/Info.plist; 435 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 436 | LD_RUNPATH_SEARCH_PATHS = ( 437 | "$(inherited)", 438 | "@executable_path/Frameworks", 439 | "@loader_path/Frameworks", 440 | ); 441 | PRODUCT_BUNDLE_IDENTIFIER = blog.fivestars.SightLogoTests; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | SWIFT_VERSION = 5.0; 444 | TARGETED_DEVICE_FAMILY = "1,2"; 445 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SightLogo.app/SightLogo"; 446 | }; 447 | name = Release; 448 | }; 449 | /* End XCBuildConfiguration section */ 450 | 451 | /* Begin XCConfigurationList section */ 452 | 6455810A241C8FB800EFFCB6 /* Build configuration list for PBXProject "SightLogo" */ = { 453 | isa = XCConfigurationList; 454 | buildConfigurations = ( 455 | 6455812C241C8FBB00EFFCB6 /* Debug */, 456 | 6455812D241C8FBB00EFFCB6 /* Release */, 457 | ); 458 | defaultConfigurationIsVisible = 0; 459 | defaultConfigurationName = Release; 460 | }; 461 | 6455812E241C8FBB00EFFCB6 /* Build configuration list for PBXNativeTarget "SightLogo" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 6455812F241C8FBB00EFFCB6 /* Debug */, 465 | 64558130241C8FBB00EFFCB6 /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | 64558131241C8FBB00EFFCB6 /* Build configuration list for PBXNativeTarget "SightLogoTests" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | 64558132241C8FBB00EFFCB6 /* Debug */, 474 | 64558133241C8FBB00EFFCB6 /* Release */, 475 | ); 476 | defaultConfigurationIsVisible = 0; 477 | defaultConfigurationName = Release; 478 | }; 479 | /* End XCConfigurationList section */ 480 | }; 481 | rootObject = 64558107241C8FB800EFFCB6 /* Project object */; 482 | } 483 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SightLogo 4 | // 5 | // Created by Federico Zanetello on 3/14/20. 6 | // Copyright © 2020 Federico Zanetello. 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 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/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 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/HomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import SwiftUI 4 | 5 | struct HomeView: View { 6 | @State private var strokeWidth: CGFloat = 16 7 | @State private var innerCircleRadius: CGFloat = 16 8 | @State private var rotationNumber: Int = 3 9 | @State private var angle: Double = 90 10 | var fontSize: CGFloat = 150 11 | 12 | var body: some View { 13 | // NavigationView { 14 | HStack { 15 | // NavigationLink( 16 | // destination: LogoSettingsView( 17 | // strokeWidth: $strokeWidth, 18 | // innerCircleRadius: $innerCircleRadius, 19 | // rotationNumber: $rotationNumber, 20 | // angle: $angle)) { 21 | Text("___") 22 | .font(Font.system(size: self.fontSize, weight: .heavy, design: .default)) 23 | .hidden() 24 | .overlay( 25 | LogoView(strokeWidth: self.strokeWidth, rotationCount: self.rotationNumber, angle: self.angle, innerRadius: self.innerCircleRadius), 26 | alignment: .trailing) 27 | // } 28 | 29 | Text("SIGHT") 30 | .font(Font.system(size: self.fontSize, weight: .heavy, design: .default)) 31 | // } 32 | } 33 | } 34 | } 35 | 36 | struct HomeView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | HomeView() 39 | .previewLayout(.fixed(width: 1000, height: 200)) 40 | .padding() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/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 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/LogoSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import SwiftUI 4 | 5 | struct SightComponent: Shape { 6 | let angle: Angle 7 | 8 | func path(in rect: CGRect) -> Path { 9 | Path { path in 10 | let radius: CGFloat = min(rect.size.width, rect.size.height) / 2 11 | 12 | path.addArc( 13 | center: CGPoint(x: radius, y: radius), 14 | radius: radius, 15 | startAngle: angle / 2, 16 | endAngle: -angle / 2, 17 | clockwise: true, 18 | transform: .identity 19 | ) 20 | 21 | path.move(to: CGPoint(x: radius * 1.8, y: radius)) 22 | path.addLine(to: CGPoint(x: radius * 2.2, y: radius)) 23 | } 24 | } 25 | } 26 | 27 | struct SightView: View { 28 | let strokeWidth: CGFloat 29 | let rotationCount: Int 30 | let angle: Double 31 | let innerRadius: CGFloat 32 | 33 | var body: some View { 34 | ZStack { 35 | ForEach(0.. RadialGradient { 59 | RadialGradient( 60 | gradient: Gradient(colors: [.red, .orange]), 61 | center: UnitPoint(x: 0.25, y: 0.25), 62 | startRadius: 0, 63 | endRadius: geometry.size.width 64 | ) 65 | } 66 | 67 | var body: some View { 68 | ZStack { 69 | sightView 70 | .hidden() 71 | GeometryReader { geometry in 72 | self.gradient(for: geometry) 73 | .mask(self.sightView) 74 | } 75 | } 76 | } 77 | } 78 | 79 | struct LogoSettingsView: View { 80 | @Binding var strokeWidth: CGFloat 81 | @Binding var innerCircleRadius: CGFloat 82 | @Binding var rotationNumber: Int 83 | @Binding var angle: Double 84 | 85 | var body: some View { 86 | List { 87 | Section { 88 | LogoView(strokeWidth: self.strokeWidth, rotationCount: self.rotationNumber, angle: self.angle, innerRadius: self.innerCircleRadius) 89 | } 90 | 91 | Section(header: Text("Stroke Width: ")) { 92 | Slider(value: $strokeWidth, in: 0...100, step: 0.1) 93 | } 94 | 95 | Section(header: Text("Inner Circle Radius: ")) { 96 | Slider(value: $innerCircleRadius, in: 0...100, step: 0.1) 97 | } 98 | 99 | Section { 100 | Stepper("Rotations: ", value: $rotationNumber) 101 | } 102 | 103 | Section(header: Text("Arc Angle: ")) { 104 | Slider(value: $angle, in: 10...120, step: 1) 105 | } 106 | } 107 | .listStyle(GroupedListStyle()) 108 | .environment(\.horizontalSizeClass, .regular) 109 | } 110 | } 111 | 112 | struct ContentView_Previews: PreviewProvider { 113 | 114 | static var previews: some View { 115 | LogoSettingsView( 116 | strokeWidth: .constant(16), 117 | innerCircleRadius: .constant(20), 118 | rotationNumber: .constant(3), 119 | angle: .constant(90)) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SightLogo 4 | // 5 | // Created by Federico Zanetello on 3/14/20. 6 | // Copyright © 2020 Federico Zanetello. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = HomeView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogoTests/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 | 22 | 23 | -------------------------------------------------------------------------------- /.logo/SightLogo/SightLogoTests/SightLogoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SightLogoTests.swift 3 | // SightLogoTests 4 | // 5 | // Created by Federico Zanetello on 3/14/20. 6 | // Copyright © 2020 Federico Zanetello. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SightLogo 11 | 12 | class SightLogoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /.logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonnam/Sight/40f98f5a0d6d3e3dd7f9e215418a014a9cf39d10/.logo/logo.png -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: # paths to include during linting. `--path` is ignored if present. 2 | - Sources 3 | - Tests -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Federico Zanetello 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Sight", 8 | platforms: [ 9 | .iOS(.v10), 10 | .macOS(.v10_12), 11 | .tvOS(.v10) 12 | ], 13 | products: [ 14 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 15 | .library( 16 | name: "Sight", 17 | targets: ["Sight"]), 18 | ], 19 | dependencies: [ 20 | // Dependencies declare other packages that this package depends on. 21 | // .package(url: /* package url */, from: "1.0.0"), 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 26 | .target( 27 | name: "Sight", 28 | dependencies: []), 29 | .testTarget( 30 | name: "SightTests", 31 | dependencies: ["Sight"]), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Sight 3 |

4 | 5 |

6 | 7 | 8 | Build status 9 | 10 | 11 | Swift Package Manager 12 | 13 | MacOS + iOS + iPadOS + tvOS 14 | 15 | Twitter: @zntfdr 16 | 17 |

18 | 19 | Welcome to **Sight**, a Swift spatial search μlibrary. Its primary goal is to extend Apple's [`GameplayKit`](https://developer.apple.com/documentation/gameplaykit) framework. 20 | 21 | ## Usage 22 | 23 | Sight helps you quickly determine which object is closest to a given position. 24 | 25 | This is done via a `Region` instance, which represents the space where all the objects are positioned in. 26 | 27 | ```swift 28 | import Sight 29 | 30 | // Define a region with bounds and search radius. 31 | let region = Region( 32 | minBounds: SIMD2(x: 0, y: 0), 33 | maxBounds: SIMD2(x: 1, y: 1), 34 | searchRadius: 0.3 35 | ) 36 | 37 | // Place objects in the region. 38 | region.add("A", at: SIMD2(x: 0, y: 0.1)) 39 | region.add("B", at: SIMD2(x: 0.5, y: 0)) 40 | region.add("C", at: SIMD2(x: 0, y: 0.8)) 41 | 42 | // Find the closest object to any position. 43 | region.closestValue(to: SIMD2(x: 0.5, y: 0.2)) // "B" 44 | region.closestValue(to: SIMD2(x: 1, y: 0)) // nil 45 | ``` 46 | 47 | `Region` is optimized for spatial searches, this is why during its initialization both search radius and its bounds are required. 48 | 49 | You can find many more examples in the [`Tests`](https://github.com/zntfdr/Sight/tree/master/Tests) folder. 50 | 51 | ## Installation 52 | 53 | Sight is distributed using the [Swift Package Manager](https://swift.org/package-manager). To install it into a project, follow [this tutorial](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app) and use this repository URL: `https://github.com/zntfdr/Sight.git`. 54 | 55 | ## Credits 56 | 57 | Sight was built by [Federico Zanetello](https://twitter.com/zntfdr) as a component of [Bangkok Metro](http://yourmetro.app). 58 | 59 | If you'd like to know more on how this library works, please refer to `GameplayKit`'s [`GKQuadtree` overview](https://developer.apple.com/documentation/gameplaykit/gkquadtree). 60 | 61 | ## Contributions and Support 62 | 63 | All users are welcome and encouraged to become active participants in the project continued development — by fixing any bug that they encounter, or by improving the documentation wherever it’s found to be lacking. 64 | 65 | If you'd like to make a change, please [open a Pull Request](https://github.com/zntfdr/Sight/pull/new), even if it just contains a draft of the changes you’re planning, or a test that reproduces an issue. 66 | 67 | Thank you and please enjoy using **Sight**! 68 | -------------------------------------------------------------------------------- /Sources/Sight/Element.swift: -------------------------------------------------------------------------------- 1 | import ObjectiveC 2 | 3 | /// A `Region` element. 4 | /// 5 | /// Represents an element in the region. 6 | final class Element: NSObject { 7 | /// Value associated with the element. 8 | let value: T 9 | 10 | /// Element's `Region` coordinate. 11 | let position: SIMD2 12 | 13 | /// Initializes a new `Element` instances with the specified parameters. 14 | /// 15 | /// - Parameters: 16 | /// - value: A value associated with this element. 17 | /// - position: The coordinate of this element in the region. 18 | init(value: T, position: SIMD2) { 19 | self.value = value 20 | self.position = position 21 | super.init() 22 | } 23 | 24 | /// Computes the distance between this instance and the specified coordinate. 25 | /// 26 | /// - Parameter position: Coordinate of the location we would like to compute 27 | /// the distance of. 28 | func distance(from position: SIMD2) -> Float { 29 | (self.position - position).length() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Sight/Region.swift: -------------------------------------------------------------------------------- 1 | import GameplayKit 2 | 3 | /// Space where we values are placed. 4 | public struct Region { 5 | /// GameplayKit data structure used to organize objects based on their 6 | /// locations in a two-dimensional space. 7 | /// 8 | /// - SeeAlso: [Apple Documentation](https://developer.apple.com/documentation/gameplaykit/gkquadtree) 9 | let tree: GKQuadtree> 10 | 11 | /// The spatial area radius of interest. 12 | let searchRadius: Float 13 | 14 | /// Initializes a new `Region` instance. 15 | /// 16 | /// - Parameters: 17 | /// - minBounds: The minimun position bound. 18 | /// - maxBounds: The maximum position bound. 19 | /// - searchRadius: The search radius of the area of interest. 20 | /// - minimumCellSize: The inner Quadtree minimumCellSize. This is your 21 | /// leverage on the library perfomance/memory-usage. 22 | public init( 23 | minBounds: SIMD2, 24 | maxBounds: SIMD2, 25 | searchRadius: Float, 26 | minimumCellSize: Float = 1 27 | ) { 28 | let quad = GKQuad(quadMin: minBounds, quadMax: maxBounds) 29 | self.init( 30 | boundingQuad: quad, 31 | searchRadius: searchRadius, 32 | minimumCellSize: minimumCellSize 33 | ) 34 | } 35 | 36 | /// Initializes a new `Region` instance. 37 | /// 38 | /// - SeeAlso: `init(quadMin:maxBounds:searchRadius:)`. 39 | init(boundingQuad quad: GKQuad, searchRadius: Float, minimumCellSize: Float) { 40 | self.searchRadius = searchRadius 41 | tree = GKQuadtree(boundingQuad: quad, minimumCellSize: minimumCellSize) 42 | } 43 | 44 | /// Adds the value at the specified position. 45 | /// 46 | /// If multiple values are placed at the same position, only one of them will 47 | /// be returned during a query (which one is random). 48 | /// 49 | /// - Parameters: 50 | /// - value: The value to be added in the region. 51 | /// - position: The coordinate of the new value in the region. 52 | public func add(_ value: T, at position: SIMD2) { 53 | let element = Element(value: value, position: position) 54 | tree.add(element, at: element.position) 55 | } 56 | 57 | /// Returns all elements whose corresponding positions overlap the square 58 | /// region with center on the specified `position`, and `searchRadius * 2` 59 | /// edge length. 60 | /// 61 | /// - Parameter position: The center of the region we're interest in. 62 | func elements(at position: SIMD2) -> [Element] { 63 | let quadMin = position - searchRadius 64 | let quadMax = position + searchRadius 65 | let quad = GKQuad(quadMin: quadMin, quadMax: quadMax) 66 | return elements(in: quad) 67 | } 68 | 69 | /// Returns all elements whose corresponding positions overlap the specified 70 | /// region. 71 | /// 72 | /// - Parameter quad: The axis-aligned rectangle in 2D space to search. 73 | func elements(in quad: GKQuad) -> [Element] { 74 | tree.elements(in: quad) 75 | } 76 | 77 | /// Returns the value whose position is closest to the specified position 78 | /// (if any). 79 | /// 80 | /// A value will be returned only if its region (specified with `minimumCellSize`) 81 | /// overlaps the square region centered in `position` and with 82 | /// `minimumCellSize` dimension. 83 | public func closestValue(to position: SIMD2) -> T? { 84 | if 85 | let closestElement: Element = elements(at: position) 86 | .min(by: { $0.distance(from: position) < $1.distance(from: position) }), 87 | closestElement.distance(from: position) <= searchRadius { 88 | return closestElement.value 89 | } 90 | return nil 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Sight/SIMD2.swift: -------------------------------------------------------------------------------- 1 | import Darwin 2 | 3 | extension SIMD2 where SIMD2.Scalar: FloatingPoint { 4 | /// Returns the vector length. 5 | func length() -> Scalar { 6 | sqrt((self * self).sum()) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/SightTests/Element/ElementTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Sight 2 | import XCTest 3 | 4 | final class ElementTests: XCTestCase { 5 | func testInstanceValue() { 6 | for value in 1...10 { 7 | let element = Element(value: value, position: .zero) 8 | XCTAssertEqual(element.value, value) 9 | } 10 | } 11 | 12 | func testInstancePosition() { 13 | // Verify all possible combinations of positive/negative values. 14 | let positions: [SIMD2] = [ 15 | .zero, 16 | .init(x: 0, y: 1), 17 | .one, 18 | .init(x: 1, y: 0), 19 | .init(x: 1, y: -1), 20 | .init(x: 0, y: -1), 21 | .init(x: -1, y: -1), 22 | .init(x: -1, y: 0), 23 | .init(x: -1, y: 1) 24 | ] 25 | 26 | for position in positions { 27 | let element = Element(value: (), position: position) 28 | XCTAssertEqual(element.position, position) 29 | } 30 | } 31 | 32 | func testInstanceDistance() { 33 | let position = SIMD2(x: 0, y: 1) 34 | 35 | for distance in 0...10 { 36 | let element = Element(value: (), position: .zero) 37 | let position = position * Float(distance) 38 | let distanceFromElement = element.distance(from: position) 39 | XCTAssertEqual(distanceFromElement, Float(distance)) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/SightTests/Region/RegionTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Darwin 3 | import Sight 4 | import XCTest 5 | 6 | final class RegionTests: XCTestCase { 7 | func testInstanceNoValue() { 8 | let region = Region(minBounds: .zero, maxBounds: .one, searchRadius: 10) 9 | XCTAssertNil(region.closestValue(to: .zero)) 10 | XCTAssertNil(region.closestValue(to: .one)) 11 | XCTAssertNil(region.closestValue(to: .zero / 2)) 12 | } 13 | 14 | func testOneValue() { 15 | let region = Region(minBounds: .zero, maxBounds: .one, searchRadius: 10) 16 | let value = 1 17 | let valuePosition = SIMD2.one / 2 18 | 19 | region.add(value, at: valuePosition) 20 | 21 | XCTAssertEqual(value, region.closestValue(to: valuePosition)) 22 | } 23 | 24 | func testFindValueOnEdge() { 25 | let searchRadius: Float = 0.5 26 | let region = Region(minBounds: .zero, maxBounds: .one * 10, searchRadius: searchRadius) 27 | let value = 1 28 | let valuePosition = SIMD2(x: 1, y: 0) * 10 29 | 30 | region.add(value, at: valuePosition) 31 | 32 | // Just out of area position. 33 | let outOfAreaSearchPosition = valuePosition - SIMD2(x: 1, y: 0) * searchRadius - 0.00001 34 | XCTAssertNil(region.closestValue(to: outOfAreaSearchPosition)) 35 | 36 | // On the edge position. 37 | let edgeSearchPosition = valuePosition - SIMD2(x: 1, y: 0) * searchRadius 38 | XCTAssertNotNil(region.closestValue(to: edgeSearchPosition)) 39 | } 40 | 41 | func testTwoValuesCompete() { 42 | let region = Region(minBounds: .zero, maxBounds: .one, searchRadius: 1) 43 | let zeroValue = 0 44 | let oneValue = 1 45 | let oneValuePosition = SIMD2(x: 1, y: 0) 46 | region.add(zeroValue, at: .zero) 47 | region.add(oneValue, at: oneValuePosition) 48 | 49 | // From zero to halfway we should get `zeroValue`. 50 | for step in 0...5 { 51 | let position = SIMD2(x: 0.1 * Float(step), y: 0) 52 | XCTAssertEqual(region.closestValue(to: position), zeroValue) 53 | } 54 | 55 | // From halfway till the end we should get `oneValue`. 56 | for step in 6...10 { 57 | let position = SIMD2(x: 0.1 * Float(step), y: 0) 58 | XCTAssertEqual(region.closestValue(to: position), oneValue) 59 | } 60 | } 61 | 62 | func testOutOfBounds() { 63 | let region = Region(minBounds: .zero, maxBounds: .one, searchRadius: 0.1) 64 | region.add((), at: .zero) 65 | 66 | for step in 2...10 { 67 | let position = SIMD2(x: -0.1 * Float(step), y: 0) 68 | XCTAssertNil(region.closestValue(to: position), "fail for \(step)") 69 | } 70 | 71 | let position = SIMD2(x: -0.1, y: 0) 72 | XCTAssertNotNil(region.closestValue(to: position)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Tests/SightTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SightTests.allTests) 7 | ] 8 | } 9 | #endif 10 | --------------------------------------------------------------------------------