diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 000000000..ac65bda96 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,6 @@ +#!/bin/bash + +# Will download all dependencies if not already existing +swift package plugin --list + +./.tools/git-format-staged --formatter ".build/checkouts/SwiftFormat/CommandLineTool/swiftformat stdin --stdinpath '{}'" "*.swift" \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..496116258 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,10 @@ +### Description + + + +### Open Tasks + +- [ ] + +### Infos for Reviewer + diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index fa9170d27..dff8c5df7 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -1,4 +1,4 @@ -name: iOS starter workflow +name: iOS on: push: @@ -7,22 +7,36 @@ on: pull_request: jobs: - build: - name: Build and Test using Xcode 15.3 on iPhone 15 Pro + format: + name: Code Style runs-on: macos-14 + steps: + - name: Install tools + run: brew install swiftlint + + - name: Checkout + uses: actions/checkout@v4 + + - name: Check format + run: swiftlint lint . --reporter github-actions-logging + test: + name: Build and Test + runs-on: macos-14 steps: - - name: Configure Xcode 15.3 - run: | - sudo xcode-select -s /Applications/Xcode_15.3.app - - name: Print Xcode Version - run: | - xcode-select --print-path + - name: Install tools + run: brew install xcbeautify + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Build - run: | - set -o pipefail && xcodebuild build -scheme maplibre-navigation-ios -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' | xcbeautify --renderer github-actions + run: set -o pipefail && xcodebuild build -scheme maplibre-navigation-ios -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' | xcbeautify --renderer github-actions + - name: Test - run: | - set -o pipefail && xcodebuild test -scheme maplibre-navigation-ios -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' | xcbeautify --renderer github-actions \ No newline at end of file + run: set -o pipefail && xcodebuild test -scheme maplibre-navigation-ios -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' | xcbeautify --renderer github-actions \ No newline at end of file diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 000000000..bbbddafa0 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,85 @@ +--acronyms ID,URL,UUID +--allman false +--anonymousforeach convert +--assetliterals visual-width +--asynccapturing +--beforemarks +--binarygrouping none +--categorymark "MARK: %c" +--classthreshold 0 +--closingparen balanced +--closurevoid remove +--commas inline +--conflictmarkers reject +--decimalgrouping 3,6 +--doccomments before-declarations +--elseposition same-line +--emptybraces no-space +--enumnamespaces always +--enumthreshold 0 +--exponentcase lowercase +--exponentgrouping disabled +--extensionacl on-extension +--extensionlength 0 +--extensionmark "MARK: - %t + %c" +--fractiongrouping disabled +--fragment false +--funcattributes preserve +--generictypes +--groupedextension "MARK: %c" +--guardelse auto +--header ignore +--hexgrouping none +--hexliteralcase uppercase +--ifdef no-indent +--importgrouping alpha +--indent 4 +--indentcase false +--indentstrings false +--lifecycle +--lineaftermarks true +--linebreaks lf +--markcategories true +--markextensions always +--marktypes always +--maxwidth none +--modifierorder +--nevertrailing +--nospaceoperators +--nowrapoperators +--octalgrouping none +--onelineforeach ignore +--operatorfunc spaced +--organizetypes actor,class,enum,struct +--patternlet hoist +--ranges spaced +--redundanttype infer-locals-only +--self insert +--selfrequired +--semicolons inline +--shortoptionals except-properties +--smarttabs enabled +--someany true +--stripunusedargs closure-only +--structthreshold 0 +--tabwidth unspecified +--throwcapturing +--trailingclosures +--trimwhitespace nonblank-lines +--typeattributes preserve +--typeblanklines remove +--typemark "MARK: - %t" +--varattributes preserve +--voidtype void +--wraparguments disabled +--wrapcollections preserve +--wrapconditions preserve +--wrapeffects preserve +--wrapenumcases always +--wrapparameters after-first +--wrapreturntype preserve +--wrapternary default +--wraptypealiases preserve +--xcodeindentation disabled +--yodaswap always +--disable wrapMultilineStatementBraces diff --git a/.swiftlint.yml b/.swiftlint.yml index 4fd33f313..926c619ed 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,8 +1,30 @@ disabled_rules: - trailing_whitespace - private_over_fileprivate + - line_length + - force_cast + - function_body_length + - identifier_name + - cyclomatic_complexity + - type_body_length + - file_length + - force_try excluded: # paths to ignore during linting. Takes precedence over `included`. - Carthage - MapboxCoreNavigationTests - MapboxNavigationTests - scripts + - .build + +identifier_name: + max_length: + warning: 60 + error: 100 + +nesting: + type_level: + warning: 3 + error: 6 + function_level: + warning: 5 + error: 10 \ No newline at end of file diff --git a/.tools/git-format-staged b/.tools/git-format-staged new file mode 100755 index 000000000..4fe225b60 --- /dev/null +++ b/.tools/git-format-staged @@ -0,0 +1,279 @@ +#!/usr/bin/env python3 +# +# Git command to transform staged files according to a command that accepts file +# content on stdin and produces output on stdout. This command is useful in +# combination with `git add -p` which allows you to stage specific changes in +# a file. This command runs a formatter on the file with staged changes while +# ignoring unstaged changes. +# +# Usage: git-format-staged [OPTION]... [FILE]... +# Example: git-format-staged --formatter 'prettier --stdin-filepath "{}"' '*.js' +# +# Tested with Python 3.10 and Python 2.7. +# +# Original author: Jesse Hallett + +from __future__ import print_function +import argparse +from fnmatch import fnmatch +from gettext import gettext as _ +import os +import re +import subprocess +import sys + +# The string $VERSION is replaced during the publish process. +VERSION = '$VERSION' +PROG = sys.argv[0] + +def info(msg): + print(msg, file=sys.stdout) + +def warn(msg): + print('{}: warning: {}'.format(PROG, msg), file=sys.stderr) + +def fatal(msg): + print('{}: error: {}'.format(PROG, msg), file=sys.stderr) + exit(1) + +def format_staged_files(file_patterns, formatter, git_root, update_working_tree=True, write=True, verbose=False): + try: + output = subprocess.check_output([ + 'git', 'diff-index', + '--cached', + '--diff-filter=AM', # select only file additions and modifications + '--no-renames', + 'HEAD' + ]) + for line in output.splitlines(): + entry = parse_diff(line.decode('utf-8')) + entry_path = normalize_path(entry['src_path'], relative_to=git_root) + if entry['dst_mode'] == '120000': + # Do not process symlinks + continue + if not (matches_some_path(file_patterns, entry_path)): + continue + if format_file_in_index(formatter, entry, update_working_tree=update_working_tree, write=write, verbose=verbose): + info('Reformatted {} with {}'.format(entry['src_path'], formatter)) + except Exception as err: + fatal(str(err)) + +# Run formatter on file in the git index. Creates a new git object with the +# result, and replaces the content of the file in the index with that object. +# Returns hash of the new object if formatting produced any changes. +def format_file_in_index(formatter, diff_entry, update_working_tree=True, write=True, verbose=False): + orig_hash = diff_entry['dst_hash'] + new_hash = format_object(formatter, orig_hash, diff_entry['src_path'], verbose=verbose) + + # If the new hash is the same then the formatter did not make any changes. + if not write or new_hash == orig_hash: + return None + + # If the content of the new object is empty then the formatter did not + # produce any output. We want to abort instead of replacing the file with an + # empty one. + if object_is_empty(new_hash): + return None + + replace_file_in_index(diff_entry, new_hash) + + if update_working_tree: + try: + patch_working_file(diff_entry['src_path'], orig_hash, new_hash) + except Exception as err: + # Errors patching working tree files are not fatal + warn(str(err)) + + return new_hash + +file_path_placeholder = re.compile('\{\}') + +# Run formatter on a git blob identified by its hash. Writes output to a new git +# blob, and returns the hash of the new blob. +def format_object(formatter, object_hash, file_path, verbose=False): + get_content = subprocess.Popen( + ['git', 'cat-file', '-p', object_hash], + stdout=subprocess.PIPE + ) + command = re.sub(file_path_placeholder, file_path, formatter) + if verbose: + info(command) + format_content = subprocess.Popen( + command, + shell=True, + stdin=get_content.stdout, + stdout=subprocess.PIPE + ) + write_object = subprocess.Popen( + ['git', 'hash-object', '-w', '--stdin'], + stdin=format_content.stdout, + stdout=subprocess.PIPE + ) + + get_content.stdout.close() + format_content.stdout.close() + + if get_content.wait() != 0: + raise ValueError('unable to read file content from object database: ' + object_hash) + + if format_content.wait() != 0: + raise Exception('formatter exited with non-zero status') # TODO: capture stderr from format command + + new_hash, err = write_object.communicate() + + if write_object.returncode != 0: + raise Exception('unable to write formatted content to object database') + + return new_hash.decode('utf-8').rstrip() + +def object_is_empty(object_hash): + get_content = subprocess.Popen( + ['git', 'cat-file', '-p', object_hash], + stdout=subprocess.PIPE + ) + content, err = get_content.communicate() + + if get_content.returncode != 0: + raise Exception('unable to verify content of formatted object') + + return not content + +def replace_file_in_index(diff_entry, new_object_hash): + subprocess.check_call(['git', 'update-index', + '--cacheinfo', '{},{},{}'.format( + diff_entry['dst_mode'], + new_object_hash, + diff_entry['src_path'] + )]) + +def patch_working_file(path, orig_object_hash, new_object_hash): + patch = subprocess.check_output( + ['git', 'diff', '--no-ext-diff', '--color=never', orig_object_hash, new_object_hash] + ) + + # Substitute object hashes in patch header with path to working tree file + patch_b = patch.replace(orig_object_hash.encode(), path.encode()).replace(new_object_hash.encode(), path.encode()) + + apply_patch = subprocess.Popen( + ['git', 'apply', '-'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + output, err = apply_patch.communicate(input=patch_b) + + if apply_patch.returncode != 0: + raise Exception('could not apply formatting changes to working tree file {}'.format(path)) + +# Format: src_mode dst_mode src_hash dst_hash status/score? src_path dst_path? +diff_pat = re.compile('^:(\d+) (\d+) ([a-f0-9]+) ([a-f0-9]+) ([A-Z])(\d+)?\t([^\t]+)(?:\t([^\t]+))?$') + +# Parse output from `git diff-index` +def parse_diff(diff): + m = diff_pat.match(diff) + if not m: + raise ValueError('Failed to parse diff-index line: ' + diff) + return { + 'src_mode': unless_zeroed(m.group(1)), + 'dst_mode': unless_zeroed(m.group(2)), + 'src_hash': unless_zeroed(m.group(3)), + 'dst_hash': unless_zeroed(m.group(4)), + 'status': m.group(5), + 'score': int(m.group(6)) if m.group(6) else None, + 'src_path': m.group(7), + 'dst_path': m.group(8) + } + +zeroed_pat = re.compile('^0+$') + +# Returns the argument unless the argument is a string of zeroes, in which case +# returns `None` +def unless_zeroed(s): + return s if not zeroed_pat.match(s) else None + +def get_git_root(): + return subprocess.check_output( + ['git', 'rev-parse', '--show-toplevel'] + ).decode('utf-8').rstrip() + +def normalize_path(p, relative_to=None): + return os.path.abspath( + os.path.join(relative_to, p) if relative_to else p + ) + +def matches_some_path(patterns, target): + is_match = False + for signed_pattern in patterns: + (is_pattern_positive, pattern) = from_signed_pattern(signed_pattern) + if fnmatch(target, normalize_path(pattern)): + is_match = is_pattern_positive + return is_match + +# Checks for a '!' as the first character of a pattern, returns the rest of the +# pattern in a tuple. The tuple takes the form (is_pattern_positive, pattern). +# For example: +# from_signed_pattern('!pat') == (False, 'pat') +# from_signed_pattern('pat') == (True, 'pat') +def from_signed_pattern(pattern): + if pattern[0] == '!': + return (False, pattern[1:]) + else: + return (True, pattern) + +class CustomArgumentParser(argparse.ArgumentParser): + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = argparse._( + 'unrecognized arguments: %s. Do you need to quote your formatter command?' + ) + self.error(msg % ' '.join(argv)) + return args + +if __name__ == '__main__': + parser = CustomArgumentParser( + description='Transform staged files using a formatting command that accepts content via stdin and produces a result via stdout.', + epilog='Example: %(prog)s --formatter "prettier --stdin-filepath \'{}\'" "src/*.js" "test/*.js"' + ) + parser.add_argument( + '--formatter', '-f', + required=True, + help='Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will be replaced with a path to the file being formatted. (Example: "prettier --stdin-filepath \'{}\'")' + ) + parser.add_argument( + '--no-update-working-tree', + action='store_true', + help='By default formatting changes made to staged file content will also be applied to working tree files via a patch. This option disables that behavior, leaving working tree files untouched.' + ) + parser.add_argument( + '--no-write', + action='store_true', + help='Prevents %(prog)s from modifying staged or working tree files. You can use this option to check staged changes with a linter instead of formatting. With this option stdout from the formatter command is ignored. Example: %(prog)s --no-write -f "eslint --stdin --stdin-filename \'{}\' >&2" "*.js"' + ) + parser.add_argument( + '--version', + action='version', + version='%(prog)s version {}'.format(VERSION), + help='Display version of %(prog)s' + ) + parser.add_argument( + '--verbose', + help='Show the formatting commands that are running', + action='store_true' + ) + parser.add_argument( + 'files', + nargs='+', + help='Patterns that specify files to format. The formatter will only transform staged files that are given here. Patterns may be literal file paths, or globs which will be tested against staged file paths using Python\'s fnmatch function. For example "src/*.js" will match all files with a .js extension in src/ and its subdirectories. Patterns may be negated to exclude files using a "!" character. Patterns are evaluated left-to-right. (Example: "main.js" "src/*.js" "test/*.js" "!test/todo/*")' + ) + args = parser.parse_args() + files = vars(args)['files'] + format_staged_files( + file_patterns=files, + formatter=vars(args)['formatter'], + git_root=get_git_root(), + update_working_tree=not vars(args)['no_update_working_tree'], + write=not vars(args)['no_write'], + verbose=vars(args)['verbose'] + ) diff --git a/.tools/git-format-staged-LICENSE.txt b/.tools/git-format-staged-LICENSE.txt new file mode 100644 index 000000000..792ca2246 --- /dev/null +++ b/.tools/git-format-staged-LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2018 Jesse Hallett + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8c3ee449..f66e018df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,16 +15,19 @@ When reporting a bug in the navigation SDK itself, please indicate: ## Building the SDK -To build this SDK, you need Xcode 14 and [Carthage](https://github.com/Carthage/Carthage/): +To build this SDK, you need Xcode 15. Simply open Package.swift, it will open in Xcode and download all required dependencies. -1. Run `carthage bootstrap --platform iOS --cache-builds`. -1. Once the Carthage build finishes, open `MapboxNavigation.xcodeproj` in Xcode and build the MapboxNavigation scheme. +## Enable automatic code formatting -See [the README](./README.md#running-the-example-project) for instructions on building and running the included Swift and Objective-C example projects. +We use SwiftFormat via a commit hook which allows us to reformat the changed files on commit. This ensures a conistent code style. The repo is prepared for this, however you need to enable this manually via: + +```bash +git config core.hooksPath .githooks +``` ## Testing the SDK -It is important to test the SDK using the `iPhone 8 Plus` simulator for the `FBSnapshotter` tests. +You can run tests locally in Xcode. As a safety measure tests are run via GitHub Actions when you submitt a PR. If you are a first time contributor a member of maplibre needs to approve the GitHub Actions runs first, for this please tag someone either in GitHub or in the OpenStreetMap Slack. ## Opening a pull request diff --git a/MapboxCoreNavigation/Array.swift b/MapboxCoreNavigation/Array.swift index 4ab2d1a8d..2857fa1e6 100644 --- a/MapboxCoreNavigation/Array.swift +++ b/MapboxCoreNavigation/Array.swift @@ -1,9 +1,8 @@ -import Foundation import CoreLocation +import Foundation import MapboxDirections -extension Array { - +public extension Array { /** Initializes a [CLLocation] from a JSON string at a given filePath. @@ -22,7 +21,7 @@ extension Array { - parameter filePath: The file’s path. - returns: A [CLLocation]. */ - public static func locations(from filePath: String) -> [CLLocation]! { + static func locations(from filePath: String) -> [CLLocation]! { let url = URL(fileURLWithPath: filePath) do { @@ -45,11 +44,10 @@ extension Array { extension Array where Element: MapboxDirections.Route { func mostSimilar(to route: Route) -> Route? { let target = route.description - return self.min { (left, right) -> Bool in + return self.min { left, right -> Bool in let leftDistance = left.description.minimumEditDistance(to: target) let rightDistance = right.description.minimumEditDistance(to: target) return leftDistance < rightDistance } - } } diff --git a/MapboxCoreNavigation/BundleAdditions.swift b/MapboxCoreNavigation/BundleAdditions.swift index a1b212d38..270182498 100644 --- a/MapboxCoreNavigation/BundleAdditions.swift +++ b/MapboxCoreNavigation/BundleAdditions.swift @@ -12,24 +12,16 @@ extension Bundle { } var locationAlwaysAndWhenInUseUsageDescription: String? { - get { - return object(forInfoDictionaryKey: "NSLocationAlwaysAndWhenInUseUsageDescription") as? String - } + object(forInfoDictionaryKey: "NSLocationAlwaysAndWhenInUseUsageDescription") as? String } var locationWhenInUseUsageDescription: String? { - get { - return object(forInfoDictionaryKey: "NSLocationWhenInUseUsageDescription") as? String - } + object(forInfoDictionaryKey: "NSLocationWhenInUseUsageDescription") as? String } var locationAlwaysUsageDescription: String? { - get { - return object(forInfoDictionaryKey: "NSLocationAlwaysUsageDescription") as? String - } + object(forInfoDictionaryKey: "NSLocationAlwaysUsageDescription") as? String } - class var mapboxCoreNavigation: Bundle { - get { return Bundle(for: RouteController.self) } - } + class var mapboxCoreNavigation: Bundle { Bundle(for: RouteController.self) } } diff --git a/MapboxCoreNavigation/CLLocation.swift b/MapboxCoreNavigation/CLLocation.swift index aa41d6fa7..50ba693f4 100644 --- a/MapboxCoreNavigation/CLLocation.swift +++ b/MapboxCoreNavigation/CLLocation.swift @@ -3,10 +3,8 @@ import MapboxDirections import Turf extension CLLocation { - var isQualified: Bool { - return - 0...100 ~= horizontalAccuracy + 0 ... 100 ~= horizontalAccuracy } /// Returns a dictionary representation of the location. @@ -69,21 +67,21 @@ extension CLLocation { return closestCoordinate.distance < maximumDistance } - //MARK: - Route Snapping + // MARK: - Route Snapping func snapped(to legProgress: RouteLegProgress) -> CLLocation? { - let coords = coordinates(for: legProgress) + let coords = self.coordinates(for: legProgress) - guard let closest = Polyline(coords).closestCoordinate(to: self.coordinate) else { return nil } + guard let closest = Polyline(coords).closestCoordinate(to: coordinate) else { return nil } guard let calculatedCourseForLocationOnStep = interpolatedCourse(along: coords) else { return nil } let userCourse = calculatedCourseForLocationOnStep let userCoordinate = closest.coordinate guard let firstCoordinate = legProgress.leg.steps.first?.coordinates?.first else { return nil } - guard shouldSnapCourse(toRouteWith: calculatedCourseForLocationOnStep, distanceToFirstCoordinateOnLeg: self.coordinate.distance(to: firstCoordinate)) else { return nil } + guard self.shouldSnapCourse(toRouteWith: calculatedCourseForLocationOnStep, distanceToFirstCoordinateOnLeg: coordinate.distance(to: firstCoordinate)) else { return nil } - guard (closest.distance <= (RouteControllerUserLocationSnappingDistance + self.horizontalAccuracy)) else { + guard closest.distance <= (RouteControllerUserLocationSnappingDistance + horizontalAccuracy) else { return nil } @@ -100,15 +98,13 @@ extension CLLocation { // If the upcoming maneuver a sharp turn, only look at the current step for snapping. // Otherwise, we may get false positives from nearby step coordinates if let upcomingStep = legProgress.upComingStep, - let initialHeading = upcomingStep.initialHeading, - let finalHeading = upcomingStep.finalHeading { - + let initialHeading = upcomingStep.initialHeading, + let finalHeading = upcomingStep.finalHeading { // The max here is 180. The closer it is to 180, the sharper the turn. if initialHeading.clockwiseDifference(from: finalHeading) > 180 - RouteSnappingMaxManipulatedCourseAngle { return stepCoordinates } - if finalHeading.difference(from: course) > RouteControllerMaximumAllowedDegreeOffsetForTurnCompletion { return stepCoordinates } @@ -121,7 +117,6 @@ extension CLLocation { return nearbyCoordinates } - /** Given a location and a series of coordinates, compute what the course should be for a the location. */ @@ -149,14 +144,14 @@ extension CLLocation { let relativeAnglepointAhead = (wrappedPointAhead - wrappedCourse).wrap(min: -180, max: 180) let averageRelativeAngle: Double - // User is at the beginning of the route, there is no closest point behind the user. - if pointBehindClosest.distance <= 0 && pointAheadClosest.distance > 0 { - averageRelativeAngle = relativeAnglepointAhead + // User is at the beginning of the route, there is no closest point behind the user. + = if pointBehindClosest.distance <= 0, pointAheadClosest.distance > 0 { + relativeAnglepointAhead // User is at the end of the route, there is no closest point in front of the user. - } else if pointAheadClosest.distance <= 0 && pointBehindClosest.distance > 0 { - averageRelativeAngle = relativeAnglepointBehind + } else if pointAheadClosest.distance <= 0, pointBehindClosest.distance > 0 { + relativeAnglepointBehind } else { - averageRelativeAngle = (relativeAnglepointBehind + relativeAnglepointAhead) / 2 + (relativeAnglepointBehind + relativeAnglepointAhead) / 2 } return (wrappedCourse + averageRelativeAngle).wrap(min: 0, max: 360) @@ -166,14 +161,13 @@ extension CLLocation { Determines if the course of a location is qualified enough to allow the user puck to be snapped */ func shouldSnapCourse(toRouteWith course: CLLocationDirection, distanceToFirstCoordinateOnLeg: CLLocationDistance = CLLocationDistanceMax) -> Bool { - // If the user is near the beginning of leg, allow for unsnapped more often. let isWithinDepartureStep = distanceToFirstCoordinateOnLeg < RouteControllerManeuverZoneRadius - if course >= 0 && - (speed >= RouteSnappingMinimumSpeed || isWithinDepartureStep) && - (horizontalAccuracy < RouteSnappingMinimumHorizontalAccuracy || isWithinDepartureStep) && - course.difference(from: self.course) > RouteSnappingMaxManipulatedCourseAngle { + if course >= 0, + speed >= RouteSnappingMinimumSpeed || isWithinDepartureStep, + horizontalAccuracy < RouteSnappingMinimumHorizontalAccuracy || isWithinDepartureStep, + course.difference(from: self.course) > RouteSnappingMaxManipulatedCourseAngle { return false } return true diff --git a/MapboxCoreNavigation/CLLocationDirection.swift b/MapboxCoreNavigation/CLLocationDirection.swift index 8b1e9cbce..897f46fdf 100644 --- a/MapboxCoreNavigation/CLLocationDirection.swift +++ b/MapboxCoreNavigation/CLLocationDirection.swift @@ -1,5 +1,5 @@ -import Foundation import CoreLocation +import Foundation extension CLLocationDirection { func clockwiseDifference(from otherDirection: CLLocationDirection) -> CLLocationDirection { @@ -14,6 +14,6 @@ extension CLLocationDirection { } var isQualified: Bool { - return self > -1 + self > -1 } } diff --git a/MapboxCoreNavigation/Constants.swift b/MapboxCoreNavigation/Constants.swift index 00feadfeb..0d38866a4 100644 --- a/MapboxCoreNavigation/Constants.swift +++ b/MapboxCoreNavigation/Constants.swift @@ -1,9 +1,10 @@ -import Foundation import CoreLocation +import Foundation import MapboxCoreNavigationObjC import MapboxDirections // MARK: - RouteController + /** Maximum number of meters the user can travel away from step before `RouteControllerShouldReroute` is emitted. */ @@ -56,7 +57,7 @@ public var RouteSnappingMinimumSpeed: CLLocationSpeed = 3 /** The minimum distance threshold used for giving a "Continue" type instructions. */ -public var RouteControllerMinimumDistanceForContinueInstruction: CLLocationDistance = 2_000 +public var RouteControllerMinimumDistanceForContinueInstruction: CLLocationDistance = 2000 /** The minimum distance in the opposite direction of travel that triggers rerouting. @@ -75,7 +76,8 @@ public var RouteControllerNumberOfSecondsForRerouteFeedback: TimeInterval = 10 let FasterRouteFoundEvent = "navigation.fasterRoute" -//MARK: - Route Snapping (CLLocation) +// MARK: - Route Snapping (CLLocation) + /** Accepted deviation excluding horizontal accuracy before the user is considered to be off route. */ @@ -122,46 +124,46 @@ public var RouteControllerMaximumSpeedForUsingCurrentStep: CLLocationSpeed = 1 */ public typealias RouteControllerNotificationUserInfoKey = MBRouteControllerNotificationUserInfoKey -extension Notification.Name { +public extension Notification.Name { /** Posted when `RouteController` fails to reroute the user after the user diverges from the expected route. The user info dictionary contains the key `RouteControllerNotificationUserInfoKey.errorKey`. */ - public static let routeControllerDidFailToReroute = MBRouteControllerDidFailToReroute + static let routeControllerDidFailToReroute = MBRouteControllerDidFailToReroute /** Posted after the user diverges from the expected route, just before `RouteController` attempts to calculate a new route. The user info dictionary contains the key `RouteControllerNotificationUserInfoKey.locationKey`. */ - public static let routeControllerWillReroute = MBRouteControllerWillReroute + static let routeControllerWillReroute = MBRouteControllerWillReroute /** Posted when `RouteController` obtains a new route in response to the user diverging from a previous route. The user info dictionary contains the keys `RouteControllerNotificationUserInfoKey.locationKey` and `RouteControllerNotificationUserInfoKey.isProactiveKey`. */ - public static let routeControllerDidReroute = MBRouteControllerDidReroute + static let routeControllerDidReroute = MBRouteControllerDidReroute /** Posted when `RouteController` receives a user location update representing movement along the expected route. The user info dictionary contains the keys `RouteControllerNotificationUserInfoKey.routeProgressKey`, `RouteControllerNotificationUserInfoKey.locationKey`, and `RouteControllerNotificationUserInfoKey.rawLocationKey`. */ - public static let routeControllerProgressDidChange = MBRouteControllerProgressDidChange + static let routeControllerProgressDidChange = MBRouteControllerProgressDidChange /** Posted when `RouteController` detects that the user has passed an ideal point for saying an instruction aloud. The user info dictionary contains the key `RouteControllerNotificationUserInfoKey.routeProgressKey`. */ - public static let routeControllerDidPassSpokenInstructionPoint = MBRouteControllerDidPassSpokenInstructionPoint + static let routeControllerDidPassSpokenInstructionPoint = MBRouteControllerDidPassSpokenInstructionPoint /** Posted when `RouteController` detects that the user has passed an ideal point for displaying an instruction. The user info dictionary contains the key `RouteControllerNotificationUserInfoKey.routeProgressKey`. */ - public static let routeControllerDidPassVisualInstructionPoint = MBRouteControllerDidPassVisualInstructionPoint + static let routeControllerDidPassVisualInstructionPoint = MBRouteControllerDidPassVisualInstructionPoint } diff --git a/MapboxCoreNavigation/CoreFeedbackEvent.swift b/MapboxCoreNavigation/CoreFeedbackEvent.swift index e0c12742e..5935b87c4 100644 --- a/MapboxCoreNavigation/CoreFeedbackEvent.swift +++ b/MapboxCoreNavigation/CoreFeedbackEvent.swift @@ -15,11 +15,11 @@ class CoreFeedbackEvent: Hashable { } func hash(into hasher: inout Hasher) { - hasher.combine(id) + hasher.combine(self.id) } - static func ==(lhs: CoreFeedbackEvent, rhs: CoreFeedbackEvent) -> Bool { - return lhs.id == rhs.id + static func == (lhs: CoreFeedbackEvent, rhs: CoreFeedbackEvent) -> Bool { + lhs.id == rhs.id } } diff --git a/MapboxCoreNavigation/Date.swift b/MapboxCoreNavigation/Date.swift index 468150385..2a178ca6c 100644 --- a/MapboxCoreNavigation/Date.swift +++ b/MapboxCoreNavigation/Date.swift @@ -2,7 +2,7 @@ import Foundation extension Date { var ISO8601: String { - return Date.ISO8601Formatter.string(from: self) + Date.ISO8601Formatter.string(from: self) } static let ISO8601Formatter: DateFormatter = { diff --git a/MapboxCoreNavigation/DistanceFormatter.swift b/MapboxCoreNavigation/DistanceFormatter.swift index ccf3d6a93..0f109a151 100644 --- a/MapboxCoreNavigation/DistanceFormatter.swift +++ b/MapboxCoreNavigation/DistanceFormatter.swift @@ -1,58 +1,57 @@ import CoreLocation extension CLLocationDistance { - - static let metersPerMile: CLLocationDistance = 1_609.344 + static let metersPerMile: CLLocationDistance = 1609.344 static let feetPerMeter: CLLocationDistance = 3.28084 // Returns the distance converted to miles var miles: Double { - return self / .metersPerMile + self / .metersPerMile } // Returns the distance converted to feet var feet: Double { - return self * .feetPerMeter + self * .feetPerMeter } // Returns the distance converted to yards var yards: Double { - return feet / 3 + self.feet / 3 } // Returns the distance converted to kilometers var kilometers: Double { - return self / 1000 + self / 1000 } // Returns the distance in meters converted from miles func inMiles() -> Double { - return self * .metersPerMile + self * .metersPerMile } // Returns the distance in meters converted from yards func inYards() -> Double { - return self * .feetPerMeter / 3 + self * .feetPerMeter / 3 } func converted(to unit: LengthFormatter.Unit) -> Double { switch unit { case .millimeter: - return self / 1_000 + return self / 1000 case .centimeter: return self / 100 case .meter: return self case .kilometer: - return kilometers + return self.kilometers case .inch: - return feet * 12 + return self.feet * 12 case .foot: - return feet + return self.feet case .yard: - return yards + return self.yards case .mile: - return miles + return self.miles @unknown default: fatalError("Unknown unit") } @@ -68,7 +67,7 @@ struct RoundingTable { @available(iOS 10.0, *) func measurement(for distance: CLLocationDistance) -> Measurement { - switch unit { + switch self.unit { case .millimeter: return Measurement(value: distance.kilometers / 1e6, unit: .millimeters) case .centimeter: @@ -91,17 +90,17 @@ struct RoundingTable { } func localizedDistanceString(for distance: CLLocationDistance, using formatter: DistanceFormatter) -> String { - switch unit { + switch self.unit { case .mile: - return formatter.string(fromValue: distance.miles, unit: unit) + formatter.string(fromValue: distance.miles, unit: self.unit) case .foot: - return formatter.string(fromValue: distance.feet, unit: unit) + formatter.string(fromValue: distance.feet, unit: self.unit) case .yard: - return formatter.string(fromValue: distance.yards, unit: unit) + formatter.string(fromValue: distance.yards, unit: self.unit) case .kilometer: - return formatter.string(fromValue: distance.kilometers, unit: unit) + formatter.string(fromValue: distance.kilometers, unit: self.unit) default: - return formatter.string(fromValue: distance, unit: unit) + formatter.string(fromValue: distance, unit: self.unit) } } } @@ -109,17 +108,17 @@ struct RoundingTable { let thresholds: [Threshold] func threshold(for distance: CLLocationDistance) -> Threshold { - for threshold in thresholds { + for threshold in self.thresholds { if distance < threshold.maximumDistance { return threshold } } - return thresholds.last! + return self.thresholds.last! } } -extension NSAttributedString.Key { - public static let quantity = NSAttributedString.Key(rawValue: "MBQuantity") +public extension NSAttributedString.Key { + static let quantity = NSAttributedString.Key(rawValue: "MBQuantity") } /// Provides appropriately formatted, localized descriptions of linear distances. @@ -134,27 +133,21 @@ open class DistanceFormatter: LengthFormatter { public private(set) var unit: LengthFormatter.Unit = .millimeter // Rounding tables for metric, imperial, and UK measurement systems. The last threshold is used as a default. - lazy var roundingTableMetric: RoundingTable = { - return RoundingTable(thresholds: [.init(maximumDistance: 25, roundingIncrement: 5, unit: .meter, maximumFractionDigits: 0), - .init(maximumDistance: 100, roundingIncrement: 25, unit: .meter, maximumFractionDigits: 0), - .init(maximumDistance: 999, roundingIncrement: 50, unit: .meter, maximumFractionDigits: 0), - .init(maximumDistance: 3_000, roundingIncrement: 0, unit: .kilometer, maximumFractionDigits: 1), - .init(maximumDistance: 5_000, roundingIncrement: 0, unit: .kilometer, maximumFractionDigits: 0)]) - }() - - lazy var roundingTableUK: RoundingTable = { - return RoundingTable(thresholds: [.init(maximumDistance: 20.inYards(), roundingIncrement: 10, unit: .yard, maximumFractionDigits: 0), - .init(maximumDistance: 100.inYards(), roundingIncrement: 25, unit: .yard, maximumFractionDigits: 0), - .init(maximumDistance: 0.1.inMiles(), roundingIncrement: 50, unit: .yard, maximumFractionDigits: 1), - .init(maximumDistance: 3.inMiles(), roundingIncrement: 0.1, unit: .mile, maximumFractionDigits: 1), - .init(maximumDistance: 5.inMiles(), roundingIncrement: 0, unit: .mile, maximumFractionDigits: 0)]) - }() - - lazy var roundingTableImperial: RoundingTable = { - return RoundingTable(thresholds: [.init(maximumDistance: 0.1.inMiles(), roundingIncrement: 50, unit: .foot, maximumFractionDigits: 0), - .init(maximumDistance: 3.inMiles(), roundingIncrement: 0.1, unit: .mile, maximumFractionDigits: 1), - .init(maximumDistance: 5.inMiles(), roundingIncrement: 0, unit: .mile, maximumFractionDigits: 0)]) - }() + lazy var roundingTableMetric: RoundingTable = .init(thresholds: [.init(maximumDistance: 25, roundingIncrement: 5, unit: .meter, maximumFractionDigits: 0), + .init(maximumDistance: 100, roundingIncrement: 25, unit: .meter, maximumFractionDigits: 0), + .init(maximumDistance: 999, roundingIncrement: 50, unit: .meter, maximumFractionDigits: 0), + .init(maximumDistance: 3000, roundingIncrement: 0, unit: .kilometer, maximumFractionDigits: 1), + .init(maximumDistance: 5000, roundingIncrement: 0, unit: .kilometer, maximumFractionDigits: 0)]) + + lazy var roundingTableUK: RoundingTable = .init(thresholds: [.init(maximumDistance: 20.inYards(), roundingIncrement: 10, unit: .yard, maximumFractionDigits: 0), + .init(maximumDistance: 100.inYards(), roundingIncrement: 25, unit: .yard, maximumFractionDigits: 0), + .init(maximumDistance: 0.1.inMiles(), roundingIncrement: 50, unit: .yard, maximumFractionDigits: 1), + .init(maximumDistance: 3.inMiles(), roundingIncrement: 0.1, unit: .mile, maximumFractionDigits: 1), + .init(maximumDistance: 5.inMiles(), roundingIncrement: 0, unit: .mile, maximumFractionDigits: 0)]) + + lazy var roundingTableImperial: RoundingTable = .init(thresholds: [.init(maximumDistance: 0.1.inMiles(), roundingIncrement: 50, unit: .foot, maximumFractionDigits: 0), + .init(maximumDistance: 3.inMiles(), roundingIncrement: 0.1, unit: .mile, maximumFractionDigits: 1), + .init(maximumDistance: 5.inMiles(), roundingIncrement: 0, unit: .mile, maximumFractionDigits: 0)]) /** Intializes a new `DistanceFormatter`. @@ -164,62 +157,62 @@ open class DistanceFormatter: LengthFormatter { @objc public init(approximate: Bool = false) { self.approx = approximate super.init() - self.numberFormatter.locale = .nationalizedCurrent + numberFormatter.locale = .nationalizedCurrent } public required init?(coder decoder: NSCoder) { - approx = decoder.decodeBool(forKey: "approximate") + self.approx = decoder.decodeBool(forKey: "approximate") super.init(coder: decoder) } - open override func encode(with aCoder: NSCoder) { + override open func encode(with aCoder: NSCoder) { super.encode(with: aCoder) - aCoder.encode(approx, forKey: "approximate") + aCoder.encode(self.approx, forKey: "approximate") } func threshold(for distance: CLLocationDistance) -> RoundingTable.Threshold { if NavigationSettings.shared.usesMetric { - return roundingTableMetric.threshold(for: distance) + self.roundingTableMetric.threshold(for: distance) } else if numberFormatter.locale.identifier == "en-GB" { - return roundingTableUK.threshold(for: distance) + self.roundingTableUK.threshold(for: distance) } else { - return roundingTableImperial.threshold(for: distance) + self.roundingTableImperial.threshold(for: distance) } } /** - Returns a more human readable `String` from a given `CLLocationDistance`. + Returns a more human readable `String` from a given `CLLocationDistance`. - The user’s `Locale` is used here to set the units. - */ + The user’s `Locale` is used here to set the units. + */ @objc public func string(from distance: CLLocationDistance) -> String { numberFormatter.positivePrefix = "" numberFormatter.positiveSuffix = "" - numberFormatter.decimalSeparator = nonFractionalLengthFormatter.numberFormatter.decimalSeparator - numberFormatter.alwaysShowsDecimalSeparator = nonFractionalLengthFormatter.numberFormatter.alwaysShowsDecimalSeparator + numberFormatter.decimalSeparator = self.nonFractionalLengthFormatter.numberFormatter.decimalSeparator + numberFormatter.alwaysShowsDecimalSeparator = self.nonFractionalLengthFormatter.numberFormatter.alwaysShowsDecimalSeparator numberFormatter.usesSignificantDigits = false - return formattedDistance(distance) + return self.formattedDistance(distance) } - @objc open override func string(fromMeters numberInMeters: Double) -> String { - return self.string(from: numberInMeters) + @objc override open func string(fromMeters numberInMeters: Double) -> String { + self.string(from: numberInMeters) } func formattedDistance(_ distance: CLLocationDistance) -> String { - let threshold = self.threshold(for: distance) + let threshold = threshold(for: distance) numberFormatter.maximumFractionDigits = threshold.maximumFractionDigits numberFormatter.roundingIncrement = threshold.roundingIncrement as NSNumber - unit = threshold.unit + self.unit = threshold.unit return threshold.localizedDistanceString(for: distance, using: self) } @available(iOS 10.0, *) @objc(measurementOfDistance:) public func measurement(of distance: CLLocationDistance) -> Measurement { - let threshold = self.threshold(for: distance) + let threshold = threshold(for: distance) numberFormatter.maximumFractionDigits = threshold.maximumFractionDigits numberFormatter.roundingIncrement = threshold.roundingIncrement as NSNumber - unit = threshold.unit + self.unit = threshold.unit return threshold.measurement(for: distance) } @@ -228,14 +221,14 @@ open class DistanceFormatter: LengthFormatter { `NSAttributedStringKey.quantity` is applied to the numeric quantity. */ - @objc open override func attributedString(for obj: Any, withDefaultAttributes attrs: [NSAttributedString.Key : Any]? = nil) -> NSAttributedString? { + @objc override open func attributedString(for obj: Any, withDefaultAttributes attrs: [NSAttributedString.Key: Any]? = nil) -> NSAttributedString? { guard let distance = obj as? CLLocationDistance else { return nil } - let string = self.string(from: distance) + let string = string(from: distance) let attributedString = NSMutableAttributedString(string: string, attributes: attrs) - let convertedDistance = distance.converted(to: threshold(for: distance).unit) + let convertedDistance = distance.converted(to: self.threshold(for: distance).unit) if let quantityString = numberFormatter.string(from: convertedDistance as NSNumber) { // NSMutableAttributedString methods accept NSRange, not Range. let quantityRange = (string as NSString).range(of: quantityString) diff --git a/MapboxCoreNavigation/EndOfRouteFeedback.swift b/MapboxCoreNavigation/EndOfRouteFeedback.swift index 39f93d54a..54c3e5cb5 100644 --- a/MapboxCoreNavigation/EndOfRouteFeedback.swift +++ b/MapboxCoreNavigation/EndOfRouteFeedback.swift @@ -5,13 +5,13 @@ import Foundation */ @objc open class EndOfRouteFeedback: NSObject { /** - Rating: The user's rating for the route. Normalized between 0 and 100. - */ + Rating: The user's rating for the route. Normalized between 0 and 100. + */ let rating: Int? /** - Comment: Any comments that the user had about the route. - */ + Comment: Any comments that the user had about the route. + */ let comment: String? @nonobjc public init(rating: Int? = nil, comment: String? = nil) { @@ -19,6 +19,7 @@ import Foundation self.comment = comment super.init() } + @objc public convenience init(rating ratingNumber: NSNumber?, comment: String?) { let rating = ratingNumber?.intValue self.init(rating: rating, comment: comment) diff --git a/MapboxCoreNavigation/Feedback.swift b/MapboxCoreNavigation/Feedback.swift index cb884b448..413bf50d9 100644 --- a/MapboxCoreNavigation/Feedback.swift +++ b/MapboxCoreNavigation/Feedback.swift @@ -63,27 +63,27 @@ public enum FeedbackType: Int, CustomStringConvertible { public var description: String { switch self { case .general: - return "general" + "general" case .accident: - return "accident" + "accident" case .hazard: - return "hazard" + "hazard" case .roadClosed: - return "road_closed" + "road_closed" case .notAllowed: - return "not_allowed" + "not_allowed" case .missingRoad: - return "missing_road" + "missing_road" case .missingExit: - return "missing_exit" + "missing_exit" case .routingError: - return "routing_error" + "routing_error" case .confusingInstruction: - return "confusing_instruction" + "confusing_instruction" case .reportTraffic: - return "report_traffic" + "report_traffic" case .mapIssue: - return "other_map_issue" + "other_map_issue" } } } @@ -97,11 +97,11 @@ public enum FeedbackSource: Int, CustomStringConvertible { public var description: String { switch self { case .user: - return "user" + "user" case .reroute: - return "reroute" + "reroute" case .unknown: - return "unknown" + "unknown" } } } diff --git a/MapboxCoreNavigation/Fixture.swift b/MapboxCoreNavigation/Fixture.swift index e370df313..462d548e0 100644 --- a/MapboxCoreNavigation/Fixture.swift +++ b/MapboxCoreNavigation/Fixture.swift @@ -1,7 +1,6 @@ import Foundation public class Fixture: NSObject { - public class func stringFromFileNamed(name: String, bundle: Bundle) -> String { guard let path = bundle.path(forResource: name, ofType: "json") ?? bundle.path(forResource: name, ofType: "geojson") else { return "" diff --git a/MapboxCoreNavigation/Locale.swift b/MapboxCoreNavigation/Locale.swift index 04843110a..bbc79bef6 100644 --- a/MapboxCoreNavigation/Locale.swift +++ b/MapboxCoreNavigation/Locale.swift @@ -1,11 +1,10 @@ import Foundation -extension Locale { - +public extension Locale { /** Given the app's localized language setting, returns a string representing the user's localization. */ - public static var preferredLocalLanguageCountryCode: String { + static var preferredLocalLanguageCountryCode: String { let firstBundleLocale = Bundle.main.preferredLocalizations.first! let bundleLocale = firstBundleLocale.components(separatedBy: "-") @@ -23,17 +22,17 @@ extension Locale { /** Returns a `Locale` from `preferredLocalLanguageCountryCode`. */ - public static var nationalizedCurrent = Locale(identifier: preferredLocalLanguageCountryCode) + static var nationalizedCurrent = Locale(identifier: preferredLocalLanguageCountryCode) - public static var usesMetric: Bool { - let locale = self.current as NSLocale + static var usesMetric: Bool { + let locale = current as NSLocale guard let measurementSystem = locale.object(forKey: .measurementSystem) as? String else { return false } return measurementSystem == "Metric" } - public var usesMetric: Bool { + var usesMetric: Bool { let locale = self as NSLocale guard let measurementSystem = locale.object(forKey: .measurementSystem) as? String else { return false diff --git a/MapboxCoreNavigation/MMEEventsManager.swift b/MapboxCoreNavigation/MMEEventsManager.swift index e6c9df9b4..f86018d11 100644 --- a/MapboxCoreNavigation/MMEEventsManager.swift +++ b/MapboxCoreNavigation/MMEEventsManager.swift @@ -1,6 +1,6 @@ -import Polyline -import MapboxDirections import AVFoundation +import MapboxDirections +import Polyline import UIKit let SecondsBeforeCollectionAfterFeedbackEvent: TimeInterval = 20 @@ -8,14 +8,12 @@ let EventVersion = 8 extension UIDevice { @nonobjc var machine: String { - get { - var systemInfo = utsname() - uname(&systemInfo) - let machineMirror = Mirror(reflecting: systemInfo.machine) - return machineMirror.children.reduce("") { (identifier: String, element: Mirror.Child) in - guard let value = element.value as? Int8, value != 0 else { return identifier } - return identifier + String(UnicodeScalar(UInt8(value))) - } + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + return machineMirror.children.reduce("") { (identifier: String, element: Mirror.Child) in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) } } } diff --git a/MapboxCoreNavigation/NavigationLocationManager.swift b/MapboxCoreNavigation/NavigationLocationManager.swift index c4a899f58..8c80596ba 100644 --- a/MapboxCoreNavigation/NavigationLocationManager.swift +++ b/MapboxCoreNavigation/NavigationLocationManager.swift @@ -1,5 +1,5 @@ -import Foundation import CoreLocation +import Foundation #if os(iOS) import UIKit #endif @@ -9,14 +9,12 @@ import UIKit */ @objc(MBNavigationLocationManager) open class NavigationLocationManager: CLLocationManager, NSCopying { - public func copy(with zone: NSZone? = nil) -> Any { let copy = NavigationLocationManager() - copy.lastKnownLocation = lastKnownLocation + copy.lastKnownLocation = self.lastKnownLocation return copy } - var lastKnownLocation: CLLocation? override public init() { diff --git a/MapboxCoreNavigation/NavigationRouteOptions.swift b/MapboxCoreNavigation/NavigationRouteOptions.swift index 77ab71e59..370eb412c 100644 --- a/MapboxCoreNavigation/NavigationRouteOptions.swift +++ b/MapboxCoreNavigation/NavigationRouteOptions.swift @@ -9,7 +9,6 @@ import MapboxDirectionsObjc */ @objc(MBNavigationRouteOptions) open class NavigationRouteOptions: RouteOptions { - /** Initializes a navigation route options object for routes between the given waypoints and an optional profile identifier optimized for navigation. @@ -66,7 +65,6 @@ open class NavigationRouteOptions: RouteOptions { */ @objc(MBNavigationMatchOptions) open class NavigationMatchOptions: MatchOptions { - /** Initializes a navigation route options object for routes between the given waypoints and an optional profile identifier optimized for navigation. diff --git a/MapboxCoreNavigation/NavigationSettings.swift b/MapboxCoreNavigation/NavigationSettings.swift index 495d7984f..b1d345a49 100644 --- a/MapboxCoreNavigation/NavigationSettings.swift +++ b/MapboxCoreNavigation/NavigationSettings.swift @@ -1,12 +1,12 @@ import Foundation -extension Notification.Name { +public extension Notification.Name { /** Posted when something changes in the shared `NavigationSettings` object. The user info dictionary indicates which keys and values changed. */ - public static let navigationSettingsDidChange = MBNavigationSettingsDidChange + static let navigationSettingsDidChange = MBNavigationSettingsDidChange } /** @@ -16,36 +16,33 @@ extension Notification.Name { */ @objc(MBNavigationSettings) public class NavigationSettings: NSObject { - /** - The volume that the voice controller will use. + The volume that the voice controller will use. - This volume is relative to the system’s volume where 1.0 is same volume as the system. - */ + This volume is relative to the system’s volume where 1.0 is same volume as the system. + */ @objc public dynamic var voiceVolume: Float = 1.0 /** Specifies whether to mute the voice controller or not. */ - @objc public dynamic var voiceMuted : Bool = false + @objc public dynamic var voiceMuted: Bool = false /** Specifies the preferred distance measurement unit. - note: Anything but `kilometer` and `mile` will fall back to the default measurement for the current locale. Meters and feets will be used when the presented distances are small enough. See `DistanceFormatter` for more information. */ - @objc public dynamic var distanceUnit : LengthFormatter.Unit = Locale.current.usesMetric ? .kilometer : .mile + @objc public dynamic var distanceUnit: LengthFormatter.Unit = Locale.current.usesMetric ? .kilometer : .mile var usesMetric: Bool { - get { - switch distanceUnit { - case .kilometer: - return true - case .mile: - return false - default: - return Locale.current.usesMetric - } + switch self.distanceUnit { + case .kilometer: + true + case .mile: + false + default: + Locale.current.usesMetric } } @@ -54,17 +51,17 @@ public class NavigationSettings: NSObject { /// Returns a reflection of this class excluding the `properties` variable. lazy var properties: [Mirror.Child] = { let properties = Mirror(reflecting: self).children - return properties.filter({ (child) -> Bool in + return properties.filter { child -> Bool in if let label = child.label { return label != "properties.storage" && label != "$__lazy_storage_$_properties" // The last is needed to fix a weird crash: https://github.com/mapbox/mapbox-navigation-ios/issues/2262 } return false - }) + } }() override init() { super.init() - for property in properties { + for property in self.properties { guard let key = property.label else { continue } let val = UserDefaults.standard.object(forKey: key.prefixed) ?? value(forKey: key) setValue(val, forKey: key) @@ -82,7 +79,7 @@ public class NavigationSettings: NSObject { override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { var found = false - for property in properties { + for property in self.properties { guard let key = property.label else { continue } if key == keyPath { @@ -102,8 +99,8 @@ public class NavigationSettings: NSObject { } } -extension String { - fileprivate var prefixed: String { - return "MB" + self +private extension String { + var prefixed: String { + "MB" + self } } diff --git a/MapboxCoreNavigation/ProcessInfo.swift b/MapboxCoreNavigation/ProcessInfo.swift index 711bbec9f..c567adb98 100644 --- a/MapboxCoreNavigation/ProcessInfo.swift +++ b/MapboxCoreNavigation/ProcessInfo.swift @@ -1,19 +1,19 @@ #if os(iOS) || os(tvOS) - import UIKit +import UIKit #elseif os(watchOS) - import WatchKit +import WatchKit #endif extension ProcessInfo { static var systemName: String { #if os(iOS) || os(tvOS) - return UIDevice.current.systemName + return UIDevice.current.systemName #elseif os(watchOS) - return WKInterfaceDevice.current.systemName + return WKInterfaceDevice.current.systemName #elseif os(OSX) - return "macOS" + return "macOS" #else - return "unknown" + return "unknown" #endif } diff --git a/MapboxCoreNavigation/ReplayLocationManager.swift b/MapboxCoreNavigation/ReplayLocationManager.swift index 35f1165ca..8a6b8f61f 100644 --- a/MapboxCoreNavigation/ReplayLocationManager.swift +++ b/MapboxCoreNavigation/ReplayLocationManager.swift @@ -1,5 +1,5 @@ -import Foundation import CoreLocation +import Foundation /** `ReplayLocationManager` replays an array of locations exactly as they were @@ -8,7 +8,6 @@ import CoreLocation */ @objc(MBReplayLocationManager) open class ReplayLocationManager: NavigationLocationManager { - /** `speedMultiplier` adjusts the speed of the replay. */ @@ -23,14 +22,12 @@ open class ReplayLocationManager: NavigationLocationManager { */ @objc public var locations: [CLLocation]! { didSet { - currentIndex = 0 + self.currentIndex = 0 } } @objc override open var location: CLLocation? { - get { - return lastKnownLocation - } + lastKnownLocation } public init(locations: [CLLocation]) { @@ -43,34 +40,34 @@ open class ReplayLocationManager: NavigationLocationManager { } override open func startUpdatingLocation() { - startDate = Date() - tick() + self.startDate = Date() + self.tick() } override open func stopUpdatingLocation() { - startDate = nil - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(tick), object: nil) + self.startDate = nil + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.tick), object: nil) } @objc fileprivate func tick() { - guard let startDate = startDate else { return } - let location = locations[currentIndex] + guard let startDate else { return } + let location = self.locations[self.currentIndex] lastKnownLocation = location delegate?.locationManager?(self, didUpdateLocations: [location]) - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(tick), object: nil) + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.tick), object: nil) - if currentIndex < locations.count - 1 { - let nextLocation = locations[currentIndex+1] - let interval = nextLocation.timestamp.timeIntervalSince(location.timestamp) / TimeInterval(speedMultiplier) - let intervalSinceStart = Date().timeIntervalSince(startDate)+interval - let actualInterval = nextLocation.timestamp.timeIntervalSince(locations.first!.timestamp) - let diff = min(max(0, intervalSinceStart-actualInterval), 0.9) // Don't try to resync more than 0.9 seconds per location update - let syncedInterval = interval-diff + if self.currentIndex < self.locations.count - 1 { + let nextLocation = self.locations[self.currentIndex + 1] + let interval = nextLocation.timestamp.timeIntervalSince(location.timestamp) / TimeInterval(self.speedMultiplier) + let intervalSinceStart = Date().timeIntervalSince(startDate) + interval + let actualInterval = nextLocation.timestamp.timeIntervalSince(self.locations.first!.timestamp) + let diff = min(max(0, intervalSinceStart - actualInterval), 0.9) // Don't try to resync more than 0.9 seconds per location update + let syncedInterval = interval - diff - perform(#selector(tick), with: nil, afterDelay: syncedInterval) - currentIndex += 1 + perform(#selector(self.tick), with: nil, afterDelay: syncedInterval) + self.currentIndex += 1 } else { - currentIndex = 0 + self.currentIndex = 0 } } } diff --git a/MapboxCoreNavigation/RouteController.swift b/MapboxCoreNavigation/RouteController.swift index af0c5f226..15b0b1ff6 100644 --- a/MapboxCoreNavigation/RouteController.swift +++ b/MapboxCoreNavigation/RouteController.swift @@ -1,5 +1,5 @@ -import Foundation import CoreLocation +import Foundation import MapboxDirections import Polyline import Turf @@ -9,11 +9,10 @@ import UIKit A `RouteController` tracks the user’s progress along a route, posting notifications as the user reaches significant points along the route. On every location update, the route controller evaluates the user’s location, determining whether the user remains on the route. If not, the route controller calculates a new route. `RouteController` is responsible for the core navigation logic whereas - `NavigationViewController` is responsible for displaying a default drop-in navigation UI. + `NavigationViewController` is responsible for displaying a default drop-in navigation UI. */ @objc(MBRouteController) open class RouteController: NSObject, Router { - /** The number of seconds between attempts to automatically calculate a more optimal route while traveling. */ @@ -38,7 +37,7 @@ open class RouteController: NSObject, Router { @objc public var locationManager: NavigationLocationManager! { didSet { oldValue.delegate = nil - locationManager.delegate = self + self.locationManager.delegate = self } } @@ -62,7 +61,7 @@ open class RouteController: NSObject, Router { Will only be enabled if `tunnelSimulationEnabled` is true. */ - public var tunnelIntersectionManager: TunnelIntersectionManager = TunnelIntersectionManager() + public var tunnelIntersectionManager: TunnelIntersectionManager = .init() /** If true, the first voice announcement is always triggered when beginning a leg. @@ -80,9 +79,9 @@ open class RouteController: NSObject, Router { if let location = locationManager.location { userInfo[.locationKey] = location } - userInfo[.isProactiveKey] = didFindFasterRoute + userInfo[.isProactiveKey] = self.didFindFasterRoute NotificationCenter.default.post(name: .routeControllerDidReroute, object: self, userInfo: userInfo) - movementsAwayFromRoute = 0 + self.movementsAwayFromRoute = 0 } } @@ -120,11 +119,11 @@ open class RouteController: NSObject, Router { public var description: String { switch self { case .ETAUpdate: - return "ETAUpdate" + "ETAUpdate" case .fasterRoute: - return "fasterRoute" + "fasterRoute" case .divertedFromRoute: - return "divertedFromRoute" + "divertedFromRoute" } } } @@ -147,12 +146,12 @@ open class RouteController: NSObject, Router { super.init() self.locationManager.delegate = self - resumeNotifications() + self.resumeNotifications() checkForUpdates() checkForLocationUsageDescription() - tunnelIntersectionManager.delegate = self + self.tunnelIntersectionManager.delegate = self } deinit { @@ -169,7 +168,7 @@ open class RouteController: NSObject, Router { } func resumeNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(applicationWillTerminate(_:)), name: UIApplication.willTerminateNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.applicationWillTerminate(_:)), name: UIApplication.willTerminateNotification, object: nil) } func suspendNotifications() { @@ -177,7 +176,7 @@ open class RouteController: NSObject, Router { } @objc private func applicationWillTerminate(_ notification: NSNotification) { - endNavigation() + self.endNavigation() } /** @@ -186,18 +185,18 @@ open class RouteController: NSObject, Router { Will continue monitoring until `suspendLocationUpdates()` is called. */ @objc public func resume() { - locationManager.delegate = self - locationManager.startUpdatingLocation() - locationManager.startUpdatingHeading() + self.locationManager.delegate = self + self.locationManager.startUpdatingLocation() + self.locationManager.startUpdatingHeading() } /** Stops monitoring the user’s location along the route. */ @objc public func suspendLocationUpdates() { - locationManager.stopUpdatingLocation() - locationManager.stopUpdatingHeading() - locationManager.delegate = nil + self.locationManager.stopUpdatingLocation() + self.locationManager.stopUpdatingHeading() + self.locationManager.delegate = nil NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(interpolateLocation), object: nil) } @@ -205,8 +204,8 @@ open class RouteController: NSObject, Router { Ends the current navigation session. */ @objc public func endNavigation() { - suspendLocationUpdates() - suspendNotifications() + self.suspendLocationUpdates() + self.suspendNotifications() } /** @@ -214,17 +213,16 @@ open class RouteController: NSObject, Router { - seeAlso: snappedLocation, rawLocation */ @objc public var location: CLLocation? { - // If there is no snapped location, and the rawLocation course is unqualified, use the user's heading as long as it is accurate. - if snappedLocation == nil, - let heading = heading, - let loc = rawLocation, - !loc.course.isQualified, - heading.trueHeading.isQualified { + if self.snappedLocation == nil, + let heading, + let loc = rawLocation, + !loc.course.isQualified, + heading.trueHeading.isQualified { return CLLocation(coordinate: loc.coordinate, altitude: loc.altitude, horizontalAccuracy: loc.horizontalAccuracy, verticalAccuracy: loc.verticalAccuracy, course: heading.trueHeading, speed: loc.speed, timestamp: loc.timestamp) } - return snappedLocation ?? rawLocation + return self.snappedLocation ?? self.rawLocation } /** @@ -232,7 +230,7 @@ open class RouteController: NSObject, Router { - important: If the rawLocation is outside of the route snapping tolerances, this value is nil. */ var snappedLocation: CLLocation? { - return rawLocation?.snapped(to: routeProgress.currentLegProgress) + self.rawLocation?.snapped(to: self.routeProgress.currentLegProgress) } var heading: CLHeading? @@ -243,16 +241,16 @@ open class RouteController: NSObject, Router { */ var rawLocation: CLLocation? { didSet { - updateDistanceToManeuver() + self.updateDistanceToManeuver() } } func updateDistanceToManeuver() { guard let coordinates = routeProgress.currentLegProgress.currentStep.coordinates, let coordinate = rawLocation?.coordinate else { - userSnapToStepDistanceFromManeuver = nil + self.userSnapToStepDistanceFromManeuver = nil return } - userSnapToStepDistanceFromManeuver = Polyline(coordinates).distance(from: coordinate) + self.userSnapToStepDistanceFromManeuver = Polyline(coordinates).distance(from: coordinate) } /** @@ -273,34 +271,31 @@ open class RouteController: NSObject, Router { } // MARK: - Pre-defined routes for testing - private lazy var testA12ToVeenendaalNormalWithTraffic = { - Route( - jsonFileName: "A12-To-Veenendaal-Normal-With-Big-Trafficjam", - waypoints: [ - CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), - CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) - ], - bundle: .mapboxCoreNavigation, - accessToken: "nonsense" - ) - }() + + private lazy var testA12ToVeenendaalNormalWithTraffic = Route( + jsonFileName: "A12-To-Veenendaal-Normal-With-Big-Trafficjam", + waypoints: [ + CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), + CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) + ], + bundle: .mapboxCoreNavigation, + accessToken: "nonsense" + ) - private lazy var testA12ToVeenendaalNormal = { - Route( - jsonFileName: "A12-To-Veenendaal-Normal", - waypoints: [ - CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), - CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) - ], - bundle: .mapboxCoreNavigation, - accessToken: "nonsense" - ) - }() + private lazy var testA12ToVeenendaalNormal = Route( + jsonFileName: "A12-To-Veenendaal-Normal", + waypoints: [ + CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), + CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) + ], + bundle: .mapboxCoreNavigation, + accessToken: "nonsense" + ) } // MARK: - CLLocationManagerDelegate -extension RouteController: CLLocationManagerDelegate { +extension RouteController: CLLocationManagerDelegate { @objc func interpolateLocation() { guard let location = locationManager.lastKnownLocation else { return } guard let coordinates = routeProgress.route.coordinates else { return } @@ -308,12 +303,12 @@ extension RouteController: CLLocationManagerDelegate { let distance = location.speed as CLLocationDistance - guard let interpolatedCoordinate = polyline.coordinateFromStart(distance: routeProgress.distanceTraveled+distance) else { + guard let interpolatedCoordinate = polyline.coordinateFromStart(distance: routeProgress.distanceTraveled + distance) else { return } var course = location.course - if let upcomingCoordinate = polyline.coordinateFromStart(distance: routeProgress.distanceTraveled+(distance*2)) { + if let upcomingCoordinate = polyline.coordinateFromStart(distance: routeProgress.distanceTraveled + (distance * 2)) { course = interpolatedCoordinate.direction(to: upcomingCoordinate) } @@ -325,43 +320,40 @@ extension RouteController: CLLocationManagerDelegate { speed: location.speed, timestamp: Date()) - self.locationManager(locationManager, didUpdateLocations: [interpolatedLocation]) + self.locationManager(self.locationManager, didUpdateLocations: [interpolatedLocation]) } @objc public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { - heading = newHeading + self.heading = newHeading } @objc public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - let filteredLocations = locations.filter { - return $0.isQualified - } + let filteredLocations = locations.filter(\.isQualified) - if !filteredLocations.isEmpty, hasFoundOneQualifiedLocation == false { - hasFoundOneQualifiedLocation = true + if !filteredLocations.isEmpty, self.hasFoundOneQualifiedLocation == false { + self.hasFoundOneQualifiedLocation = true } - let currentStepProgress = routeProgress.currentLegProgress.currentStepProgress + let currentStepProgress = self.routeProgress.currentLegProgress.currentStepProgress var potentialLocation: CLLocation? // `filteredLocations` contains qualified locations if let lastFiltered = filteredLocations.last { potentialLocation = lastFiltered - // `filteredLocations` does not contain good locations and we have found at least one good location previously. - } else if hasFoundOneQualifiedLocation { + // `filteredLocations` does not contain good locations and we have found at least one good location previously. + } else if self.hasFoundOneQualifiedLocation { if let lastLocation = locations.last, delegate?.routeController?(self, shouldDiscard: lastLocation) ?? true { - // Allow the user puck to advance. A stationary puck is not great. self.rawLocation = lastLocation // Check for a tunnel intersection at the current step we found the bad location update. - tunnelIntersectionManager.checkForTunnelIntersection(at: lastLocation, routeProgress: routeProgress) + self.tunnelIntersectionManager.checkForTunnelIntersection(at: lastLocation, routeProgress: self.routeProgress) return } - // This case handles the first location. - // This location is not a good location, but we need the rest of the UI to update and at least show something. + // This case handles the first location. + // This location is not a good location, but we need the rest of the UI to update and at least show something. } else if let lastLocation = locations.last { potentialLocation = lastLocation } @@ -372,17 +364,17 @@ extension RouteController: CLLocationManagerDelegate { self.rawLocation = location - delegate?.routeController?(self, didUpdate: [location]) + self.delegate?.routeController?(self, didUpdate: [location]) - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(interpolateLocation), object: nil) + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.interpolateLocation), object: nil) - if isDeadReckoningEnabled { - perform(#selector(interpolateLocation), with: nil, afterDelay: 1.1) + if self.isDeadReckoningEnabled { + perform(#selector(self.interpolateLocation), with: nil, afterDelay: 1.1) } let currentStep = currentStepProgress.step - updateIntersectionIndex(for: currentStepProgress) + self.updateIntersectionIndex(for: currentStepProgress) // Notify observers if the step’s remaining distance has changed. let polyline = Polyline(routeProgress.currentLegProgress.currentStep.coordinates!) if let closestCoordinate = polyline.closestCoordinate(to: location.coordinate) { @@ -390,38 +382,38 @@ extension RouteController: CLLocationManagerDelegate { let distanceTraveled = currentStep.distance - remainingDistance currentStepProgress.distanceTraveled = distanceTraveled NotificationCenter.default.post(name: .routeControllerProgressDidChange, object: self, userInfo: [ - RouteControllerNotificationUserInfoKey.routeProgressKey: routeProgress, - RouteControllerNotificationUserInfoKey.locationKey: self.location!, //guaranteed value - RouteControllerNotificationUserInfoKey.rawLocationKey: location //raw - ]) + RouteControllerNotificationUserInfoKey.routeProgressKey: self.routeProgress, + RouteControllerNotificationUserInfoKey.locationKey: self.location!, // guaranteed value + RouteControllerNotificationUserInfoKey.rawLocationKey: location // raw + ]) // Check for a tunnel intersection whenever the current route step progresses. - tunnelIntersectionManager.checkForTunnelIntersection(at: location, routeProgress: routeProgress) + self.tunnelIntersectionManager.checkForTunnelIntersection(at: location, routeProgress: self.routeProgress) } - updateDistanceToIntersection(from: location) - updateRouteStepProgress(for: location) - updateRouteLegProgress(for: location) - updateVisualInstructionProgress() + self.updateDistanceToIntersection(from: location) + self.updateRouteStepProgress(for: location) + self.updateRouteLegProgress(for: location) + self.updateVisualInstructionProgress() - guard userIsOnRoute(location) || !(delegate?.routeController?(self, shouldRerouteFrom: location) ?? true) else { - rerouteForDiversion(from: location, along: routeProgress) + guard self.userIsOnRoute(location) || !(self.delegate?.routeController?(self, shouldRerouteFrom: location) ?? true) else { + self.rerouteForDiversion(from: location, along: self.routeProgress) return } - updateSpokenInstructionProgress() + self.updateSpokenInstructionProgress() // Check for faster route given users current location - guard reroutesProactively else { return } + guard self.reroutesProactively else { return } // Only check for faster routes or ETA updates if the user has plenty of time left on the route (10+min) // Except when configured in the SDK that we may - guard routeProgress.durationRemaining > 600 || !shouldCheckForRerouteInLastMinutes else { return } + guard self.routeProgress.durationRemaining > 600 || !self.shouldCheckForRerouteInLastMinutes else { return } // If the user is approaching a maneuver (within 70secs of the maneuver), don't check for a faster routes or ETA updates - guard routeProgress.currentLegProgress.currentStepProgress.durationRemaining > RouteControllerMediumAlertInterval else { return } + guard self.routeProgress.currentLegProgress.currentStepProgress.durationRemaining > RouteControllerMediumAlertInterval else { return } - checkForNewRoute(from: location) + self.checkForNewRoute(from: location) } func updateIntersectionIndex(for currentStepProgress: RouteStepProgress) { @@ -431,19 +423,19 @@ extension RouteController: CLLocationManagerDelegate { } func updateRouteLegProgress(for location: CLLocation) { - let currentDestination = routeProgress.currentLeg.destination + let currentDestination = self.routeProgress.currentLeg.destination guard let remainingVoiceInstructions = routeProgress.currentLegProgress.currentStepProgress.remainingSpokenInstructions else { return } - if routeProgress.currentLegProgress.remainingSteps.count <= 1 && remainingVoiceInstructions.count == 0 && currentDestination != previousArrivalWaypoint { - previousArrivalWaypoint = currentDestination + if self.routeProgress.currentLegProgress.remainingSteps.count <= 1, remainingVoiceInstructions.count == 0, currentDestination != self.previousArrivalWaypoint { + self.previousArrivalWaypoint = currentDestination - routeProgress.currentLegProgress.userHasArrivedAtWaypoint = true + self.routeProgress.currentLegProgress.userHasArrivedAtWaypoint = true - let advancesToNextLeg = delegate?.routeController?(self, didArriveAt: currentDestination) ?? true + let advancesToNextLeg = self.delegate?.routeController?(self, didArriveAt: currentDestination) ?? true - if !routeProgress.isFinalLeg && advancesToNextLeg { - routeProgress.legIndex += 1 - updateDistanceToManeuver() + if !self.routeProgress.isFinalLeg, advancesToNextLeg { + self.routeProgress.legIndex += 1 + self.updateDistanceToManeuver() } } } @@ -452,17 +444,17 @@ extension RouteController: CLLocationManagerDelegate { Monitors the user's course to see if it is consistently moving away from what we expect the course to be at a given point. */ func userCourseIsOnRoute(_ location: CLLocation) -> Bool { - let nearByCoordinates = routeProgress.currentLegProgress.nearbyCoordinates + let nearByCoordinates = self.routeProgress.currentLegProgress.nearbyCoordinates guard let calculatedCourseForLocationOnStep = location.interpolatedCourse(along: nearByCoordinates) else { return true } let maxUpdatesAwayFromRouteGivenAccuracy = Int(location.horizontalAccuracy / Double(RouteControllerIncorrectCourseMultiplier)) - if movementsAwayFromRoute >= max(RouteControllerMinNumberOfInCorrectCourses, maxUpdatesAwayFromRouteGivenAccuracy) { + if self.movementsAwayFromRoute >= max(RouteControllerMinNumberOfInCorrectCourses, maxUpdatesAwayFromRouteGivenAccuracy) { return false } else if location.shouldSnapCourse(toRouteWith: calculatedCourseForLocationOnStep) { - movementsAwayFromRoute = 0 + self.movementsAwayFromRoute = 0 } else { - movementsAwayFromRoute += 1 + self.movementsAwayFromRoute += 1 } return true @@ -474,9 +466,8 @@ extension RouteController: CLLocationManagerDelegate { If the user is not on the route, they should be rerouted. */ @objc public func userIsOnRoute(_ location: CLLocation) -> Bool { - // If the user has arrived, do not continue monitor reroutes, step progress, etc - guard !routeProgress.currentLegProgress.userHasArrivedAtWaypoint && (delegate?.routeController?(self, shouldPreventReroutesWhenArrivingAt: routeProgress.currentLeg.destination) ?? true) else { + guard !self.routeProgress.currentLegProgress.userHasArrivedAtWaypoint && (self.delegate?.routeController?(self, shouldPreventReroutesWhenArrivingAt: self.routeProgress.currentLeg.destination) ?? true) else { return true } @@ -484,10 +475,10 @@ extension RouteController: CLLocationManagerDelegate { let radius = max(reroutingTolerance, RouteControllerManeuverZoneRadius) // This checks all coordinates of the step, if user is close to the step, they on the route - let isCloseToCurrentStep = location.isWithin(radius, of: routeProgress.currentLegProgress.currentStep) + let isCloseToCurrentStep = location.isWithin(radius, of: self.routeProgress.currentLegProgress.currentStep) // If the user is either close to the current step or at least driving in the right direction, we assume the user is on route - guard !isCloseToCurrentStep || !userCourseIsOnRoute(location) else { return true } + guard !isCloseToCurrentStep || !self.userCourseIsOnRoute(location) else { return true } // Check and see if the user is near a future step. guard let nearestStep = routeProgress.currentLegProgress.closestStep(to: location.coordinate) else { @@ -496,8 +487,8 @@ extension RouteController: CLLocationManagerDelegate { if nearestStep.distance < RouteControllerUserLocationSnappingDistance { // Only advance the stepIndex to a future step if the step is new. Otherwise, the user is still on the current step. - if nearestStep.index != routeProgress.currentLegProgress.stepIndex { - advanceStepIndex(to: nearestStep.index) + if nearestStep.index != self.routeProgress.currentLegProgress.stepIndex { + self.advanceStepIndex(to: nearestStep.index) } return true } @@ -506,7 +497,7 @@ extension RouteController: CLLocationManagerDelegate { } func checkForNewRoute(from location: CLLocation) { - guard !isFindingFasterRoute else { + guard !self.isFindingFasterRoute else { return } @@ -514,24 +505,24 @@ extension RouteController: CLLocationManagerDelegate { return } - guard let lastLocationDate = lastLocationDate else { + guard let lastLocationDate else { self.lastLocationDate = location.timestamp return } // Only check every so often for a faster route. - guard location.timestamp.timeIntervalSince(lastLocationDate) >= routeControllerProactiveReroutingInterval else { + guard location.timestamp.timeIntervalSince(lastLocationDate) >= self.routeControllerProactiveReroutingInterval else { return } - let durationRemaining = routeProgress.durationRemaining + let durationRemaining = self.routeProgress.durationRemaining - isFindingFasterRoute = true + self.isFindingFasterRoute = true print("[RouteController] Checking for faster/updated route...") - getDirections(from: location, along: routeProgress) { [weak self] mostSimilarRoute, routes, error in - guard let self = self else { return } + self.getDirections(from: location, along: self.routeProgress) { [weak self] mostSimilarRoute, routes, _ in + guard let self else { return } // Every request should reset the lastLocationDate, else we spam the server by calling this method every location update. // If the call fails, tough luck buddy! Then wait until the next interval before retrying @@ -540,8 +531,7 @@ extension RouteController: CLLocationManagerDelegate { // Also only do one 'findFasterRoute' call per time self.isFindingFasterRoute = false - - guard let route = mostSimilarRoute, let routes = routes else { + guard let route = mostSimilarRoute, let routes else { return } @@ -555,7 +545,7 @@ extension RouteController: CLLocationManagerDelegate { } // Current and First step of old and new route should be significant enough of a maneuver before applying a faster route, so we don't apply the route just before a maneuver will occur - let isFirstStepSignificant = firstStep.expectedTravelTime >= RouteControllerMediumAlertInterval && routeProgress.currentLegProgress.currentStepProgress.durationRemaining > RouteControllerMediumAlertInterval + let isFirstStepSignificant = firstStep.expectedTravelTime >= RouteControllerMediumAlertInterval && self.routeProgress.currentLegProgress.currentStepProgress.durationRemaining > RouteControllerMediumAlertInterval // Current maneuver should correspond to the next maneuver in the new route let hasSameUpcomingManeuver = firstLeg.steps.indices.contains(1) ? currentUpcomingManeuver == firstLeg.steps[1] : false @@ -578,20 +568,20 @@ extension RouteController: CLLocationManagerDelegate { print("[RouteController] Found faster route") // Need to set this for notifications being sent - didFindFasterRoute = true + self.didFindFasterRoute = true // If the upcoming maneuver in the new route is the same as the current upcoming maneuver, don't announce it again, just set new progress - routeProgress = RouteProgress(route: mostSimilarRoute, legIndex: 0, spokenInstructionIndex: routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex) + self.routeProgress = RouteProgress(route: mostSimilarRoute, legIndex: 0, spokenInstructionIndex: self.routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex) // Let delegate know - delegate?.routeController?(self, didRerouteAlong: mostSimilarRoute, reason: .fasterRoute) + self.delegate?.routeController?(self, didRerouteAlong: mostSimilarRoute, reason: .fasterRoute) // Reset flag for notification - didFindFasterRoute = false + self.didFindFasterRoute = false } // If route is not faster, but matches criteria and we get a match that is similar enough (so we don't apply a route alternative that the user doesn't want), we will apply the route too - else if shouldApplySlowerRoute, let matchingRoute = Self.bestMatch(for: self.routeProgress.route, and: allRoutes) { + else if shouldApplySlowerRoute, let matchingRoute = Self.bestMatch(for: routeProgress.route, and: allRoutes) { // Check if the time difference is more than 30 seconds between best match and current ETA for extra measure let isExpectedTravelTimeChangedSignificantly = abs(routeProgress.durationRemaining - matchingRoute.route.expectedTravelTime) > 30 print("[RouteController] New ETA differs more than 30s from current ETA: \(isExpectedTravelTimeChangedSignificantly)") @@ -599,50 +589,50 @@ extension RouteController: CLLocationManagerDelegate { var routeToApply = matchingRoute.route // When testing flag is flipped, return instead one of the testing routes - if shouldReturnTestingETAUpdateReroutes { + if self.shouldReturnTestingETAUpdateReroutes { let rightOrLeft = Bool.random() - routeToApply = rightOrLeft ? testA12ToVeenendaalNormal : testA12ToVeenendaalNormalWithTraffic + routeToApply = rightOrLeft ? self.testA12ToVeenendaalNormal : self.testA12ToVeenendaalNormalWithTraffic print("[RouteController] Testing route: ON") } - if isExpectedTravelTimeChangedSignificantly || shouldReturnTestingETAUpdateReroutes { + if isExpectedTravelTimeChangedSignificantly || self.shouldReturnTestingETAUpdateReroutes { // Set new route and inform delegates print("[RouteController] Found matching route \(matchingRoute.matchPercentage)%, updating ETA...") - print("[RouteController] Duration remaining CURRENT: \(routeProgress.durationRemaining)") + print("[RouteController] Duration remaining CURRENT: \(self.routeProgress.durationRemaining)") print("[RouteController] Expected travel time: \(matchingRoute.route.expectedTravelTime)") print("[RouteController] Set the new route") // Don't announce new route - routeProgress = RouteProgress(route: routeToApply, legIndex: 0, spokenInstructionIndex: routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex) + self.routeProgress = RouteProgress(route: routeToApply, legIndex: 0, spokenInstructionIndex: self.routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex) // Inform delegate - delegate?.routeController?(self, didRerouteAlong: routeToApply, reason: .ETAUpdate) + self.delegate?.routeController?(self, didRerouteAlong: routeToApply, reason: .ETAUpdate) } } } /// Reroutes the user when the user isn't on the route anymore func rerouteForDiversion(from location: CLLocation, along progress: RouteProgress) { - if let lastRerouteLocation = lastRerouteLocation { + if let lastRerouteLocation { guard location.distance(from: lastRerouteLocation) >= RouteControllerMaximumDistanceBeforeRecalculating else { return } } - if isRerouting { + if self.isRerouting { return } - isRerouting = true + self.isRerouting = true - delegate?.routeController?(self, willRerouteFrom: location) + self.delegate?.routeController?(self, willRerouteFrom: location) NotificationCenter.default.post(name: .routeControllerWillReroute, object: self, userInfo: [ RouteControllerNotificationUserInfoKey.locationKey: location ]) - self.lastRerouteLocation = location + lastRerouteLocation = location - getDirections(from: location, along: progress) { [weak self] (route, _, error) in + self.getDirections(from: location, along: progress) { [weak self] route, _, error in guard let strongSelf = self else { return } @@ -650,7 +640,7 @@ extension RouteController: CLLocationManagerDelegate { // Update the rerouting state strongSelf.isRerouting = false - if let error = error { + if let error { strongSelf.delegate?.routeController?(strongSelf, didFailToRerouteWith: error) NotificationCenter.default.post(name: .routeControllerDidFailToReroute, object: self, userInfo: [ RouteControllerNotificationUserInfoKey.routingErrorKey: error @@ -658,7 +648,7 @@ extension RouteController: CLLocationManagerDelegate { return } - guard let route = route else { return } + guard let route else { return } strongSelf.routeProgress = RouteProgress(route: route, legIndex: 0) strongSelf.routeProgress.currentLegProgress.stepIndex = 0 @@ -668,19 +658,19 @@ extension RouteController: CLLocationManagerDelegate { private func checkForUpdates() { #if TARGET_IPHONE_SIMULATOR - guard let version = Bundle(for: RouteController.self).object(forInfoDictionaryKey: "CFBundleShortVersionString") else { return } - let latestVersion = String(describing: version) - _ = URLSession.shared.dataTask(with: URL(string: "https://www.mapbox.com/mapbox-navigation-ios/latest_version")!, completionHandler: { (data, response, error) in - if let _ = error { return } - guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return } + guard let version = Bundle(for: RouteController.self).object(forInfoDictionaryKey: "CFBundleShortVersionString") else { return } + let latestVersion = String(describing: version) + _ = URLSession.shared.dataTask(with: URL(string: "https://www.mapbox.com/mapbox-navigation-ios/latest_version")!, completionHandler: { data, response, error in + if let _ = error { return } + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return } - guard let data = data, let currentVersion = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .newlines) else { return } + guard let data, let currentVersion = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .newlines) else { return } - if latestVersion != currentVersion { - let updateString = NSLocalizedString("UPDATE_AVAILABLE", bundle: .mapboxCoreNavigation, value: "Mapbox Navigation SDK for iOS version %@ is now available.", comment: "Inform developer an update is available") - print(String.localizedStringWithFormat(updateString, latestVersion), "https://github.com/mapbox/mapbox-navigation-ios/releases/tag/v\(latestVersion)") - } - }).resume() + if latestVersion != currentVersion { + let updateString = NSLocalizedString("UPDATE_AVAILABLE", bundle: .mapboxCoreNavigation, value: "Mapbox Navigation SDK for iOS version %@ is now available.", comment: "Inform developer an update is available") + print(String.localizedStringWithFormat(updateString, latestVersion), "https://github.com/mapbox/mapbox-navigation-ios/releases/tag/v\(latestVersion)") + } + }).resume() #endif } @@ -688,15 +678,15 @@ extension RouteController: CLLocationManagerDelegate { guard let _ = Bundle.main.bundleIdentifier else { return } - if Bundle.main.locationAlwaysUsageDescription == nil && Bundle.main.locationWhenInUseUsageDescription == nil && Bundle.main.locationAlwaysAndWhenInUseUsageDescription == nil { + if Bundle.main.locationAlwaysUsageDescription == nil, Bundle.main.locationWhenInUseUsageDescription == nil, Bundle.main.locationAlwaysAndWhenInUseUsageDescription == nil { preconditionFailure("This application’s Info.plist file must include a NSLocationWhenInUseUsageDescription. See https://developer.apple.com/documentation/corelocation for more information.") } } - func getDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ mostSimilarRoute: Route?, _ routes: [Route]?, _ error: Error?)->Void) { - if(delegate?.routeControllerGetDirections?(from: location, along: progress, completion: completion) != true){ + func getDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ mostSimilarRoute: Route?, _ routes: [Route]?, _ error: Error?) -> Void) { + if self.delegate?.routeControllerGetDirections?(from: location, along: progress, completion: completion) != true { // Run the default route calculation if the delegate method is not defined or does not return true - routeTask?.cancel() + self.routeTask?.cancel() let options = progress.reroutingOptions(with: location) self.lastRerouteLocation = location @@ -705,7 +695,7 @@ extension RouteController: CLLocationManagerDelegate { completion(mostSimilarRoute, routes, error) } - routeTask = directions.calculate(options) {(waypoints, potentialRoutes, potentialError) in + self.routeTask = self.directions.calculate(options) { _, potentialRoutes, potentialError in guard let routes = potentialRoutes else { return complete(nil, nil, potentialError) @@ -718,33 +708,31 @@ extension RouteController: CLLocationManagerDelegate { } } - - func updateDistanceToIntersection(from location: CLLocation) { guard var intersections = routeProgress.currentLegProgress.currentStepProgress.step.intersections else { return } - let currentStepProgress = routeProgress.currentLegProgress.currentStepProgress + let currentStepProgress = self.routeProgress.currentLegProgress.currentStepProgress // The intersections array does not include the upcoming maneuver intersection. if let upcomingStep = routeProgress.currentLegProgress.upComingStep, let upcomingIntersection = upcomingStep.intersections, let firstUpcomingIntersection = upcomingIntersection.first { intersections += [firstUpcomingIntersection] } - routeProgress.currentLegProgress.currentStepProgress.intersectionsIncludingUpcomingManeuverIntersection = intersections + self.routeProgress.currentLegProgress.currentStepProgress.intersectionsIncludingUpcomingManeuverIntersection = intersections if let upcomingIntersection = routeProgress.currentLegProgress.currentStepProgress.upcomingIntersection { - routeProgress.currentLegProgress.currentStepProgress.userDistanceToUpcomingIntersection = Polyline(currentStepProgress.step.coordinates!).distance(from: location.coordinate, to: upcomingIntersection.location) + self.routeProgress.currentLegProgress.currentStepProgress.userDistanceToUpcomingIntersection = Polyline(currentStepProgress.step.coordinates!).distance(from: location.coordinate, to: upcomingIntersection.location) } - if routeProgress.currentLegProgress.currentStepProgress.intersectionDistances == nil { - routeProgress.currentLegProgress.currentStepProgress.intersectionDistances = [CLLocationDistance]() - updateIntersectionDistances() + if self.routeProgress.currentLegProgress.currentStepProgress.intersectionDistances == nil { + self.routeProgress.currentLegProgress.currentStepProgress.intersectionDistances = [CLLocationDistance]() + self.updateIntersectionDistances() } } func updateRouteStepProgress(for location: CLLocation) { - guard routeProgress.currentLegProgress.remainingSteps.count > 0 else { return } + guard self.routeProgress.currentLegProgress.remainingSteps.count > 0 else { return } - guard let userSnapToStepDistanceFromManeuver = userSnapToStepDistanceFromManeuver else { return } + guard let userSnapToStepDistanceFromManeuver else { return } var courseMatchesManeuverFinalHeading = false // Bearings need to normalized so when the `finalHeading` is 359 and the user heading is 1, @@ -768,54 +756,52 @@ extension RouteController: CLLocationManagerDelegate { } } - let step = routeProgress.currentLegProgress.upComingStep?.maneuverLocation ?? routeProgress.currentLegProgress.currentStep.maneuverLocation + let step = self.routeProgress.currentLegProgress.upComingStep?.maneuverLocation ?? self.routeProgress.currentLegProgress.currentStep.maneuverLocation let userAbsoluteDistance = step.distance(to: location.coordinate) - let lastKnownUserAbsoluteDistance = routeProgress.currentLegProgress.currentStepProgress.userDistanceToManeuverLocation + let lastKnownUserAbsoluteDistance = self.routeProgress.currentLegProgress.currentStepProgress.userDistanceToManeuverLocation - if userSnapToStepDistanceFromManeuver <= RouteControllerManeuverZoneRadius && - (courseMatchesManeuverFinalHeading || (userAbsoluteDistance > lastKnownUserAbsoluteDistance && lastKnownUserAbsoluteDistance > RouteControllerManeuverZoneRadius)) { - advanceStepIndex() + if userSnapToStepDistanceFromManeuver <= RouteControllerManeuverZoneRadius, + courseMatchesManeuverFinalHeading || (userAbsoluteDistance > lastKnownUserAbsoluteDistance && lastKnownUserAbsoluteDistance > RouteControllerManeuverZoneRadius) { + self.advanceStepIndex() } - routeProgress.currentLegProgress.currentStepProgress.userDistanceToManeuverLocation = userAbsoluteDistance + self.routeProgress.currentLegProgress.currentStepProgress.userDistanceToManeuverLocation = userAbsoluteDistance } func updateSpokenInstructionProgress() { - guard let userSnapToStepDistanceFromManeuver = userSnapToStepDistanceFromManeuver else { return } + guard let userSnapToStepDistanceFromManeuver else { return } guard let spokenInstructions = routeProgress.currentLegProgress.currentStepProgress.remainingSpokenInstructions else { return } - let firstInstructionOnFirstStep = speakFirstInstructionOnFirstStep - // Always give the first voice announcement when beginning a leg. - ? routeProgress.currentLegProgress.stepIndex == 0 && routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex == 0 - : false + let firstInstructionOnFirstStep = self.speakFirstInstructionOnFirstStep + // Always give the first voice announcement when beginning a leg. + ? self.routeProgress.currentLegProgress.stepIndex == 0 && self.routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex == 0 + : false for voiceInstruction in spokenInstructions { if userSnapToStepDistanceFromManeuver <= voiceInstruction.distanceAlongStep || firstInstructionOnFirstStep { - NotificationCenter.default.post(name: .routeControllerDidPassSpokenInstructionPoint, object: self, userInfo: [ - RouteControllerNotificationUserInfoKey.routeProgressKey: routeProgress + RouteControllerNotificationUserInfoKey.routeProgressKey: self.routeProgress ]) - routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex += 1 + self.routeProgress.currentLegProgress.currentStepProgress.spokenInstructionIndex += 1 return } } } func updateVisualInstructionProgress() { - guard let userSnapToStepDistanceFromManeuver = userSnapToStepDistanceFromManeuver else { return } + guard let userSnapToStepDistanceFromManeuver else { return } guard let visualInstructions = routeProgress.currentLegProgress.currentStepProgress.remainingVisualInstructions else { return } - let firstInstructionOnFirstStep = routeProgress.currentLegProgress.stepIndex == 0 && routeProgress.currentLegProgress.currentStepProgress.visualInstructionIndex == 0 + let firstInstructionOnFirstStep = self.routeProgress.currentLegProgress.stepIndex == 0 && self.routeProgress.currentLegProgress.currentStepProgress.visualInstructionIndex == 0 for visualInstruction in visualInstructions { if userSnapToStepDistanceFromManeuver <= visualInstruction.distanceAlongStep || firstInstructionOnFirstStep { - NotificationCenter.default.post(name: .routeControllerDidPassVisualInstructionPoint, object: self, userInfo: [ - RouteControllerNotificationUserInfoKey.routeProgressKey: routeProgress - ]) + RouteControllerNotificationUserInfoKey.routeProgressKey: self.routeProgress + ]) - routeProgress.currentLegProgress.currentStepProgress.visualInstructionIndex += 1 + self.routeProgress.currentLegProgress.currentStepProgress.visualInstructionIndex += 1 return } } @@ -823,21 +809,21 @@ extension RouteController: CLLocationManagerDelegate { func advanceStepIndex(to: Array.Index? = nil) { if let forcedStepIndex = to { - guard forcedStepIndex < routeProgress.currentLeg.steps.count else { return } - routeProgress.currentLegProgress.stepIndex = forcedStepIndex + guard forcedStepIndex < self.routeProgress.currentLeg.steps.count else { return } + self.routeProgress.currentLegProgress.stepIndex = forcedStepIndex } else { - routeProgress.currentLegProgress.stepIndex += 1 + self.routeProgress.currentLegProgress.stepIndex += 1 } - updateIntersectionDistances() - updateDistanceToManeuver() + self.updateIntersectionDistances() + self.updateDistanceToManeuver() } func updateIntersectionDistances() { if let coordinates = routeProgress.currentLegProgress.currentStep.coordinates, let intersections = routeProgress.currentLegProgress.currentStep.intersections { let polyline = Polyline(coordinates) let distances: [CLLocationDistance] = intersections.map { polyline.distance(from: coordinates.first, to: $0.location) } - routeProgress.currentLegProgress.currentStepProgress.intersectionDistances = distances + self.routeProgress.currentLegProgress.currentStepProgress.intersectionDistances = distances } } @@ -879,18 +865,16 @@ extension RouteController: CLLocationManagerDelegate { return bestMatch } - } // MARK: - TunnelIntersectionManagerDelegate + extension RouteController: TunnelIntersectionManagerDelegate { public func tunnelIntersectionManager(_ manager: TunnelIntersectionManager, willEnableAnimationAt location: CLLocation) { - tunnelIntersectionManager.enableTunnelAnimation(routeController: self, routeProgress: routeProgress) + self.tunnelIntersectionManager.enableTunnelAnimation(routeController: self, routeProgress: self.routeProgress) } public func tunnelIntersectionManager(_ manager: TunnelIntersectionManager, willDisableAnimationAt location: CLLocation) { - tunnelIntersectionManager.suspendTunnelAnimation(at: location, routeController: self) + self.tunnelIntersectionManager.suspendTunnelAnimation(at: location, routeController: self) } } - - diff --git a/MapboxCoreNavigation/RouteControllerDelegate.swift b/MapboxCoreNavigation/RouteControllerDelegate.swift index 346c71e6e..8bb2f8654 100644 --- a/MapboxCoreNavigation/RouteControllerDelegate.swift +++ b/MapboxCoreNavigation/RouteControllerDelegate.swift @@ -1,8 +1,7 @@ -import Foundation import CoreLocation +import Foundation import MapboxDirections - /** The `RouteControllerDelegate` protocol provides methods for responding to significant events during the user’s traversal of a route monitored by a `RouteController`. */ @@ -103,7 +102,6 @@ public protocol RouteControllerDelegate: AnyObject { @objc(routeController:shouldPreventReroutesWhenArrivingAtWaypoint:) optional func routeController(_ routeController: RouteController, shouldPreventReroutesWhenArrivingAt waypoint: Waypoint) -> Bool - /** Called when the route controller will disable battery monitoring. @@ -116,15 +114,15 @@ public protocol RouteControllerDelegate: AnyObject { optional func routeControllerShouldDisableBatteryMonitoring(_ routeController: RouteController) -> Bool /** - Allows to customise the calculation of a route. + Allows to customise the calculation of a route. - If you want to overwrite the default rerouting logic, return true. + If you want to overwrite the default rerouting logic, return true. - - parameter from: The current location of the user - - parameter along: The route progress - - parameter completion: Callback function when either the route was successfully calculated or if there was an error - - return: True to prevent the route controller from running its own rerouting logic - */ + - parameter from: The current location of the user + - parameter along: The route progress + - parameter completion: Callback function when either the route was successfully calculated or if there was an error + - return: True to prevent the route controller from running its own rerouting logic + */ @objc(routeControllerGetDirections:along:completion:) - optional func routeControllerGetDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ mostSimilarRoute: Route?, _ routes: [Route]?, _ error: Error?)->Void) -> Bool + optional func routeControllerGetDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ mostSimilarRoute: Route?, _ routes: [Route]?, _ error: Error?) -> Void) -> Bool } diff --git a/MapboxCoreNavigation/RouteOptions.swift b/MapboxCoreNavigation/RouteOptions.swift index c3c8b9059..35382ee74 100644 --- a/MapboxCoreNavigation/RouteOptions.swift +++ b/MapboxCoreNavigation/RouteOptions.swift @@ -3,12 +3,12 @@ import MapboxDirections import MapboxDirectionsObjc extension RouteOptions { - internal var activityType: CLActivityType { - switch self.profileIdentifier { + var activityType: CLActivityType { + switch profileIdentifier { case MBDirectionsProfileIdentifier.cycling, MBDirectionsProfileIdentifier.walking: - return .fitness + .fitness default: - return .automotiveNavigation + .automotiveNavigation } } @@ -20,7 +20,7 @@ extension RouteOptions { */ public func without(waypoint: Waypoint) -> RouteOptions { let waypointsWithoutSpecified = waypoints.filter { $0 != waypoint } - let copy = self.copy() as! RouteOptions + let copy = copy() as! RouteOptions copy.waypoints = waypointsWithoutSpecified return copy diff --git a/MapboxCoreNavigation/RouteProgress.swift b/MapboxCoreNavigation/RouteProgress.swift index fe1ca39c7..66305811d 100644 --- a/MapboxCoreNavigation/RouteProgress.swift +++ b/MapboxCoreNavigation/RouteProgress.swift @@ -10,7 +10,6 @@ import CarPlay */ @objc(MBRouteProgress) open class RouteProgress: NSObject { - private static let reroutingAccuracy: CLLocationAccuracy = 90 /** @@ -23,9 +22,9 @@ open class RouteProgress: NSObject { */ @objc public var legIndex: Int { didSet { - assert(legIndex >= 0 && legIndex < route.legs.endIndex) + assert(self.legIndex >= 0 && self.legIndex < self.route.legs.endIndex) // TODO: Set stepIndex to 0 or last index based on whether leg index was incremented or decremented. - currentLegProgress = RouteLegProgress(leg: currentLeg) + self.currentLegProgress = RouteLegProgress(leg: self.currentLeg) } } @@ -33,50 +32,50 @@ open class RouteProgress: NSObject { If waypoints are provided in the `Route`, this will contain which leg the user is on. */ @objc public var currentLeg: RouteLeg { - return route.legs[legIndex] + self.route.legs[self.legIndex] } /** - Returns true if `currentLeg` is the last leg. - */ + Returns true if `currentLeg` is the last leg. + */ public var isFinalLeg: Bool { guard let lastLeg = route.legs.last else { return false } - return currentLeg == lastLeg + return self.currentLeg == lastLeg } /** Total distance traveled by user along all legs. */ @objc public var distanceTraveled: CLLocationDistance { - return route.legs.prefix(upTo: legIndex).map { $0.distance }.reduce(0, +) + currentLegProgress.distanceTraveled + self.route.legs.prefix(upTo: self.legIndex).map(\.distance).reduce(0, +) + self.currentLegProgress.distanceTraveled } /** Total seconds remaining on all legs. */ @objc public var durationRemaining: TimeInterval { - return route.legs.suffix(from: legIndex + 1).map { $0.expectedTravelTime }.reduce(0, +) + currentLegProgress.durationRemaining + self.route.legs.suffix(from: self.legIndex + 1).map(\.expectedTravelTime).reduce(0, +) + self.currentLegProgress.durationRemaining } /** Number between 0 and 1 representing how far along the `Route` the user has traveled. */ @objc public var fractionTraveled: Double { - return distanceTraveled / route.distance + self.distanceTraveled / self.route.distance } /** Total distance remaining in meters along route. */ @objc public var distanceRemaining: CLLocationDistance { - return route.distance - distanceTraveled + self.route.distance - self.distanceTraveled } /** Number of waypoints remaining on the current route. */ @objc public var remainingWaypoints: [Waypoint] { - return route.legs.suffix(from: legIndex).map { $0.destination } + self.route.legs.suffix(from: self.legIndex).map(\.destination) } /** @@ -97,7 +96,7 @@ open class RouteProgress: NSObject { /** An dictionary containing a `TimeInterval` total per `CongestionLevel`. Only `CongestionLevel` founnd on that step will present. Broken up by leg and then step. */ - public var congestionTimesPerStep: [[[CongestionLevel: TimeInterval]]] = [[[:]]] + public var congestionTimesPerStep: [[[CongestionLevel: TimeInterval]]] = [[[:]]] /** Intializes a new `RouteProgress`. @@ -114,13 +113,12 @@ open class RouteProgress: NSObject { for (legIndex, leg) in route.legs.enumerated() { var maneuverCoordinateIndex = 0 - congestionTimesPerStep.append([]) + self.congestionTimesPerStep.append([]) /// An index into the route’s coordinates and congestionTravelTimesSegmentsByStep that corresponds to a step’s maneuver location. var congestionTravelTimesSegmentsByLeg: [[TimedCongestionLevel]] = [] if let segmentCongestionLevels = leg.segmentCongestionLevels, let expectedSegmentTravelTimes = leg.expectedSegmentTravelTimes { - for step in leg.steps { guard let coordinates = step.coordinates else { continue } let stepCoordinateCount = step.maneuverType == .arrive ? Int(step.coordinateCount) : coordinates.dropLast().count @@ -129,8 +127,8 @@ open class RouteProgress: NSObject { guard nextManeuverCoordinateIndex < segmentCongestionLevels.count else { continue } guard nextManeuverCoordinateIndex < expectedSegmentTravelTimes.count else { continue } - let stepSegmentCongestionLevels = Array(segmentCongestionLevels[maneuverCoordinateIndex..= 0 else { return .unknown } - guard legIndex < congestionTravelTimesSegmentsByStep.count, - currentLegProgress.stepIndex < congestionTravelTimesSegmentsByStep[legIndex].count else { return .unknown } + guard self.legIndex < self.congestionTravelTimesSegmentsByStep.count, + self.currentLegProgress.stepIndex < self.congestionTravelTimesSegmentsByStep[self.legIndex].count else { return .unknown } - let congestionTimesForStep = congestionTravelTimesSegmentsByStep[legIndex][currentLegProgress.stepIndex] + let congestionTimesForStep = self.congestionTravelTimesSegmentsByStep[self.legIndex][self.currentLegProgress.stepIndex] guard coordinatesLeftOnStepCount <= congestionTimesForStep.count else { return .unknown } let remainingCongestionTimesForStep = congestionTimesForStep.suffix(from: coordinatesLeftOnStepCount) - let remainingCongestionTimesForRoute = congestionTimesPerStep[legIndex].suffix(from: currentLegProgress.stepIndex + 1) + let remainingCongestionTimesForRoute = self.congestionTimesPerStep[self.legIndex].suffix(from: self.currentLegProgress.stepIndex + 1) var remainingStepCongestionTotals: [CongestionLevel: TimeInterval] = [:] for stepValues in remainingCongestionTimesForRoute { @@ -173,7 +171,7 @@ open class RouteProgress: NSObject { remainingStepCongestionTotals[segmentCongestion] = (remainingStepCongestionTotals[segmentCongestion] ?? 0) + segmentTime } - if durationRemaining < 60 { + if self.durationRemaining < 60 { return .unknown } else { if let max = remainingStepCongestionTotals.max(by: { a, b in a.value < b.value }) { @@ -185,14 +183,14 @@ open class RouteProgress: NSObject { } func reroutingOptions(with current: CLLocation) -> RouteOptions { - let oldOptions = route.routeOptions + let oldOptions = self.route.routeOptions let user = Waypoint(coordinate: current.coordinate) - if (current.course >= 0) { + if current.course >= 0 { user.heading = current.course user.headingAccuracy = RouteProgress.reroutingAccuracy } - let newWaypoints = [user] + remainingWaypoints + let newWaypoints = [user] + self.remainingWaypoints let newOptions = oldOptions.copy() as! RouteOptions newOptions.waypoints = newWaypoints @@ -215,8 +213,8 @@ open class RouteLegProgress: NSObject { */ @objc public var stepIndex: Int { didSet { - assert(stepIndex >= 0 && stepIndex < leg.steps.endIndex) - currentStepProgress = RouteStepProgress(step: currentStep) + assert(self.stepIndex >= 0 && self.stepIndex < self.leg.steps.endIndex) + self.currentStepProgress = RouteStepProgress(step: self.currentStep) } } @@ -224,35 +222,35 @@ open class RouteLegProgress: NSObject { The remaining steps for user to complete. */ @objc public var remainingSteps: [RouteStep] { - return Array(leg.steps.suffix(from: stepIndex + 1)) + Array(self.leg.steps.suffix(from: self.stepIndex + 1)) } /** Total distance traveled in meters along current leg. */ @objc public var distanceTraveled: CLLocationDistance { - return leg.steps.prefix(upTo: stepIndex).map { $0.distance }.reduce(0, +) + currentStepProgress.distanceTraveled + self.leg.steps.prefix(upTo: self.stepIndex).map(\.distance).reduce(0, +) + self.currentStepProgress.distanceTraveled } /** Duration remaining in seconds on current leg. */ @objc public var durationRemaining: TimeInterval { - return remainingSteps.map { $0.expectedTravelTime }.reduce(0, +) + currentStepProgress.durationRemaining + self.remainingSteps.map(\.expectedTravelTime).reduce(0, +) + self.currentStepProgress.durationRemaining } /** Distance remaining on the current leg. */ @objc public var distanceRemaining: CLLocationDistance { - return remainingSteps.map { $0.distance }.reduce(0, +) + currentStepProgress.distanceRemaining + self.remainingSteps.map(\.distance).reduce(0, +) + self.currentStepProgress.distanceRemaining } /** Number between 0 and 1 representing how far along the current leg the user has traveled. */ @objc public var fractionTraveled: Double { - return distanceTraveled / leg.distance + self.distanceTraveled / self.leg.distance } @objc public var userHasArrivedAtWaypoint = false @@ -265,7 +263,7 @@ open class RouteLegProgress: NSObject { return nil } if index > 0 { - return leg.steps[index-1] + return self.leg.steps[index - 1] } return nil } @@ -277,8 +275,8 @@ open class RouteLegProgress: NSObject { guard let index = leg.steps.firstIndex(of: step) else { return nil } - if index+1 < leg.steps.endIndex { - return leg.steps[index+1] + if index + 1 < self.leg.steps.endIndex { + return self.leg.steps[index + 1] } return nil } @@ -289,17 +287,17 @@ open class RouteLegProgress: NSObject { If there is no `priorStep`, nil is returned. */ @objc public var priorStep: RouteStep? { - guard stepIndex - 1 >= 0 else { + guard self.stepIndex - 1 >= 0 else { return nil } - return leg.steps[stepIndex - 1] + return self.leg.steps[self.stepIndex - 1] } /** Returns the current `RouteStep` for the leg the user is on. */ @objc public var currentStep: RouteStep { - return leg.steps[stepIndex] + self.leg.steps[self.stepIndex] } /** @@ -308,10 +306,10 @@ open class RouteLegProgress: NSObject { If there is no `upcomingStep`, nil is returned. */ @objc public var upComingStep: RouteStep? { - guard stepIndex + 1 < leg.steps.endIndex else { + guard self.stepIndex + 1 < self.leg.steps.endIndex else { return nil } - return leg.steps[stepIndex + 1] + return self.leg.steps[self.stepIndex + 1] } /** @@ -320,17 +318,17 @@ open class RouteLegProgress: NSObject { If there is no `followOnStep`, nil is returned. */ @objc public var followOnStep: RouteStep? { - guard stepIndex + 2 < leg.steps.endIndex else { + guard self.stepIndex + 2 < self.leg.steps.endIndex else { return nil } - return leg.steps[stepIndex + 2] + return self.leg.steps[self.stepIndex + 2] } /** Return bool whether step provided is the current `RouteStep` the user is on. */ @objc public func isCurrentStep(_ step: RouteStep) -> Bool { - return step == currentStep + step == self.currentStep } /** @@ -347,16 +345,16 @@ open class RouteLegProgress: NSObject { @objc public init(leg: RouteLeg, stepIndex: Int = 0, spokenInstructionIndex: Int = 0) { self.leg = leg self.stepIndex = stepIndex - currentStepProgress = RouteStepProgress(step: leg.steps[stepIndex], spokenInstructionIndex: spokenInstructionIndex) + self.currentStepProgress = RouteStepProgress(step: leg.steps[stepIndex], spokenInstructionIndex: spokenInstructionIndex) } /** Returns an array of `CLLocationCoordinate2D` of the prior, current and upcoming step geometry. */ @objc public var nearbyCoordinates: [CLLocationCoordinate2D] { - let priorCoords = priorStep?.coordinates ?? [] - let upcomingCoords = upComingStep?.coordinates ?? [] - let currentCoords = currentStep.coordinates ?? [] + let priorCoords = self.priorStep?.coordinates ?? [] + let upcomingCoords = self.upComingStep?.coordinates ?? [] + let currentCoords = self.currentStep.coordinates ?? [] let nearby = priorCoords + currentCoords + upcomingCoords assert(!nearby.isEmpty, "Step must have coordinates") return nearby @@ -366,12 +364,12 @@ open class RouteLegProgress: NSObject { func closestStep(to coordinate: CLLocationCoordinate2D) -> StepIndexDistance? { var currentClosest: StepIndexDistance? - let remainingSteps = leg.steps.suffix(from: stepIndex) + let remainingSteps = self.leg.steps.suffix(from: self.stepIndex) for (currentStepIndex, step) in remainingSteps.enumerated() { guard let coords = step.coordinates else { continue } guard let closestCoordOnStep = Polyline(coords).closestCoordinate(to: coordinate) else { continue } - let foundIndex = currentStepIndex + stepIndex + let foundIndex = currentStepIndex + self.stepIndex // First time around, currentClosest will be `nil`. guard let currentClosestDistance = currentClosest?.distance else { @@ -393,7 +391,6 @@ open class RouteLegProgress: NSObject { */ @objc(MBRouteStepProgress) open class RouteStepProgress: NSObject { - /** Returns the current `RouteStep`. */ @@ -413,22 +410,22 @@ open class RouteStepProgress: NSObject { Total distance in meters remaining on current step. */ @objc public var distanceRemaining: CLLocationDistance { - return step.distance - distanceTraveled + self.step.distance - self.distanceTraveled } /** Number between 0 and 1 representing fraction of current step traveled. */ @objc public var fractionTraveled: Double { - guard step.distance > 0 else { return 1 } - return distanceTraveled / step.distance + guard self.step.distance > 0 else { return 1 } + return self.distanceTraveled / self.step.distance } /** Number of seconds remaining on current step. */ @objc public var durationRemaining: TimeInterval { - return (1 - fractionTraveled) * step.expectedTravelTime + (1 - self.fractionTraveled) * self.step.expectedTravelTime } /** @@ -455,11 +452,11 @@ open class RouteStepProgress: NSObject { The step must contain `intersectionsIncludingUpcomingManeuverIntersection` otherwise this property will be `nil`. */ @objc public var upcomingIntersection: Intersection? { - guard let intersections = intersectionsIncludingUpcomingManeuverIntersection, intersections.startIndex..? + @objc public var intersectionDistances: [CLLocationDistance]? /** The distance in meters the user is to the next intersection they will pass through. @@ -500,7 +497,7 @@ open class RouteStepProgress: NSObject { */ @objc public var remainingVisualInstructions: [VisualInstructionBanner]? { guard let visualInstructions = step.instructionsDisplayedAlongStep else { return nil } - return Array(visualInstructions.suffix(from: visualInstructionIndex)) + return Array(visualInstructions.suffix(from: self.visualInstructionIndex)) } /** @@ -516,7 +513,7 @@ open class RouteStepProgress: NSObject { let instructions = step.instructionsSpokenAlongStep, spokenInstructionIndex <= instructions.endIndex else { return nil } - return Array(instructions.suffix(from: spokenInstructionIndex)) + return Array(instructions.suffix(from: self.spokenInstructionIndex)) } /** @@ -524,8 +521,8 @@ open class RouteStepProgress: NSObject { */ @objc public var currentSpokenInstruction: SpokenInstruction? { guard let instructionsSpokenAlongStep = step.instructionsSpokenAlongStep else { return nil } - guard spokenInstructionIndex < instructionsSpokenAlongStep.count else { return nil } - return instructionsSpokenAlongStep[spokenInstructionIndex] + guard self.spokenInstructionIndex < instructionsSpokenAlongStep.count else { return nil } + return instructionsSpokenAlongStep[self.spokenInstructionIndex] } /** @@ -533,7 +530,7 @@ open class RouteStepProgress: NSObject { */ @objc public var currentVisualInstruction: VisualInstructionBanner? { guard let instructionsDisplayedAlongStep = step.instructionsDisplayedAlongStep else { return nil } - guard visualInstructionIndex < instructionsDisplayedAlongStep.count else { return nil } - return instructionsDisplayedAlongStep[visualInstructionIndex] + guard self.visualInstructionIndex < instructionsDisplayedAlongStep.count else { return nil } + return instructionsDisplayedAlongStep[self.visualInstructionIndex] } } diff --git a/MapboxCoreNavigation/RouteStep.swift b/MapboxCoreNavigation/RouteStep.swift index 5dc8a166a..5c64e591f 100644 --- a/MapboxCoreNavigation/RouteStep.swift +++ b/MapboxCoreNavigation/RouteStep.swift @@ -1,8 +1,7 @@ import MapboxDirections extension RouteStep { - static func ==(left: RouteStep, right: RouteStep) -> Bool { - + static func == (left: RouteStep, right: RouteStep) -> Bool { var finalHeading = false if let leftFinalHeading = left.finalHeading, let rightFinalHeading = right.finalHeading { finalHeading = leftFinalHeading == rightFinalHeading @@ -18,15 +17,15 @@ extension RouteStep { Returns true if the route step is on a motorway. */ public var isMotorway: Bool { - return intersections?.first?.outletRoadClasses?.contains(.motorway) ?? false + intersections?.first?.outletRoadClasses?.contains(.motorway) ?? false } /** Returns true if the route travels on a motorway primarily identified by a route number rather than a road name. */ var isNumberedMotorway: Bool { - guard isMotorway else { return false } - guard let codes = codes, let digitRange = codes.first?.rangeOfCharacter(from: .decimalDigits) else { + guard self.isMotorway else { return false } + guard let codes, let digitRange = codes.first?.rangeOfCharacter(from: .decimalDigits) else { return false } return !digitRange.isEmpty @@ -36,6 +35,6 @@ extension RouteStep { Returns the last instruction for a given step. */ public var lastInstruction: SpokenInstruction? { - return instructionsSpokenAlongStep?.last + instructionsSpokenAlongStep?.last } } diff --git a/MapboxCoreNavigation/Router.swift b/MapboxCoreNavigation/Router.swift index 11a038c82..09dc1c63b 100644 --- a/MapboxCoreNavigation/Router.swift +++ b/MapboxCoreNavigation/Router.swift @@ -1,5 +1,5 @@ -import Foundation import CoreLocation +import Foundation import MapboxDirections @objc public protocol Router: AnyObject, CLLocationManagerDelegate { diff --git a/MapboxCoreNavigation/ScreenCapture.swift b/MapboxCoreNavigation/ScreenCapture.swift index 05d240403..a6c821894 100644 --- a/MapboxCoreNavigation/ScreenCapture.swift +++ b/MapboxCoreNavigation/ScreenCapture.swift @@ -2,11 +2,9 @@ import Foundation #if os(iOS) import UIKit -extension UIWindow { - +public extension UIWindow { /// Returns a screenshot of the current window - public func capture() -> UIImage? { - + func capture() -> UIImage? { UIGraphicsBeginImageContextWithOptions(frame.size, isOpaque, UIScreen.main.scale) drawHierarchy(in: bounds, afterScreenUpdates: false) @@ -20,9 +18,7 @@ extension UIWindow { } extension UIImage { - func scaled(toFit newWidth: CGFloat) -> UIImage? { - let factor = newWidth / size.width let newSize = CGSize(width: size.width * factor, height: size.height * factor) @@ -42,10 +38,10 @@ extension UIImage { func captureScreen(scaledToFit width: CGFloat) -> Data? { #if os(iOS) - guard let image = UIApplication.shared.keyWindow?.capture()?.scaled(toFit: width) else { return nil } - return image.jpegData(compressionQuality: 0.2) + guard let image = UIApplication.shared.keyWindow?.capture()?.scaled(toFit: width) else { return nil } + return image.jpegData(compressionQuality: 0.2) #else - return nil // Not yet implemented for other platforms + return nil // Not yet implemented for other platforms #endif } diff --git a/MapboxCoreNavigation/Sequence.swift b/MapboxCoreNavigation/Sequence.swift index 38a599f35..bf58aae28 100644 --- a/MapboxCoreNavigation/Sequence.swift +++ b/MapboxCoreNavigation/Sequence.swift @@ -3,7 +3,7 @@ import Foundation extension Sequence where Element: Hashable { #if !swift(>=4.1) func compactMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] { - return try flatMap(transform) + try flatMap(transform) } #endif } diff --git a/MapboxCoreNavigation/SessionState.swift b/MapboxCoreNavigation/SessionState.swift index 0c4a6aed5..6d4a85a76 100644 --- a/MapboxCoreNavigation/SessionState.swift +++ b/MapboxCoreNavigation/SessionState.swift @@ -1,9 +1,8 @@ -import Foundation import CoreLocation +import Foundation import MapboxDirections import UIKit.UIDevice - struct SessionState { let identifier = UUID() var departureTimestamp: Date? @@ -40,28 +39,28 @@ struct SessionState { public mutating func reportChange(to orientation: UIDeviceOrientation) { if orientation.isPortrait { - timeSpentInLandscape += abs(lastTimeInPortrait.timeIntervalSinceNow) - lastTimeInPortrait = Date() + self.timeSpentInLandscape += abs(self.lastTimeInPortrait.timeIntervalSinceNow) + self.lastTimeInPortrait = Date() } else if orientation.isLandscape { - timeSpentInPortrait += abs(lastTimeInLandscape.timeIntervalSinceNow) - lastTimeInLandscape = Date() + self.timeSpentInPortrait += abs(self.lastTimeInLandscape.timeIntervalSinceNow) + self.lastTimeInLandscape = Date() } } public mutating func reportChange(to applicationState: UIApplication.State) { if applicationState == .active { - timeSpentInForeground += abs(lastTimeInBackground.timeIntervalSinceNow) + self.timeSpentInForeground += abs(self.lastTimeInBackground.timeIntervalSinceNow) - lastTimeInForeground = Date() + self.lastTimeInForeground = Date() } else if applicationState == .background { - timeSpentInBackground += abs(lastTimeInForeground.timeIntervalSinceNow) - lastTimeInBackground = Date() + self.timeSpentInBackground += abs(self.lastTimeInForeground.timeIntervalSinceNow) + self.lastTimeInBackground = Date() } } } class FixedLengthQueue { - private var objects = Array() + private var objects = [T]() private var length: Int public init(length: Int) { @@ -69,15 +68,13 @@ class FixedLengthQueue { } public func push(_ obj: T) { - objects.append(obj) - if objects.count == length { - objects.remove(at: 0) + self.objects.append(obj) + if self.objects.count == self.length { + self.objects.remove(at: 0) } } - public var allObjects: Array { - get { - return Array(objects) - } + public var allObjects: [T] { + Array(self.objects) } } diff --git a/MapboxCoreNavigation/SimulatedLocationManager.swift b/MapboxCoreNavigation/SimulatedLocationManager.swift index 8658aa622..1b6ef42be 100644 --- a/MapboxCoreNavigation/SimulatedLocationManager.swift +++ b/MapboxCoreNavigation/SimulatedLocationManager.swift @@ -2,23 +2,23 @@ import CoreLocation import MapboxDirections import Turf -fileprivate let maximumSpeed: CLLocationSpeed = 30 // ~108 kmh -fileprivate let minimumSpeed: CLLocationSpeed = 6 // ~21 kmh -fileprivate var distanceFilter: CLLocationDistance = 10 -fileprivate var verticalAccuracy: CLLocationAccuracy = 10 -fileprivate var horizontalAccuracy: CLLocationAccuracy = 40 +private let maximumSpeed: CLLocationSpeed = 30 // ~108 kmh +private let minimumSpeed: CLLocationSpeed = 6 // ~21 kmh +private var distanceFilter: CLLocationDistance = 10 +private var verticalAccuracy: CLLocationAccuracy = 10 +private var horizontalAccuracy: CLLocationAccuracy = 40 // minimumSpeed will be used when a location have maximumTurnPenalty -fileprivate let maximumTurnPenalty: CLLocationDirection = 90 +private let maximumTurnPenalty: CLLocationDirection = 90 // maximumSpeed will be used when a location have minimumTurnPenalty -fileprivate let minimumTurnPenalty: CLLocationDirection = 0 +private let minimumTurnPenalty: CLLocationDirection = 0 // Go maximum speed if distance to nearest coordinate is >= `safeDistance` -fileprivate let safeDistance: CLLocationDistance = 50 +private let safeDistance: CLLocationDistance = 50 -fileprivate class SimulatedLocation: CLLocation { +private class SimulatedLocation: CLLocation { var turnPenalty: Double = 0 override var description: String { - return "\(super.description) \(turnPenalty)" + "\(super.description) \(self.turnPenalty)" } } @@ -42,25 +42,23 @@ open class SimulatedLocationManager: NavigationLocationManager { @objc public var speedMultiplier: Double = 1 @objc override open var location: CLLocation? { - get { - return currentLocation - } + self.currentLocation } var route: Route? { didSet { - reset() + self.reset() } } - public override func copy(with zone: NSZone? = nil) -> Any { + override public func copy(with zone: NSZone? = nil) -> Any { let copy = SimulatedLocationManager(route: route!) - copy.currentDistance = currentDistance - copy.currentLocation = currentLocation - copy.currentSpeed = currentSpeed - copy.locations = locations - copy.routeLine = routeLine - copy.speedMultiplier = speedMultiplier + copy.currentDistance = self.currentDistance + copy.currentLocation = self.currentLocation + copy.currentSpeed = self.currentSpeed + copy.locations = self.locations + copy.routeLine = self.routeLine + copy.speedMultiplier = self.speedMultiplier return copy } @@ -74,7 +72,7 @@ open class SimulatedLocationManager: NavigationLocationManager { */ @objc public init(route: Route) { super.init() - initializeSimulatedLocationManager(for: route, currentDistance: 0, currentSpeed: 30) + self.initializeSimulatedLocationManager(for: route, currentDistance: 0, currentSpeed: 30) } /** @@ -85,33 +83,32 @@ open class SimulatedLocationManager: NavigationLocationManager { */ @objc public init(routeProgress: RouteProgress) { super.init() - let currentDistance = calculateCurrentDistance(routeProgress.distanceTraveled) - initializeSimulatedLocationManager(for: routeProgress.route, currentDistance: currentDistance, currentSpeed: 0) + let currentDistance = self.calculateCurrentDistance(routeProgress.distanceTraveled) + self.initializeSimulatedLocationManager(for: routeProgress.route, currentDistance: currentDistance, currentSpeed: 0) } private func initializeSimulatedLocationManager(for route: Route, currentDistance: CLLocationDistance, currentSpeed: CLLocationSpeed) { - self.currentSpeed = currentSpeed self.currentDistance = currentDistance self.route = route - NotificationCenter.default.addObserver(self, selector: #selector(didReroute(_:)), name: .routeControllerDidReroute, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .routeControllerProgressDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didReroute(_:)), name: .routeControllerDidReroute, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.progressDidChange(_:)), name: .routeControllerProgressDidChange, object: nil) } private func reset() { if let coordinates = route?.coordinates { - routeLine = coordinates - locations = coordinates.simulatedLocationsWithTurnPenalties() + self.routeLine = coordinates + self.locations = coordinates.simulatedLocationsWithTurnPenalties() } } private func calculateCurrentDistance(_ distance: CLLocationDistance) -> CLLocationDistance { - return distance + (currentSpeed * speedMultiplier) + distance + (self.currentSpeed * self.speedMultiplier) } @objc private func progressDidChange(_ notification: Notification) { - routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress + self.routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress } @objc private func didReroute(_ notification: Notification) { @@ -119,7 +116,7 @@ open class SimulatedLocationManager: NavigationLocationManager { return } - route = routeController.routeProgress.route + self.route = routeController.routeProgress.route } deinit { @@ -128,7 +125,7 @@ open class SimulatedLocationManager: NavigationLocationManager { } override open func startUpdatingLocation() { - DispatchQueue.main.async(execute: tick) + DispatchQueue.main.async(execute: self.tick) } override open func stopUpdatingLocation() { @@ -138,7 +135,7 @@ open class SimulatedLocationManager: NavigationLocationManager { } @objc fileprivate func tick() { - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(tick), object: nil) + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.tick), object: nil) let polyline = Polyline(routeLine) @@ -150,7 +147,7 @@ open class SimulatedLocationManager: NavigationLocationManager { guard let lookAheadCoordinate = polyline.coordinateFromStart(distance: currentDistance + 10) else { return } guard let closestCoordinate = polyline.closestCoordinate(to: newCoordinate) else { return } - let closestLocation = locations[closestCoordinate.index] + let closestLocation = self.locations[closestCoordinate.index] let distanceToClosest = closestLocation.distance(from: CLLocation(newCoordinate)) let distance = min(max(distanceToClosest, 10), safeDistance) @@ -158,14 +155,14 @@ open class SimulatedLocationManager: NavigationLocationManager { // Simulate speed based on expected segment travel time if let expectedSegmentTravelTimes = routeProgress?.currentLeg.expectedSegmentTravelTimes, - let coordinates = routeProgress?.route.coordinates, - let closestCoordinateOnRoute = Polyline(routeProgress!.route.coordinates!).closestCoordinate(to: newCoordinate), - let nextCoordinateOnRoute = coordinates.after(element: coordinates[closestCoordinateOnRoute.index]), - let time = expectedSegmentTravelTimes.optional[closestCoordinateOnRoute.index] { + let coordinates = routeProgress?.route.coordinates, + let closestCoordinateOnRoute = Polyline(routeProgress!.route.coordinates!).closestCoordinate(to: newCoordinate), + let nextCoordinateOnRoute = coordinates.after(element: coordinates[closestCoordinateOnRoute.index]), + let time = expectedSegmentTravelTimes.optional[closestCoordinateOnRoute.index] { let distance = coordinates[closestCoordinateOnRoute.index].distance(to: nextCoordinateOnRoute) - currentSpeed = max(distance / time, 2) + self.currentSpeed = max(distance / time, 2) } else { - currentSpeed = calculateCurrentSpeed(distance: distance, coordinatesNearby: coordinatesNearby, closestLocation: closestLocation) + self.currentSpeed = self.calculateCurrentSpeed(distance: distance, coordinatesNearby: coordinatesNearby, closestLocation: closestLocation) } let location = CLLocation(coordinate: newCoordinate, @@ -173,20 +170,19 @@ open class SimulatedLocationManager: NavigationLocationManager { horizontalAccuracy: horizontalAccuracy, verticalAccuracy: verticalAccuracy, course: newCoordinate.direction(to: lookAheadCoordinate).wrap(min: 0, max: 360), - speed: currentSpeed, + speed: self.currentSpeed, timestamp: Date()) - currentLocation = location + self.currentLocation = location lastKnownLocation = location - delegate?.locationManager?(self, didUpdateLocations: [currentLocation]) - currentDistance = calculateCurrentDistance(currentDistance) - perform(#selector(tick), with: nil, afterDelay: 1) + delegate?.locationManager?(self, didUpdateLocations: [self.currentLocation]) + self.currentDistance = self.calculateCurrentDistance(self.currentDistance) + perform(#selector(self.tick), with: nil, afterDelay: 1) } private func calculateCurrentSpeed(distance: CLLocationDistance, coordinatesNearby: [CLLocationCoordinate2D]? = nil, closestLocation: SimulatedLocation) -> CLLocationSpeed { - // More than 10 nearby coordinates indicates that we are in a roundabout or similar complex shape. - if let coordinatesNearby = coordinatesNearby, coordinatesNearby.count >= 10 { + if let coordinatesNearby, coordinatesNearby.count >= 10 { return minimumSpeed } // Maximum speed if we are a safe distance from the closest coordinate @@ -201,44 +197,41 @@ open class SimulatedLocationManager: NavigationLocationManager { } } -extension Double { - fileprivate func scale(minimumIn: Double, maximumIn: Double, minimumOut: Double, maximumOut: Double) -> Double { - return ((maximumOut - minimumOut) * (self - minimumIn) / (maximumIn - minimumIn)) + minimumOut +private extension Double { + func scale(minimumIn: Double, maximumIn: Double, minimumOut: Double, maximumOut: Double) -> Double { + ((maximumOut - minimumOut) * (self - minimumIn) / (maximumIn - minimumIn)) + minimumOut } } -extension CLLocation { - fileprivate convenience init(_ coordinate: CLLocationCoordinate2D) { +private extension CLLocation { + convenience init(_ coordinate: CLLocationCoordinate2D) { self.init(latitude: coordinate.latitude, longitude: coordinate.longitude) } } -extension Array where Element : Hashable { - fileprivate struct OptionalSubscript { +private extension Array where Element: Hashable { + struct OptionalSubscript { var elements: [Element] - subscript (index: Int) -> Element? { - return index < elements.count ? elements[index] : nil + subscript(index: Int) -> Element? { + index < self.elements.count ? self.elements[index] : nil } } - fileprivate var optional: OptionalSubscript { - get { return OptionalSubscript(elements: self) } - } + var optional: OptionalSubscript { OptionalSubscript(elements: self) } } -extension Array where Element : Equatable { - fileprivate func after(element: Element) -> Element? { - if let index = self.firstIndex(of: element), index + 1 <= self.count { - return index + 1 == self.count ? self[0] : self[index + 1] +private extension Array where Element: Equatable { + func after(element: Element) -> Element? { + if let index = firstIndex(of: element), index + 1 <= count { + return index + 1 == count ? self[0] : self[index + 1] } return nil } } -extension Array where Element == CLLocationCoordinate2D { - +private extension [CLLocationCoordinate2D] { // Calculate turn penalty for each coordinate. - fileprivate func simulatedLocationsWithTurnPenalties() -> [SimulatedLocation] { + func simulatedLocationsWithTurnPenalties() -> [SimulatedLocation] { var locations = [SimulatedLocation]() for (coordinate, nextCoordinate) in zip(prefix(upTo: endIndex - 1), suffix(from: 1)) { diff --git a/MapboxCoreNavigation/String.swift b/MapboxCoreNavigation/String.swift index 84d3ec982..324c5d9bd 100644 --- a/MapboxCoreNavigation/String.swift +++ b/MapboxCoreNavigation/String.swift @@ -2,53 +2,53 @@ import Foundation extension String { var ISO8601Date: Date? { - return Date.ISO8601Formatter.date(from: self) + Date.ISO8601Formatter.date(from: self) } /** Check if the current string is empty. If the string is empty, `nil` is returned, otherwise, the string is returned. */ public var nonEmptyString: String? { - return isEmpty ? nil : self + isEmpty ? nil : self } var wholeRange: Range { - return startIndex.. String { - return replacements.reduce(self) { $0.replacingOccurrences(of: $1.of, with: $1.with) } + replacements.reduce(self) { $0.replacingOccurrences(of: $1.of, with: $1.with) } } var addingXMLEscapes: String { - return byReplacing([ + self.byReplacing([ ("&", "&"), ("<", "<"), ("\"", """), ("'", "'") - ]) + ]) } var asSSMLAddress: String { - return "\(self.addingXMLEscapes)" + "\(self.addingXMLEscapes)" } var asSSMLCharacters: String { - return "\(self.addingXMLEscapes)" + "\(self.addingXMLEscapes)" } func withSSMLPhoneme(ipaNotation: String) -> String { - return "\(self.addingXMLEscapes)" + "\(self.addingXMLEscapes)" } var isUppercased: Bool { - return self == uppercased() && self != lowercased() + self == uppercased() && self != lowercased() } var containsDecimalDigit: Bool { - return self.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil + rangeOfCharacter(from: CharacterSet.decimalDigits) != nil } // Adapted from https://github.com/raywenderlich/swift-algorithm-club/blob/master/Minimum%20Edit%20Distance/MinimumEditDistance.playground/Contents.swift @@ -62,12 +62,12 @@ extension String { var matrix = [[Int]](repeating: [Int](repeating: 0, count: toWordCount + 1), count: fromWordCount + 1) // initialize matrix - for index in 1...fromWordCount { + for index in 1 ... fromWordCount { // the distance of any first string to an empty second string matrix[index][0] = index } - for index in 1...toWordCount { + for index in 1 ... toWordCount { // the distance of any second string to an empty first string matrix[0][index] = index } diff --git a/MapboxCoreNavigation/TunnelIntersectionManager.swift b/MapboxCoreNavigation/TunnelIntersectionManager.swift index 1dab9cf78..bede3f3d6 100644 --- a/MapboxCoreNavigation/TunnelIntersectionManager.swift +++ b/MapboxCoreNavigation/TunnelIntersectionManager.swift @@ -1,12 +1,11 @@ -import Foundation import CoreLocation +import Foundation /** The `TunnelIntersectionManagerDelegate` protocol provides methods for responding to events where a user enters or exits a tunnel. */ @objc(MBTunnelIntersectionManagerDelegate) public protocol TunnelIntersectionManagerDelegate: AnyObject { - /** Called immediately when the location manager detects a user will enter a tunnel. @@ -28,7 +27,6 @@ public protocol TunnelIntersectionManagerDelegate: AnyObject { @objc(MBTunnelIntersectionManager) open class TunnelIntersectionManager: NSObject { - /** The associated delegate for tunnel intersection manager. */ @@ -55,14 +53,14 @@ open class TunnelIntersectionManager: NSObject { @objc public var tunnelSimulationEnabled: Bool = true func checkForTunnelIntersection(at location: CLLocation, routeProgress: RouteProgress) { - guard tunnelSimulationEnabled else { return } + guard self.tunnelSimulationEnabled else { return } - let tunnelDetected = userWithinTunnelEntranceRadius(at: location, routeProgress: routeProgress) + let tunnelDetected = self.userWithinTunnelEntranceRadius(at: location, routeProgress: routeProgress) if tunnelDetected { - delegate?.tunnelIntersectionManager?(self, willEnableAnimationAt: location) - } else if isAnimationEnabled { - delegate?.tunnelIntersectionManager?(self, willDisableAnimationAt: location) + self.delegate?.tunnelIntersectionManager?(self, willEnableAnimationAt: location) + } else if self.isAnimationEnabled { + self.delegate?.tunnelIntersectionManager?(self, willDisableAnimationAt: location) } } @@ -86,9 +84,9 @@ open class TunnelIntersectionManager: NSObject { // Ensure the upcoming intersection is a tunnel intersection // OR the location speed is either at least 5 m/s or is considered a bad location update guard let upcomingIntersection = routeProgress.currentLegProgress.currentStepProgress.upcomingIntersection, - let roadClasses = upcomingIntersection.outletRoadClasses, roadClasses.contains(.tunnel), - (location.speed >= RouteControllerMinimumSpeedAtTunnelEntranceRadius || !location.isQualified) else { - return false + let roadClasses = upcomingIntersection.outletRoadClasses, roadClasses.contains(.tunnel), + location.speed >= RouteControllerMinimumSpeedAtTunnelEntranceRadius || !location.isQualified else { + return false } // Distance to the upcoming tunnel entrance @@ -98,7 +96,7 @@ open class TunnelIntersectionManager: NSObject { } @objc public func enableTunnelAnimation(routeController: RouteController, routeProgress: RouteProgress) { - guard !isAnimationEnabled else { return } + guard !self.isAnimationEnabled else { return } self.animatedLocationManager = SimulatedLocationManager(routeProgress: routeProgress) self.animatedLocationManager?.delegate = routeController @@ -106,27 +104,26 @@ open class TunnelIntersectionManager: NSObject { self.animatedLocationManager?.startUpdatingLocation() self.animatedLocationManager?.startUpdatingHeading() - isAnimationEnabled = true + self.isAnimationEnabled = true } @objc public func suspendTunnelAnimation(at location: CLLocation, routeController: RouteController) { - - guard isAnimationEnabled else { return } + guard self.isAnimationEnabled else { return } // Disable the tunnel animation after at least 3 good location updates. if location.isQualified { - tunnelExitLocations.append(location) + self.tunnelExitLocations.append(location) } - guard tunnelExitLocations.count >= 3 else { + guard self.tunnelExitLocations.count >= 3 else { return } - isAnimationEnabled = false + self.isAnimationEnabled = false - animatedLocationManager?.stopUpdatingLocation() - animatedLocationManager?.stopUpdatingHeading() - animatedLocationManager = nil - tunnelExitLocations.removeAll() + self.animatedLocationManager?.stopUpdatingLocation() + self.animatedLocationManager?.stopUpdatingHeading() + self.animatedLocationManager = nil + self.tunnelExitLocations.removeAll() routeController.rawLocation = location } diff --git a/MapboxCoreNavigation/UIDevice.swift b/MapboxCoreNavigation/UIDevice.swift index 2244969a9..2217878b3 100644 --- a/MapboxCoreNavigation/UIDevice.swift +++ b/MapboxCoreNavigation/UIDevice.swift @@ -1,24 +1,22 @@ -import Foundation import CoreLocation +import Foundation #if os(iOS) - import UIKit +import UIKit #endif #if os(iOS) - import UIKit +import UIKit #endif - -extension UIDevice { - +public extension UIDevice { /** Returns a `Bool` whether the device is plugged in. Returns false if not an iOS device. */ - @objc public var isPluggedIn: Bool { + @objc var isPluggedIn: Bool { #if os(iOS) - return [.charging, .full].contains(UIDevice.current.batteryState) + return [.charging, .full].contains(UIDevice.current.batteryState) #else - return false + return false #endif } } diff --git a/MapboxCoreNavigationTests/DistanceFormatterTests.swift b/MapboxCoreNavigationTests/DistanceFormatterTests.swift index 4cd05bed1..6b3fe6b45 100644 --- a/MapboxCoreNavigationTests/DistanceFormatterTests.swift +++ b/MapboxCoreNavigationTests/DistanceFormatterTests.swift @@ -1,13 +1,12 @@ -import XCTest import CoreLocation @testable import MapboxCoreNavigation +import XCTest let oneMile: CLLocationDistance = .metersPerMile let oneYard: CLLocationDistance = 0.9144 let oneFeet: CLLocationDistance = 0.3048 class DistanceFormatterTests: XCTestCase { - var distanceFormatter = DistanceFormatter(approximate: true) override func setUp() { @@ -15,10 +14,10 @@ class DistanceFormatterTests: XCTestCase { } func assertDistance(_ distance: CLLocationDistance, displayed: String, quantity: String) { - let displayedString = distanceFormatter.string(from: distance) + let displayedString = self.distanceFormatter.string(from: distance) XCTAssertEqual(displayedString, displayed, "Displayed: '\(displayedString)' should be equal to \(displayed)") - let attributedString = distanceFormatter.attributedString(for: distance as NSNumber) + let attributedString = self.distanceFormatter.attributedString(for: distance as NSNumber) XCTAssertEqual(attributedString?.string, displayed, "Displayed: '\(attributedString?.string ?? "")' should be equal to \(displayed)") guard let checkedAttributedString = attributedString else { return @@ -31,97 +30,97 @@ class DistanceFormatterTests: XCTestCase { } var effectiveQuantityRange = NSRange(location: NSNotFound, length: 0) - let quantityAttrs = checkedAttributedString.attributes(at: checkedQuantityRange.lowerBound.encodedOffset, effectiveRange: &effectiveQuantityRange) + let quantityAttrs = checkedAttributedString.attributes(at: checkedQuantityRange.lowerBound.utf16Offset(in: checkedAttributedString.string), effectiveRange: &effectiveQuantityRange) XCTAssertEqual(quantityAttrs[NSAttributedString.Key.quantity] as? NSNumber, distance as NSNumber, "'\(quantity)' should have quantity \(distance)") XCTAssertEqual(effectiveQuantityRange.length, quantity.count) - guard checkedQuantityRange.upperBound.encodedOffset < checkedAttributedString.length else { + guard checkedQuantityRange.upperBound.utf16Offset(in: checkedAttributedString.string) < checkedAttributedString.length else { return } - let unitAttrs = checkedAttributedString.attributes(at: checkedQuantityRange.upperBound.encodedOffset, effectiveRange: nil) + let unitAttrs = checkedAttributedString.attributes(at: checkedQuantityRange.upperBound.utf16Offset(in: checkedAttributedString.string), effectiveRange: nil) XCTAssertNil(unitAttrs[NSAttributedString.Key.quantity], "Unit should not be emphasized like a quantity") } func testDistanceFormatters_US() { NavigationSettings.shared.distanceUnit = .mile - distanceFormatter.numberFormatter.locale = Locale(identifier: "en-US") + self.distanceFormatter.numberFormatter.locale = Locale(identifier: "en-US") - assertDistance(0, displayed: "0 ft", quantity: "0") - assertDistance(oneFeet*50, displayed: "50 ft", quantity: "50") - assertDistance(oneFeet*100, displayed: "100 ft", quantity: "100") - assertDistance(oneFeet*249, displayed: "250 ft", quantity: "250") - assertDistance(oneFeet*305, displayed: "300 ft", quantity: "300") - assertDistance(oneMile*0.1, displayed: "0.1 mi", quantity: "0.1") - assertDistance(oneMile*0.24, displayed: "0.2 mi", quantity: "0.2") - assertDistance(oneMile*0.251, displayed: "0.3 mi", quantity: "0.3") - assertDistance(oneMile*0.75, displayed: "0.8 mi", quantity: "0.8") - assertDistance(oneMile, displayed: "1 mi", quantity: "1") - assertDistance(oneMile*2.5, displayed: "2.5 mi", quantity: "2.5") - assertDistance(oneMile*2.9, displayed: "2.9 mi", quantity: "2.9") - assertDistance(oneMile*3, displayed: "3 mi", quantity: "3") - assertDistance(oneMile*5.4, displayed: "5 mi", quantity: "5") + self.assertDistance(0, displayed: "0 ft", quantity: "0") + self.assertDistance(oneFeet * 50, displayed: "50 ft", quantity: "50") + self.assertDistance(oneFeet * 100, displayed: "100 ft", quantity: "100") + self.assertDistance(oneFeet * 249, displayed: "250 ft", quantity: "250") + self.assertDistance(oneFeet * 305, displayed: "300 ft", quantity: "300") + self.assertDistance(oneMile * 0.1, displayed: "0.1 mi", quantity: "0.1") + self.assertDistance(oneMile * 0.24, displayed: "0.2 mi", quantity: "0.2") + self.assertDistance(oneMile * 0.251, displayed: "0.3 mi", quantity: "0.3") + self.assertDistance(oneMile * 0.75, displayed: "0.8 mi", quantity: "0.8") + self.assertDistance(oneMile, displayed: "1 mi", quantity: "1") + self.assertDistance(oneMile * 2.5, displayed: "2.5 mi", quantity: "2.5") + self.assertDistance(oneMile * 2.9, displayed: "2.9 mi", quantity: "2.9") + self.assertDistance(oneMile * 3, displayed: "3 mi", quantity: "3") + self.assertDistance(oneMile * 5.4, displayed: "5 mi", quantity: "5") } func testDistanceFormatters_DE() { NavigationSettings.shared.distanceUnit = .kilometer - distanceFormatter.numberFormatter.locale = Locale(identifier: "de-DE") + self.distanceFormatter.numberFormatter.locale = Locale(identifier: "de-DE") - assertDistance(0, displayed: "0 m", quantity: "0") - assertDistance(4, displayed: "5 m", quantity: "5") - assertDistance(11, displayed: "10 m", quantity: "10") - assertDistance(15, displayed: "15 m", quantity: "15") - assertDistance(24, displayed: "25 m", quantity: "25") - assertDistance(89, displayed: "100 m", quantity: "100") - assertDistance(226, displayed: "250 m", quantity: "250") - assertDistance(275, displayed: "300 m", quantity: "300") - assertDistance(500, displayed: "500 m", quantity: "500") - assertDistance(949, displayed: "950 m", quantity: "950") - assertDistance(951, displayed: "950 m", quantity: "950") - assertDistance(999, displayed: "1 km", quantity: "1") - assertDistance(1000, displayed: "1 km", quantity: "1") - assertDistance(1001, displayed: "1 km", quantity: "1") - assertDistance(2_500, displayed: "2.5 km", quantity: "2.5") - assertDistance(2_900, displayed: "2.9 km", quantity: "2.9") - assertDistance(3_000, displayed: "3 km", quantity: "3") - assertDistance(3_500, displayed: "4 km", quantity: "4") + self.assertDistance(0, displayed: "0 m", quantity: "0") + self.assertDistance(4, displayed: "5 m", quantity: "5") + self.assertDistance(11, displayed: "10 m", quantity: "10") + self.assertDistance(15, displayed: "15 m", quantity: "15") + self.assertDistance(24, displayed: "25 m", quantity: "25") + self.assertDistance(89, displayed: "100 m", quantity: "100") + self.assertDistance(226, displayed: "250 m", quantity: "250") + self.assertDistance(275, displayed: "300 m", quantity: "300") + self.assertDistance(500, displayed: "500 m", quantity: "500") + self.assertDistance(949, displayed: "950 m", quantity: "950") + self.assertDistance(951, displayed: "950 m", quantity: "950") + self.assertDistance(999, displayed: "1 km", quantity: "1") + self.assertDistance(1000, displayed: "1 km", quantity: "1") + self.assertDistance(1001, displayed: "1 km", quantity: "1") + self.assertDistance(2500, displayed: "2.5 km", quantity: "2.5") + self.assertDistance(2900, displayed: "2.9 km", quantity: "2.9") + self.assertDistance(3000, displayed: "3 km", quantity: "3") + self.assertDistance(3500, displayed: "4 km", quantity: "4") } func testDistanceFormatters_GB() { NavigationSettings.shared.distanceUnit = .mile - distanceFormatter.numberFormatter.locale = Locale(identifier: "en-GB") + self.distanceFormatter.numberFormatter.locale = Locale(identifier: "en-GB") - assertDistance(0, displayed: "0 yd", quantity: "0") - assertDistance(oneYard*4, displayed: "0 yd", quantity: "0") - assertDistance(oneYard*5, displayed: "10 yd", quantity: "10") - assertDistance(oneYard*12, displayed: "10 yd", quantity: "10") - assertDistance(oneYard*24, displayed: "25 yd", quantity: "25") - assertDistance(oneYard*25, displayed: "25 yd", quantity: "25") - assertDistance(oneYard*38, displayed: "50 yd", quantity: "50") - assertDistance(oneYard*126, displayed: "150 yd", quantity: "150") - assertDistance(oneYard*150, displayed: "150 yd", quantity: "150") - assertDistance(oneYard*174, displayed: "150 yd", quantity: "150") - assertDistance(oneYard*175, displayed: "200 yd", quantity: "200") - assertDistance(oneMile/2, displayed: "0.5 mi", quantity: "0.5") - assertDistance(oneMile, displayed: "1 mi", quantity: "1") - assertDistance(oneMile*2.5, displayed: "2.5 mi", quantity: "2.5") - assertDistance(oneMile*3, displayed: "3 mi", quantity: "3") + self.assertDistance(0, displayed: "0 yd", quantity: "0") + self.assertDistance(oneYard * 4, displayed: "0 yd", quantity: "0") + self.assertDistance(oneYard * 5, displayed: "10 yd", quantity: "10") + self.assertDistance(oneYard * 12, displayed: "10 yd", quantity: "10") + self.assertDistance(oneYard * 24, displayed: "25 yd", quantity: "25") + self.assertDistance(oneYard * 25, displayed: "25 yd", quantity: "25") + self.assertDistance(oneYard * 38, displayed: "50 yd", quantity: "50") + self.assertDistance(oneYard * 126, displayed: "150 yd", quantity: "150") + self.assertDistance(oneYard * 150, displayed: "150 yd", quantity: "150") + self.assertDistance(oneYard * 174, displayed: "150 yd", quantity: "150") + self.assertDistance(oneYard * 175, displayed: "200 yd", quantity: "200") + self.assertDistance(oneMile / 2, displayed: "0.5 mi", quantity: "0.5") + self.assertDistance(oneMile, displayed: "1 mi", quantity: "1") + self.assertDistance(oneMile * 2.5, displayed: "2.5 mi", quantity: "2.5") + self.assertDistance(oneMile * 3, displayed: "3 mi", quantity: "3") } func testDistanceFormatters_he_IL() { NavigationSettings.shared.distanceUnit = .kilometer - distanceFormatter.numberFormatter.locale = Locale(identifier: "he-IL") + self.distanceFormatter.numberFormatter.locale = Locale(identifier: "he-IL") - assertDistance(0, displayed: "0 מ׳", quantity: "0") - assertDistance(4, displayed: "5 מ׳", quantity: "5") - assertDistance(11, displayed: "10 מ׳", quantity: "10") - assertDistance(15, displayed: "15 מ׳", quantity: "15") - assertDistance(24, displayed: "25 מ׳", quantity: "25") - assertDistance(89, displayed: "100 מ׳", quantity: "100") - assertDistance(226, displayed: "250 מ׳", quantity: "250") - assertDistance(275, displayed: "300 מ׳", quantity: "300") - assertDistance(500, displayed: "500 מ׳", quantity: "500") - assertDistance(949, displayed: "950 מ׳", quantity: "950") - assertDistance(951, displayed: "950 מ׳", quantity: "950") + self.assertDistance(0, displayed: "0 מ׳", quantity: "0") + self.assertDistance(4, displayed: "5 מ׳", quantity: "5") + self.assertDistance(11, displayed: "10 מ׳", quantity: "10") + self.assertDistance(15, displayed: "15 מ׳", quantity: "15") + self.assertDistance(24, displayed: "25 מ׳", quantity: "25") + self.assertDistance(89, displayed: "100 מ׳", quantity: "100") + self.assertDistance(226, displayed: "250 מ׳", quantity: "250") + self.assertDistance(275, displayed: "300 מ׳", quantity: "300") + self.assertDistance(500, displayed: "500 מ׳", quantity: "500") + self.assertDistance(949, displayed: "950 מ׳", quantity: "950") + self.assertDistance(951, displayed: "950 מ׳", quantity: "950") } func testInches() { diff --git a/MapboxCoreNavigationTests/Heading.swift b/MapboxCoreNavigationTests/Heading.swift index eee28ebf3..da5d369c8 100644 --- a/MapboxCoreNavigationTests/Heading.swift +++ b/MapboxCoreNavigationTests/Heading.swift @@ -1,7 +1,6 @@ import CoreLocation class Heading: CLHeading { - private var _heading: CLLocationDirection private var _accuracy: CLLocationDirection @@ -11,25 +10,26 @@ class Heading: CLHeading { super.init() } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - open override var trueHeading: CLLocationDirection { + override open var trueHeading: CLLocationDirection { get { - return _heading + self._heading } set { - _heading = newValue + self._heading = newValue } } - open override var headingAccuracy: CLLocationDirection { + override open var headingAccuracy: CLLocationDirection { get { - return _accuracy + self._accuracy } set { - _accuracy = newValue + self._accuracy = newValue } } } diff --git a/MapboxCoreNavigationTests/LocationTests.swift b/MapboxCoreNavigationTests/LocationTests.swift index 18f0469ba..4b6d692f9 100644 --- a/MapboxCoreNavigationTests/LocationTests.swift +++ b/MapboxCoreNavigationTests/LocationTests.swift @@ -1,9 +1,8 @@ -import XCTest import CoreLocation @testable import MapboxCoreNavigation +import XCTest class LocationTests: XCTestCase { - var setup: (progress: RouteProgress, firstLocation: CLLocation) { let progress = RouteProgress(route: route) let firstCoord = progress.currentLegProgress.nearbyCoordinates.first! @@ -12,7 +11,6 @@ class LocationTests: XCTestCase { return (progress, firstLocation) } - func testSerializeAndDeserializeLocation() { let coordinate = CLLocationCoordinate2D(latitude: 1.1, longitude: 2.2) let altitude: CLLocationAccuracy = 3.3 @@ -22,7 +20,7 @@ class LocationTests: XCTestCase { let course: CLLocationDirection = 7.7 let timestamp = Date().ISO8601 - var locationDictionary:[String: Any] = [:] + var locationDictionary: [String: Any] = [:] locationDictionary["lat"] = coordinate.latitude locationDictionary["lng"] = coordinate.longitude locationDictionary["altitude"] = altitude @@ -41,8 +39,8 @@ class LocationTests: XCTestCase { } func testSnappedLocation100MetersAlongRoute() { - let progress = setup.progress - let firstLocation = setup.firstLocation + let progress = self.setup.progress + let firstLocation = self.setup.firstLocation let initialHeadingOnFirstStep = progress.currentLegProgress.currentStep.finalHeading! let coordinateAlongFirstStep = firstLocation.coordinate.coordinate(at: 100, facing: initialHeadingOnFirstStep) @@ -51,24 +49,21 @@ class LocationTests: XCTestCase { return XCTFail("Location should have snapped to route") } - XCTAssertTrue(locationAlongFirstStep.distance(from: snapped) < 1, "The location is less than 1 meter away from the calculated snapped location") - } func testInterpolatedCourse() { - let progress = setup.progress - let firstLocation = setup.firstLocation + let progress = self.setup.progress + let firstLocation = self.setup.firstLocation let calculatedCourse = firstLocation.interpolatedCourse(along: progress.currentLegProgress.currentStepProgress.step.coordinates!)! let initialHeadingOnFirstStep = progress.currentLegProgress.currentStepProgress.step.finalHeading! XCTAssertTrue(calculatedCourse - initialHeadingOnFirstStep < 1, "At the beginning of the route, the final heading of the departure step should be very similar to the caclulated course of the first location update.") } - func testShouldSnap() { - let progress = setup.progress - let firstLocation = setup.firstLocation + let progress = self.setup.progress + let firstLocation = self.setup.firstLocation let initialHeadingOnFirstStep = progress.currentLegProgress.currentStepProgress.step.finalHeading! @@ -78,5 +73,4 @@ class LocationTests: XCTestCase { XCTAssertFalse(differentCourseAndAccurateLocation.shouldSnapCourse(toRouteWith: initialHeadingOnFirstStep), "Should not snap when user course is different, the location is accurate and moving") } - } diff --git a/MapboxCoreNavigationTests/MD5Tests.swift b/MapboxCoreNavigationTests/MD5Tests.swift index ca329378a..0b720595a 100644 --- a/MapboxCoreNavigationTests/MD5Tests.swift +++ b/MapboxCoreNavigationTests/MD5Tests.swift @@ -1,8 +1,7 @@ -import XCTest @testable import MapboxCoreNavigation +import XCTest class Tests: XCTestCase { - func testMD5() { XCTAssertEqual("hello".md5(), "5d41402abc4b2a76b9719d911017c592") @@ -53,7 +52,7 @@ class Tests: XCTestCase { let content = try! String(contentsOfFile: path, encoding: .utf8) let lines = content.components(separatedBy: .newlines) - lines.forEach { line in + for line in lines { let md5 = line.md5() XCTAssertEqual(md5.count, 32) } diff --git a/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift b/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift index 0dff9db4e..77da1df4f 100644 --- a/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift +++ b/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift @@ -1,11 +1,11 @@ -import XCTest -import MapboxDirections -import Turf import CoreLocation @testable import MapboxCoreNavigation +import MapboxDirections +import Turf +import XCTest let response = Fixture.JSONFromFileNamed(name: "routeWithInstructions", bundle: .module) -let jsonRoute = (response["routes"] as! [AnyObject]).first as! [String : Any] +let jsonRoute = (response["routes"] as! [AnyObject]).first as! [String: Any] // -122.413165,37.795042 let waypoint1 = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.795042, longitude: -122.413165)) // -122.433378,37.7727 @@ -13,19 +13,17 @@ let waypoint2 = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.7727, l let directions = Directions(accessToken: "pk.feedCafeDeadBeefBadeBede") let route = Route(json: jsonRoute, waypoints: [waypoint1, waypoint2], options: NavigationRouteOptions(waypoints: [waypoint1, waypoint2])) -let waitForInterval: TimeInterval = 5 - +let waitForInterval: TimeInterval = 30 class MapboxCoreNavigationTests: XCTestCase { - var navigation: RouteController! func testDepart() { route.accessToken = "foo" - navigation = RouteController(along: route, directions: directions) + self.navigation = RouteController(along: route, directions: directions) let depart = CLLocation(coordinate: CLLocationCoordinate2D(latitude: 37.795042, longitude: -122.413165), altitude: 1, horizontalAccuracy: 1, verticalAccuracy: 1, course: 0, speed: 10, timestamp: Date()) - expectation(forNotification: .routeControllerDidPassSpokenInstructionPoint, object: navigation) { (notification) -> Bool in + expectation(forNotification: .routeControllerDidPassSpokenInstructionPoint, object: self.navigation) { notification -> Bool in XCTAssertEqual(notification.userInfo?.count, 1) let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress @@ -33,27 +31,27 @@ class MapboxCoreNavigationTests: XCTestCase { return routeProgress != nil && routeProgress?.currentLegProgress.userHasArrivedAtWaypoint == false } - navigation.resume() - navigation.locationManager(navigation.locationManager, didUpdateLocations: [depart]) + self.navigation.resume() + self.navigation.locationManager(self.navigation.locationManager, didUpdateLocations: [depart]) - waitForExpectations(timeout: waitForInterval) { (error) in + waitForExpectations(timeout: waitForInterval) { error in XCTAssertNil(error) } } func makeLocation(latitude: Double, longitude: Double, course: CLLocationDirection) -> CLLocation { - return CLLocation(coordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), altitude: 1, horizontalAccuracy: 1, verticalAccuracy: 1, course: course, speed: 10, timestamp: Date()) + CLLocation(coordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), altitude: 1, horizontalAccuracy: 1, verticalAccuracy: 1, course: course, speed: 10, timestamp: Date()) } func testNewStep() { route.accessToken = "foo" let coordOnStep1 = route.legs[0].steps[1].coordinates![5] - let location = makeLocation(latitude: coordOnStep1.latitude, longitude: coordOnStep1.longitude, course: 250) + let location = self.makeLocation(latitude: coordOnStep1.latitude, longitude: coordOnStep1.longitude, course: 250) let locationManager = ReplayLocationManager(locations: [location, location]) - navigation = RouteController(along: route, directions: directions, locationManager: locationManager) + self.navigation = RouteController(along: route, directions: directions, locationManager: locationManager) - expectation(forNotification: .routeControllerDidPassSpokenInstructionPoint, object: navigation) { (notification) -> Bool in + expectation(forNotification: .routeControllerDidPassSpokenInstructionPoint, object: self.navigation) { notification -> Bool in XCTAssertEqual(notification.userInfo?.count, 1) let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress @@ -61,9 +59,9 @@ class MapboxCoreNavigationTests: XCTestCase { return routeProgress?.currentLegProgress.stepIndex == 1 } - navigation.resume() + self.navigation.resume() - waitForExpectations(timeout: waitForInterval) { (error) in + waitForExpectations(timeout: waitForInterval) { error in XCTAssertNil(error) } } @@ -71,12 +69,12 @@ class MapboxCoreNavigationTests: XCTestCase { func testJumpAheadToLastStep() { route.accessToken = "foo" let coordOnLastStep = route.legs[0].steps[6].coordinates![5] - let location = makeLocation(latitude: coordOnLastStep.latitude, longitude: coordOnLastStep.longitude, course: 171) + let location = self.makeLocation(latitude: coordOnLastStep.latitude, longitude: coordOnLastStep.longitude, course: 171) let locationManager = ReplayLocationManager(locations: [location, location]) - navigation = RouteController(along: route, directions: directions, locationManager: locationManager) + self.navigation = RouteController(along: route, directions: directions, locationManager: locationManager) - expectation(forNotification: .routeControllerDidPassSpokenInstructionPoint, object: navigation) { (notification) -> Bool in + expectation(forNotification: .routeControllerDidPassSpokenInstructionPoint, object: self.navigation) { notification -> Bool in XCTAssertEqual(notification.userInfo?.count, 1) let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress @@ -84,9 +82,9 @@ class MapboxCoreNavigationTests: XCTestCase { return routeProgress?.currentLegProgress.stepIndex == 6 } - navigation.resume() + self.navigation.resume() - waitForExpectations(timeout: waitForInterval) { (error) in + waitForExpectations(timeout: waitForInterval) { error in XCTAssertNil(error) } } @@ -102,18 +100,18 @@ class MapboxCoreNavigationTests: XCTestCase { timestamp: Date(timeIntervalSinceNow: 1)) let locationManager = ReplayLocationManager(locations: [firstLocation, secondLocation]) - navigation = RouteController(along: route, directions: directions, locationManager: locationManager) + self.navigation = RouteController(along: route, directions: directions, locationManager: locationManager) - expectation(forNotification: .routeControllerWillReroute, object: navigation) { (notification) -> Bool in + expectation(forNotification: .routeControllerWillReroute, object: self.navigation) { notification -> Bool in XCTAssertEqual(notification.userInfo?.count, 1) let location = notification.userInfo![RouteControllerNotificationUserInfoKey.locationKey] as? CLLocation return location?.coordinate == secondLocation.coordinate } - navigation.resume() + self.navigation.resume() - waitForExpectations(timeout: waitForInterval) { (error) in + waitForExpectations(timeout: waitForInterval) { error in XCTAssertNil(error) } } @@ -124,17 +122,17 @@ class MapboxCoreNavigationTests: XCTestCase { let locationManager = ReplayLocationManager(locations: locations) locationManager.speedMultiplier = 20 - navigation = RouteController(along: route, directions: directions, locationManager: locationManager) + self.navigation = RouteController(along: route, directions: directions, locationManager: locationManager) - expectation(forNotification: .routeControllerProgressDidChange, object: navigation) { (notification) -> Bool in + expectation(forNotification: .routeControllerProgressDidChange, object: self.navigation) { notification -> Bool in let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress return routeProgress != nil } - navigation.resume() + self.navigation.resume() let timeout = locations.last!.timestamp.timeIntervalSince(locations.first!.timestamp) / locationManager.speedMultiplier - waitForExpectations(timeout: timeout + 2) { (error) in + waitForExpectations(timeout: timeout + 2) { error in XCTAssertNil(error) } } @@ -142,20 +140,20 @@ class MapboxCoreNavigationTests: XCTestCase { func testFailToReroute() { route.accessToken = "foo" let directionsClientSpy = DirectionsSpy(accessToken: "garbage", host: nil) - navigation = RouteController(along: route, directions: directionsClientSpy) + self.navigation = RouteController(along: route, directions: directionsClientSpy) - expectation(forNotification: .routeControllerWillReroute, object: navigation) { (notification) -> Bool in - return true + expectation(forNotification: .routeControllerWillReroute, object: self.navigation) { _ -> Bool in + true } - expectation(forNotification: .routeControllerDidFailToReroute, object: navigation) { (notification) -> Bool in - return true + expectation(forNotification: .routeControllerDidFailToReroute, object: self.navigation) { _ -> Bool in + true } - navigation.rerouteForDiversion(from: CLLocation(latitude: 0, longitude: 0), along: navigation.routeProgress) + self.navigation.rerouteForDiversion(from: CLLocation(latitude: 0, longitude: 0), along: self.navigation.routeProgress) directionsClientSpy.fireLastCalculateCompletion(with: nil, routes: nil, error: NSError()) - waitForExpectations(timeout: 2) { (error) in + waitForExpectations(timeout: 2) { error in XCTAssertNil(error) } } diff --git a/MapboxCoreNavigationTests/NavigationLocationManagerTests.swift b/MapboxCoreNavigationTests/NavigationLocationManagerTests.swift index 88f80d0bf..23b6d5683 100644 --- a/MapboxCoreNavigationTests/NavigationLocationManagerTests.swift +++ b/MapboxCoreNavigationTests/NavigationLocationManagerTests.swift @@ -3,7 +3,6 @@ import MapboxCoreNavigation import XCTest class NavigationLocationManagerTests: XCTestCase { - func testNavigationLocationManagerDefaultAccuracy() { let locationManager = NavigationLocationManager() XCTAssertEqual(locationManager.desiredAccuracy, kCLLocationAccuracyBest) diff --git a/MapboxCoreNavigationTests/RouteControllerTests.swift b/MapboxCoreNavigationTests/RouteControllerTests.swift index 23214ec1a..8e91a0ee3 100644 --- a/MapboxCoreNavigationTests/RouteControllerTests.swift +++ b/MapboxCoreNavigationTests/RouteControllerTests.swift @@ -1,17 +1,17 @@ import CoreLocation +@testable import MapboxCoreNavigation import MapboxDirections import Turf import XCTest -@testable import MapboxCoreNavigation -fileprivate let mbTestHeading: CLLocationDirection = 50 +private let mbTestHeading: CLLocationDirection = 50 class RouteControllerTests: XCTestCase { - - struct Constants { + enum Constants { static let jsonRoute = (response["routes"] as! [AnyObject]).first as! [String: Any] static let accessToken = "nonsense" } + let directionsClientSpy = DirectionsSpy(accessToken: "garbage", host: nil) let delegate = RouteControllerDelegateSpy() @@ -19,7 +19,7 @@ class RouteControllerTests: XCTestCase { lazy var dependencies: (routeController: RouteController, routeLocations: RouteLocations) = { let routeController = RouteController(along: initialRoute, directions: directionsClientSpy, locationManager: NavigationLocationManager()) - routeController.delegate = delegate + routeController.delegate = self.delegate let legProgress: RouteLegProgress = routeController.routeProgress.currentLegProgress @@ -57,21 +57,21 @@ class RouteControllerTests: XCTestCase { override func setUp() { super.setUp() - directionsClientSpy.reset() - delegate.reset() + self.directionsClientSpy.reset() + self.delegate.reset() } func testUserIsOnRoute() { - let navigation = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation + let navigation = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation navigation.locationManager(navigation.locationManager, didUpdateLocations: [firstLocation]) XCTAssertTrue(navigation.userIsOnRoute(firstLocation), "User should be on route") } func testUserIsOffRoute() { - let navigation = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation + let navigation = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation let coordinateOffRoute = firstLocation.coordinate.coordinate(at: 100, facing: 90) let locationOffRoute = CLLocation(latitude: coordinateOffRoute.latitude, longitude: coordinateOffRoute.longitude) @@ -80,8 +80,8 @@ class RouteControllerTests: XCTestCase { } func testAdvancingToFutureStepAndNotRerouting() { - let navigation = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation + let navigation = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation navigation.locationManager(navigation.locationManager, didUpdateLocations: [firstLocation]) XCTAssertTrue(navigation.userIsOnRoute(firstLocation), "User should be on route") XCTAssertEqual(navigation.routeProgress.currentLegProgress.stepIndex, 0, "User is on first step") @@ -95,15 +95,15 @@ class RouteControllerTests: XCTestCase { } func testSnappedLocation() { - let navigation = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation + let navigation = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation navigation.locationManager(navigation.locationManager, didUpdateLocations: [firstLocation]) XCTAssertEqual(navigation.location!.coordinate, firstLocation.coordinate, "Check snapped location is working") } func testSnappedAtEndOfStepLocationWhenMovingSlowly() { - let navigation = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation + let navigation = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation navigation.locationManager(navigation.locationManager, didUpdateLocations: [firstLocation]) XCTAssertEqual(navigation.location!.coordinate, firstLocation.coordinate, "Check snapped location is working") @@ -120,8 +120,8 @@ class RouteControllerTests: XCTestCase { } func testSnappedAtEndOfStepLocationWhenCourseIsSimilar() { - let navigation = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation + let navigation = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation navigation.locationManager(navigation.locationManager, didUpdateLocations: [firstLocation]) XCTAssertEqual(navigation.location!.coordinate, firstLocation.coordinate, "Check snapped location is working") @@ -140,8 +140,8 @@ class RouteControllerTests: XCTestCase { } func testSnappedLocationForUnqualifiedLocation() { - let navigation = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation + let navigation = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation navigation.locationManager(navigation.locationManager, didUpdateLocations: [firstLocation]) XCTAssertEqual(navigation.location!.coordinate, firstLocation.coordinate, "Check snapped location is working") @@ -182,8 +182,8 @@ class RouteControllerTests: XCTestCase { } func testLocationShouldUseHeading() { - let navigation = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation + let navigation = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation navigation.locationManager(navigation.locationManager, didUpdateLocations: [firstLocation]) XCTAssertEqual(navigation.location!.course, firstLocation.course, "Course should be using course") @@ -201,17 +201,17 @@ class RouteControllerTests: XCTestCase { // MARK: - Events & Delegation func testReroutingFromALocationSendsEvents() { - let routeController = dependencies.routeController - let testLocation = dependencies.routeLocations.firstLocation + let routeController = self.dependencies.routeController + let testLocation = self.dependencies.routeLocations.firstLocation - let willRerouteNotificationExpectation = expectation(forNotification: .routeControllerWillReroute, object: routeController) { (notification) -> Bool in + let willRerouteNotificationExpectation = expectation(forNotification: .routeControllerWillReroute, object: routeController) { notification -> Bool in let fromLocation = notification.userInfo![RouteControllerNotificationUserInfoKey.locationKey] as? CLLocation return fromLocation == testLocation } let didRerouteNotificationExpectation = expectation(forNotification: .routeControllerDidReroute, object: routeController, handler: nil) - let routeProgressDidChangeNotificationExpectation = expectation(forNotification: .routeControllerProgressDidChange, object: routeController) { (notification) -> Bool in + let routeProgressDidChangeNotificationExpectation = expectation(forNotification: .routeControllerProgressDidChange, object: routeController) { notification -> Bool in let location = notification.userInfo![RouteControllerNotificationUserInfoKey.locationKey] as? CLLocation let rawLocation = notification.userInfo![RouteControllerNotificationUserInfoKey.rawLocationKey] as? CLLocation let _ = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as! RouteProgress @@ -220,86 +220,100 @@ class RouteControllerTests: XCTestCase { } // MARK: When told to re-route from location -- `reroute(from:)` + routeController.rerouteForDiversion(from: testLocation, along: routeController.routeProgress) // MARK: it tells the delegate & posts a willReroute notification - XCTAssertTrue(delegate.recentMessages.contains("routeController(_:willRerouteFrom:)")) + + XCTAssertTrue(self.delegate.recentMessages.contains("routeController(_:willRerouteFrom:)")) wait(for: [willRerouteNotificationExpectation], timeout: 0.1) // MARK: Upon rerouting successfully... - directionsClientSpy.fireLastCalculateCompletion(with: nil, routes: [alternateRoute], error: nil) + + self.directionsClientSpy.fireLastCalculateCompletion(with: nil, routes: [self.alternateRoute], error: nil) // MARK: It tells the delegate & posts a didReroute notification - XCTAssertTrue(delegate.recentMessages.contains("routeController(_:didRerouteAlong:reason:)")) + + XCTAssertTrue(self.delegate.recentMessages.contains("routeController(_:didRerouteAlong:reason:)")) wait(for: [didRerouteNotificationExpectation], timeout: 0.1) // MARK: On the next call to `locationManager(_, didUpdateLocations:)` + routeController.locationManager(routeController.locationManager, didUpdateLocations: [testLocation]) // MARK: It tells the delegate & posts a routeProgressDidChange notification - XCTAssertTrue(delegate.recentMessages.contains("routeController(_:didUpdate:)")) + + XCTAssertTrue(self.delegate.recentMessages.contains("routeController(_:didUpdate:)")) wait(for: [routeProgressDidChangeNotificationExpectation], timeout: 0.1) } func testGeneratingAnArrivalEvent() { - let routeController = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation - let penultimateLocation = dependencies.routeLocations.penultimateLocation - let lastLocation = dependencies.routeLocations.lastLocation + let routeController = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation + let penultimateLocation = self.dependencies.routeLocations.penultimateLocation + let lastLocation = self.dependencies.routeLocations.lastLocation // MARK: When navigation begins with a location update + routeController.locationManager(routeController.locationManager, didUpdateLocations: [firstLocation]) // MARK: When at a valid location just before the last location (should this really be necessary?) + routeController.locationManager(routeController.locationManager, didUpdateLocations: [penultimateLocation]) // MARK: When navigation continues with a location update to the last location + routeController.locationManager(routeController.locationManager, didUpdateLocations: [lastLocation]) // MARK: And then navigation continues with another location update at the last location + let currentLocation = routeController.location! routeController.locationManager(routeController.locationManager, didUpdateLocations: [currentLocation]) // MARK: It tells the delegate that the user did arrive - XCTAssertTrue(delegate.recentMessages.contains("routeController(_:didArriveAt:)")) + + XCTAssertTrue(self.delegate.recentMessages.contains("routeController(_:didArriveAt:)")) } func testNoReroutesAfterArriving() { - let routeController = dependencies.routeController - let firstLocation = dependencies.routeLocations.firstLocation - let penultimateLocation = dependencies.routeLocations.penultimateLocation - let lastLocation = dependencies.routeLocations.lastLocation + let routeController = self.dependencies.routeController + let firstLocation = self.dependencies.routeLocations.firstLocation + let penultimateLocation = self.dependencies.routeLocations.penultimateLocation + let lastLocation = self.dependencies.routeLocations.lastLocation // MARK: When navigation begins with a location update + routeController.locationManager(routeController.locationManager, didUpdateLocations: [firstLocation]) // MARK: When at a valid location just before the last location (should this really be necessary?) + routeController.locationManager(routeController.locationManager, didUpdateLocations: [penultimateLocation]) // MARK: When navigation continues with a location update to the last location + routeController.locationManager(routeController.locationManager, didUpdateLocations: [lastLocation]) // MARK: And then navigation continues with another location update at the last location + let currentLocation = routeController.location! routeController.locationManager(routeController.locationManager, didUpdateLocations: [currentLocation]) // MARK: It tells the delegate that the user did arrive - XCTAssertTrue(delegate.recentMessages.contains("routeController(_:didArriveAt:)")) + + XCTAssertTrue(self.delegate.recentMessages.contains("routeController(_:didArriveAt:)")) // Find a location that is very far off route let locationBeyondRoute = routeController.location!.coordinate.coordinate(at: 2000, facing: 0) routeController.locationManager(routeController.locationManager, didUpdateLocations: [CLLocation(latitude: locationBeyondRoute.latitude, longitude: locationBeyondRoute.latitude)]) // Make sure configurable delegate is called - XCTAssertTrue(delegate.recentMessages.contains("routeController(_:shouldPreventReroutesWhenArrivingAt:)")) + XCTAssertTrue(self.delegate.recentMessages.contains("routeController(_:shouldPreventReroutesWhenArrivingAt:)")) // We should not reroute here because the user has arrived. - XCTAssertFalse(delegate.recentMessages.contains("routeController(_:didRerouteAlong:)")) + XCTAssertFalse(self.delegate.recentMessages.contains("routeController(_:didRerouteAlong:)")) } - func testRouteControllerDoesNotHaveRetainCycle() { - weak var subject: RouteController? = nil autoreleasepool { @@ -312,11 +326,10 @@ class RouteControllerTests: XCTestCase { } func testRouteControllerNilsOutLocationDelegateOnDeinit() { - weak var subject: CLLocationManagerDelegate? = nil autoreleasepool { let locationManager = NavigationLocationManager() - _ = RouteController(along: initialRoute, directions: directionsClientSpy, locationManager: locationManager) + _ = RouteController(along: self.initialRoute, directions: self.directionsClientSpy, locationManager: locationManager) subject = locationManager.delegate } @@ -324,105 +337,90 @@ class RouteControllerTests: XCTestCase { } // MARK: - Matching route geometries - lazy var nijmegenArnhemVeenendaal = { - Route( - jsonFileName: "Nijmegen-Arnhem-Veenendaal", - waypoints: [ - CLLocationCoordinate2D(latitude: 51.83116792, longitude: 5.83897820), - CLLocationCoordinate2D(latitude: 52.03920380, longitude: 5.55133121) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + + lazy var nijmegenArnhemVeenendaal = Route( + jsonFileName: "Nijmegen-Arnhem-Veenendaal", + waypoints: [ + CLLocationCoordinate2D(latitude: 51.83116792, longitude: 5.83897820), + CLLocationCoordinate2D(latitude: 52.03920380, longitude: 5.55133121) + ], + bundle: .module, + accessToken: Constants.accessToken + ) - lazy var nijmegenBemmelVeenendaal = { - Route( - jsonFileName: "Nijmegen-Bemmel-Veenendaal", - waypoints: [ - CLLocationCoordinate2D(latitude: 51.83116792, longitude: 5.83897820), - CLLocationCoordinate2D(latitude: 52.03920380, longitude: 5.55133121) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + lazy var nijmegenBemmelVeenendaal = Route( + jsonFileName: "Nijmegen-Bemmel-Veenendaal", + waypoints: [ + CLLocationCoordinate2D(latitude: 51.83116792, longitude: 5.83897820), + CLLocationCoordinate2D(latitude: 52.03920380, longitude: 5.55133121) + ], + bundle: .module, + accessToken: Constants.accessToken + ) // Same route, routed on a different day - lazy var nijmegenBemmelVeenendaal2 = { - Route( - jsonFileName: "Nijmegen-Bemmel-Veenendaal2", - waypoints: [ - CLLocationCoordinate2D(latitude: 51.83116792, longitude: 5.83897820), - CLLocationCoordinate2D(latitude: 52.03920380, longitude: 5.55133121) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + lazy var nijmegenBemmelVeenendaal2 = Route( + jsonFileName: "Nijmegen-Bemmel-Veenendaal2", + waypoints: [ + CLLocationCoordinate2D(latitude: 51.83116792, longitude: 5.83897820), + CLLocationCoordinate2D(latitude: 52.03920380, longitude: 5.55133121) + ], + bundle: .module, + accessToken: Constants.accessToken + ) - lazy var wolfhezeVeenendaalNormal = { - Route( - jsonFileName: "Wolfheze-Veenendaal-Normal", - waypoints: [ - CLLocationCoordinate2D(latitude: 51.99711882858318, longitude: 5.7932572786103265), - CLLocationCoordinate2D(latitude: 52.0392038, longitude: 5.55133121) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + lazy var wolfhezeVeenendaalNormal = Route( + jsonFileName: "Wolfheze-Veenendaal-Normal", + waypoints: [ + CLLocationCoordinate2D(latitude: 51.99711882858318, longitude: 5.7932572786103265), + CLLocationCoordinate2D(latitude: 52.0392038, longitude: 5.55133121) + ], + bundle: .module, + accessToken: Constants.accessToken + ) - lazy var wolfhezeVeenendaalSmallDetourAtEnd = { - Route( - jsonFileName: "Wolfheze-Veenendaal-Small-Detour-At-End", - waypoints: [ - CLLocationCoordinate2D(latitude: 51.99711882858318, longitude: 5.7932572786103265), - CLLocationCoordinate2D(latitude: 52.04451273, longitude: 5.57902714), - CLLocationCoordinate2D(latitude: 52.0392038, longitude: 5.55133121) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + lazy var wolfhezeVeenendaalSmallDetourAtEnd = Route( + jsonFileName: "Wolfheze-Veenendaal-Small-Detour-At-End", + waypoints: [ + CLLocationCoordinate2D(latitude: 51.99711882858318, longitude: 5.7932572786103265), + CLLocationCoordinate2D(latitude: 52.04451273, longitude: 5.57902714), + CLLocationCoordinate2D(latitude: 52.0392038, longitude: 5.55133121) + ], + bundle: .module, + accessToken: Constants.accessToken + ) - lazy var a12ToVeenendaalNormal = { - Route( - jsonFileName: "A12-To-Veenendaal-Normal", - waypoints: [ - CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), - CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + lazy var a12ToVeenendaalNormal = Route( + jsonFileName: "A12-To-Veenendaal-Normal", + waypoints: [ + CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), + CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) + ], + bundle: .module, + accessToken: Constants.accessToken + ) - lazy var a12ToVeenendaalSlightDifference = { - Route( - jsonFileName: "A12-To-Veenendaal-Slight-Difference", - waypoints: [ - CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), - CLLocationCoordinate2D(latitude: 52.03917716, longitude: 5.55201356), - CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + lazy var a12ToVeenendaalSlightDifference = Route( + jsonFileName: "A12-To-Veenendaal-Slight-Difference", + waypoints: [ + CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), + CLLocationCoordinate2D(latitude: 52.03917716, longitude: 5.55201356), + CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) + ], + bundle: .module, + accessToken: Constants.accessToken + ) - lazy var a12ToVeenendaalBiggerDetour = { - Route( - jsonFileName: "A12-To-Veenendaal-Bigger-Detour", - waypoints: [ - CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), - CLLocationCoordinate2D(latitude: 52.04520875, longitude: 5.5748937), - CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + lazy var a12ToVeenendaalBiggerDetour = Route( + jsonFileName: "A12-To-Veenendaal-Bigger-Detour", + waypoints: [ + CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), + CLLocationCoordinate2D(latitude: 52.04520875, longitude: 5.5748937), + CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) + ], + bundle: .module, + accessToken: Constants.accessToken + ) func testRouteControllerMatchPercentage() { // These routes differ around 40% @@ -485,7 +483,7 @@ class RouteControllerTests: XCTestCase { // - Bigger detour -> 53% // --> Same route match is correct if let bestMatch = RouteController.bestMatch(for: a12ToVeenendaalNormal, and: firstRoutes) { - XCTAssertEqual(bestMatch.route, a12ToVeenendaalNormal) + XCTAssertEqual(bestMatch.route, self.a12ToVeenendaalNormal) XCTAssertEqual(bestMatch.matchPercentage, 100.0) } else { XCTFail("Should get a match above 90%") @@ -497,7 +495,7 @@ class RouteControllerTests: XCTestCase { // --> Slight difference route match is correct let secondRoutes = [a12ToVeenendaalSlightDifference, a12ToVeenendaalBiggerDetour].shuffled() if let bestMatch = RouteController.bestMatch(for: a12ToVeenendaalNormal, and: secondRoutes) { - XCTAssertEqual(bestMatch.route, a12ToVeenendaalSlightDifference) + XCTAssertEqual(bestMatch.route, self.a12ToVeenendaalSlightDifference) XCTAssertEqual(bestMatch.matchPercentage, 91.5, accuracy: 0.1) } else { XCTFail("Should get a match above 90%") @@ -513,21 +511,22 @@ class RouteControllerTests: XCTestCase { } // MARK: - Applying faster/slower route + func testApplyingFasterRoute() { - let routeController = dependencies.routeController + let routeController = self.dependencies.routeController let oldRouteProgress = routeController.routeProgress // Starting with route 'A12-To-Veenendaal-Normal' routeController.routeProgress = .init( - route: a12ToVeenendaalNormal, + route: self.a12ToVeenendaalNormal, legIndex: 0, spokenInstructionIndex: 0 ) // Try to apply slightly faster route 'A12-To-Veenendaal-Slight-Difference' routeController.applyNewRerouteIfNeeded( - mostSimilarRoute: a12ToVeenendaalSlightDifference, - allRoutes: [a12ToVeenendaalSlightDifference], + mostSimilarRoute: self.a12ToVeenendaalSlightDifference, + allRoutes: [self.a12ToVeenendaalSlightDifference], currentUpcomingManeuver: routeController.routeProgress.currentLegProgress.upComingStep!, durationRemaining: routeController.routeProgress.durationRemaining ) @@ -536,7 +535,7 @@ class RouteControllerTests: XCTestCase { XCTAssertEqual( routeController.routeProgress.durationRemaining, RouteProgress( - route: a12ToVeenendaalSlightDifference, + route: self.a12ToVeenendaalSlightDifference, legIndex: 0, spokenInstructionIndex: 0).durationRemaining ) @@ -546,20 +545,20 @@ class RouteControllerTests: XCTestCase { } func testNotApplyingSlowerRoute() { - let routeController = dependencies.routeController + let routeController = self.dependencies.routeController let oldRouteProgress = routeController.routeProgress // Starting with route 'A12-To-Veenendaal-Normal' routeController.routeProgress = .init( - route: a12ToVeenendaalNormal, + route: self.a12ToVeenendaalNormal, legIndex: 0, spokenInstructionIndex: 0 ) // Try to apply slower route 'A12-To-Veenendaal-Slight-Difference' routeController.applyNewRerouteIfNeeded( - mostSimilarRoute: a12ToVeenendaalBiggerDetour, - allRoutes: [a12ToVeenendaalBiggerDetour], + mostSimilarRoute: self.a12ToVeenendaalBiggerDetour, + allRoutes: [self.a12ToVeenendaalBiggerDetour], currentUpcomingManeuver: routeController.routeProgress.currentLegProgress.upComingStep!, durationRemaining: routeController.routeProgress.durationRemaining ) @@ -568,7 +567,7 @@ class RouteControllerTests: XCTestCase { XCTAssertNotEqual( routeController.routeProgress.durationRemaining, RouteProgress( - route: a12ToVeenendaalBiggerDetour, + route: self.a12ToVeenendaalBiggerDetour, legIndex: 0, spokenInstructionIndex: 0).durationRemaining ) @@ -579,33 +578,31 @@ class RouteControllerTests: XCTestCase { // Same exact JSON as a12ToVeenendaalNormal, but with one of the steps increased in 'duration' with 500 secs simulating a traffic jam // Makes checking 'durationRemaining' work, as that is a sum of all step's 'duration' in a leg - lazy var a12ToVeenendaalNormalWithTraffic = { - Route( - jsonFileName: "A12-To-Veenendaal-Normal-With-Big-Trafficjam", - waypoints: [ - CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), - CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) - ], - bundle: .module, - accessToken: Constants.accessToken - ) - }() + lazy var a12ToVeenendaalNormalWithTraffic = Route( + jsonFileName: "A12-To-Veenendaal-Normal-With-Big-Trafficjam", + waypoints: [ + CLLocationCoordinate2D(latitude: 52.02224357, longitude: 5.78149084), + CLLocationCoordinate2D(latitude: 52.03924958, longitude: 5.55054131) + ], + bundle: .module, + accessToken: Constants.accessToken + ) func testApplyingSlowerRoute() { - let routeController = dependencies.routeController + let routeController = self.dependencies.routeController let oldRouteProgress = routeController.routeProgress // Starting with route 'A12-To-Veenendaal-Normal' routeController.routeProgress = .init( - route: a12ToVeenendaalNormal, + route: self.a12ToVeenendaalNormal, legIndex: 0, spokenInstructionIndex: 0 ) // Try to apply slower route 'A12-To-Veenendaal-Normal-With-Big-Trafficjam' routeController.applyNewRerouteIfNeeded( - mostSimilarRoute: a12ToVeenendaalNormalWithTraffic, - allRoutes: [a12ToVeenendaalNormalWithTraffic], + mostSimilarRoute: self.a12ToVeenendaalNormalWithTraffic, + allRoutes: [self.a12ToVeenendaalNormalWithTraffic], currentUpcomingManeuver: routeController.routeProgress.currentLegProgress.upComingStep!, durationRemaining: routeController.routeProgress.durationRemaining ) @@ -614,7 +611,7 @@ class RouteControllerTests: XCTestCase { XCTAssertEqual( routeController.routeProgress.durationRemaining, RouteProgress( - route: a12ToVeenendaalNormalWithTraffic, + route: self.a12ToVeenendaalNormalWithTraffic, legIndex: 0, spokenInstructionIndex: 0).durationRemaining ) @@ -624,20 +621,20 @@ class RouteControllerTests: XCTestCase { } func testApplyingBestMatch() { - let routeController = dependencies.routeController + let routeController = self.dependencies.routeController let oldRouteProgress = routeController.routeProgress // Starting with route 'A12-To-Veenendaal-Normal' routeController.routeProgress = .init( - route: a12ToVeenendaalNormal, + route: self.a12ToVeenendaalNormal, legIndex: 0, spokenInstructionIndex: 0 ) // Try to apply slower route 'A12-To-Veenendaal-Normal-With-Big-Trafficjam' or faster route 'A12-To-Veenendaal-Slight-Difference' routeController.applyNewRerouteIfNeeded( - mostSimilarRoute: a12ToVeenendaalSlightDifference, - allRoutes: [a12ToVeenendaalNormalWithTraffic, a12ToVeenendaalSlightDifference], + mostSimilarRoute: self.a12ToVeenendaalSlightDifference, + allRoutes: [self.a12ToVeenendaalNormalWithTraffic, self.a12ToVeenendaalSlightDifference], currentUpcomingManeuver: routeController.routeProgress.currentLegProgress.upComingStep!, durationRemaining: routeController.routeProgress.durationRemaining ) @@ -646,7 +643,7 @@ class RouteControllerTests: XCTestCase { XCTAssertEqual( routeController.routeProgress.durationRemaining, RouteProgress( - route: a12ToVeenendaalNormalWithTraffic, + route: self.a12ToVeenendaalNormalWithTraffic, legIndex: 0, spokenInstructionIndex: 0).durationRemaining ) diff --git a/MapboxCoreNavigationTests/RouteProgressTests.swift b/MapboxCoreNavigationTests/RouteProgressTests.swift index 58589b6a0..03077e084 100644 --- a/MapboxCoreNavigationTests/RouteProgressTests.swift +++ b/MapboxCoreNavigationTests/RouteProgressTests.swift @@ -1,7 +1,7 @@ import Foundation -import XCTest -import MapboxDirections @testable import MapboxCoreNavigation +import MapboxDirections +import XCTest class RouteProgressTests: XCTestCase { func testRouteProgress() { diff --git a/MapboxCoreNavigationTests/StringTests.swift b/MapboxCoreNavigationTests/StringTests.swift index 5b8087861..c8075e971 100644 --- a/MapboxCoreNavigationTests/StringTests.swift +++ b/MapboxCoreNavigationTests/StringTests.swift @@ -1,5 +1,5 @@ -import XCTest @testable import MapboxCoreNavigation +import XCTest class StringTests: XCTestCase { func testMinimumEditDistance() { diff --git a/MapboxCoreNavigationTests/Support/DirectionsSpy.swift b/MapboxCoreNavigationTests/Support/DirectionsSpy.swift index 7498192ef..acc8d8190 100644 --- a/MapboxCoreNavigationTests/Support/DirectionsSpy.swift +++ b/MapboxCoreNavigationTests/Support/DirectionsSpy.swift @@ -3,27 +3,26 @@ import MapboxDirections @objc(MBDirectionsSpy) class DirectionsSpy: Directions { - var lastCalculateOptionsCompletion: RouteCompletionHandler? override func calculate(_ options: MatchOptions, completionHandler: @escaping Directions.MatchCompletionHandler) -> URLSessionDataTask { - assert(false, "Not ready to handle \(#function)") + assertionFailure("Not ready to handle \(#function)") return DummyURLSessionDataTask() } override func calculate(_ options: RouteOptions, completionHandler: @escaping Directions.RouteCompletionHandler) -> URLSessionDataTask { - lastCalculateOptionsCompletion = completionHandler + self.lastCalculateOptionsCompletion = completionHandler return DummyURLSessionDataTask() } override func calculateRoutes(matching options: MatchOptions, completionHandler: @escaping Directions.RouteCompletionHandler) -> URLSessionDataTask { - assert(false, "Not ready to handle \(#function)") + assertionFailure("Not ready to handle \(#function)") return DummyURLSessionDataTask() } public func fireLastCalculateCompletion(with waypoints: [Waypoint]?, routes: [Route]?, error: NSError?) { - guard let lastCalculateOptionsCompletion = lastCalculateOptionsCompletion else { - assert(false, "Can't fire a completion handler which doesn't exist!") + guard let lastCalculateOptionsCompletion else { + assertionFailure("Can't fire a completion handler which doesn't exist!") return } @@ -31,6 +30,6 @@ class DirectionsSpy: Directions { } public func reset() { - lastCalculateOptionsCompletion = nil + self.lastCalculateOptionsCompletion = nil } } diff --git a/MapboxCoreNavigationTests/Support/DummyURLSessionDataTask.swift b/MapboxCoreNavigationTests/Support/DummyURLSessionDataTask.swift index 0135d8231..e7964d87b 100644 --- a/MapboxCoreNavigationTests/Support/DummyURLSessionDataTask.swift +++ b/MapboxCoreNavigationTests/Support/DummyURLSessionDataTask.swift @@ -6,6 +6,6 @@ class DummyURLSessionDataTask: URLSessionDataTask { } override func cancel() { - // + // } } diff --git a/MapboxCoreNavigationTests/Support/RouteControllerDelegateSpy.swift b/MapboxCoreNavigationTests/Support/RouteControllerDelegateSpy.swift index 1a0990364..d00d88e35 100644 --- a/MapboxCoreNavigationTests/Support/RouteControllerDelegateSpy.swift +++ b/MapboxCoreNavigationTests/Support/RouteControllerDelegateSpy.swift @@ -7,42 +7,42 @@ class RouteControllerDelegateSpy: RouteControllerDelegate { private(set) var recentMessages: [String] = [] public func reset() { - recentMessages.removeAll() + self.recentMessages.removeAll() } - internal func routeController(_ routeController: RouteController, shouldRerouteFrom location: CLLocation) -> Bool { - recentMessages.append(#function) + func routeController(_ routeController: RouteController, shouldRerouteFrom location: CLLocation) -> Bool { + self.recentMessages.append(#function) return true } - internal func routeController(_ routeController: RouteController, willRerouteFrom location: CLLocation) { - recentMessages.append(#function) + func routeController(_ routeController: RouteController, willRerouteFrom location: CLLocation) { + self.recentMessages.append(#function) } - internal func routeController(_ routeController: RouteController, shouldDiscard location: CLLocation) -> Bool { - recentMessages.append(#function) + func routeController(_ routeController: RouteController, shouldDiscard location: CLLocation) -> Bool { + self.recentMessages.append(#function) return true } - internal func routeController(_ routeController: RouteController, didRerouteAlong route: Route, reason: RouteController.RerouteReason) { - recentMessages.append(#function) + func routeController(_ routeController: RouteController, didRerouteAlong route: Route, reason: RouteController.RerouteReason) { + self.recentMessages.append(#function) } - internal func routeController(_ routeController: RouteController, didFailToRerouteWith error: Error) { - recentMessages.append(#function) + func routeController(_ routeController: RouteController, didFailToRerouteWith error: Error) { + self.recentMessages.append(#function) } - internal func routeController(_ routeController: RouteController, didUpdate locations: [CLLocation]) { - recentMessages.append(#function) + func routeController(_ routeController: RouteController, didUpdate locations: [CLLocation]) { + self.recentMessages.append(#function) } - internal func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool { - recentMessages.append(#function) + func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool { + self.recentMessages.append(#function) return true } - internal func routeController(_ routeController: RouteController, shouldPreventReroutesWhenArrivingAt waypoint: Waypoint) -> Bool { - recentMessages.append(#function) + func routeController(_ routeController: RouteController, shouldPreventReroutesWhenArrivingAt waypoint: Waypoint) -> Bool { + self.recentMessages.append(#function) return true } } diff --git a/MapboxCoreNavigationTests/TunnelIntersectionManagerTests.swift b/MapboxCoreNavigationTests/TunnelIntersectionManagerTests.swift index 9278f7215..adb46d9e2 100644 --- a/MapboxCoreNavigationTests/TunnelIntersectionManagerTests.swift +++ b/MapboxCoreNavigationTests/TunnelIntersectionManagerTests.swift @@ -1,10 +1,10 @@ -import XCTest -import MapboxDirections -import Turf import CoreLocation @testable import MapboxCoreNavigation +import MapboxDirections +import Turf +import XCTest -struct TunnelDetectorTestData { +enum TunnelDetectorTestData { static let ninthStreetFileName = "routeWithTunnels_9thStreetDC" static let kRouteKey = "routes" static let startLocation = CLLocationCoordinate2D(latitude: 38.890774, longitude: -77.023970) @@ -18,7 +18,6 @@ let tunnelWaypoint2 = Waypoint(coordinate: TunnelDetectorTestData.endLocation) let tunnelRoute = Route(json: tunnelJsonRoute, waypoints: [tunnelWayPoint1, tunnelWaypoint2], options: NavigationRouteOptions(waypoints: [tunnelWayPoint1, tunnelWaypoint2])) class TunnelIntersectionManagerTests: XCTestCase { - lazy var tunnelSetup: (tunnelIntersectionManager: TunnelIntersectionManager, routeController: RouteController, firstLocation: CLLocation) = { tunnelRoute.accessToken = "foo" let navigation = RouteController(along: tunnelRoute, directions: directions) @@ -26,20 +25,20 @@ class TunnelIntersectionManagerTests: XCTestCase { let tunnelIntersectionManager = TunnelIntersectionManager() return (tunnelIntersectionManager: tunnelIntersectionManager, - routeController: navigation, - firstLocation: CLLocation(coordinate: firstCoord, - altitude: 5, - horizontalAccuracy: 10, - verticalAccuracy: 5, - course: 20, - speed: 6, - timestamp: Date())) + routeController: navigation, + firstLocation: CLLocation(coordinate: firstCoord, + altitude: 5, + horizontalAccuracy: 10, + verticalAccuracy: 5, + course: 20, + speed: 6, + timestamp: Date())) }() func testUserWithinTunnelEntranceRadius() { - let routeController = tunnelSetup.routeController + let routeController = self.tunnelSetup.routeController - routeController.tunnelIntersectionManager = tunnelSetup.tunnelIntersectionManager + routeController.tunnelIntersectionManager = self.tunnelSetup.tunnelIntersectionManager let tunnelIntersectionManager = routeController.tunnelIntersectionManager @@ -55,9 +54,9 @@ class TunnelIntersectionManagerTests: XCTestCase { // Mock location moved from the first location on route to the tunnel intersection location var currentLocation = location(at: tunnelSetup.firstLocation.coordinate, - for: routeController, - intersection: tunnelIntersection, - distance: intersectionLocation.distance(to: tunnelSetup.firstLocation.coordinate)) + for: routeController, + intersection: tunnelIntersection, + distance: intersectionLocation.distance(to: self.tunnelSetup.firstLocation.coordinate)) routeController.locationManager(routeController.locationManager, didUpdateLocations: [currentLocation]) @@ -69,24 +68,24 @@ class TunnelIntersectionManagerTests: XCTestCase { var userIsAtTunnelEntranceRadius = tunnelIntersectionManager.userWithinTunnelEntranceRadius(at: currentLocation, routeProgress: routeController.routeProgress) XCTAssertTrue(userIsAtTunnelEntranceRadius, "Location must be within the tunnel entrance radius") - let outsideTunnelEntranceRadius = intersectionLocation.coordinate(at: 200, facing: intersectionLocation.direction(to: tunnelSetup.firstLocation.coordinate)) + let outsideTunnelEntranceRadius = intersectionLocation.coordinate(at: 200, facing: intersectionLocation.direction(to: self.tunnelSetup.firstLocation.coordinate)) let outsideTunnelEntranceRadiusLocation = CLLocation(latitude: outsideTunnelEntranceRadius.latitude, longitude: outsideTunnelEntranceRadius.longitude) routeController.locationManager(routeController.locationManager, didUpdateLocations: [outsideTunnelEntranceRadiusLocation]) - currentLocation = location(at: tunnelSetup.firstLocation.coordinate, - for: routeController, - intersection: tunnelIntersection, - distance: 10) + currentLocation = location(at: self.tunnelSetup.firstLocation.coordinate, + for: routeController, + intersection: tunnelIntersection, + distance: 10) userIsAtTunnelEntranceRadius = tunnelIntersectionManager.userWithinTunnelEntranceRadius(at: currentLocation, routeProgress: routeController.routeProgress) XCTAssertFalse(userIsAtTunnelEntranceRadius, "Location must not be within the tunnel entrance radius") } func testTunnelDetected() { - let routeController = tunnelSetup.routeController + let routeController = self.tunnelSetup.routeController - routeController.tunnelIntersectionManager = tunnelSetup.tunnelIntersectionManager + routeController.tunnelIntersectionManager = self.tunnelSetup.tunnelIntersectionManager routeController.tunnelIntersectionManager.delegate = routeController // Step with a tunnel intersection @@ -106,7 +105,7 @@ class TunnelIntersectionManagerTests: XCTestCase { // Step without a tunnel intersection routeController.advanceStepIndex(to: 2) - fakeLocation = location(at: tunnelSetup.firstLocation.coordinate, for: routeController, intersection: routeController.routeProgress.currentLegProgress.currentStep.intersections![0]) + fakeLocation = location(at: self.tunnelSetup.firstLocation.coordinate, for: routeController, intersection: routeController.routeProgress.currentLegProgress.currentStep.intersections![0]) routeController.locationManager(routeController.locationManager, didUpdateLocations: [fakeLocation]) didDetectTunnel = routeController.tunnelIntersectionManager.userWithinTunnelEntranceRadius(at: routeController.location!, routeProgress: routeController.routeProgress) @@ -115,9 +114,9 @@ class TunnelIntersectionManagerTests: XCTestCase { } func testTunnelSimulatedNavigationEnabled() { - let routeController = tunnelSetup.routeController + let routeController = self.tunnelSetup.routeController - routeController.tunnelIntersectionManager = tunnelSetup.tunnelIntersectionManager + routeController.tunnelIntersectionManager = self.tunnelSetup.tunnelIntersectionManager routeController.tunnelIntersectionManager.delegate = routeController // Step with a tunnel intersection @@ -128,9 +127,9 @@ class TunnelIntersectionManagerTests: XCTestCase { let intersectionLocation = tunnelIntersection.location let currentLocation = location(at: tunnelSetup.firstLocation.coordinate, - for: routeController, - intersection: tunnelIntersection, - distance: intersectionLocation.distance(to: tunnelSetup.firstLocation.coordinate)) + for: routeController, + intersection: tunnelIntersection, + distance: intersectionLocation.distance(to: self.tunnelSetup.firstLocation.coordinate)) routeController.locationManager(routeController.locationManager, didUpdateLocations: [currentLocation]) @@ -145,9 +144,9 @@ class TunnelIntersectionManagerTests: XCTestCase { } func testTunnelSimulatedNavigationDisabled() { - let routeController = tunnelSetup.routeController + let routeController = self.tunnelSetup.routeController - routeController.tunnelIntersectionManager = tunnelSetup.tunnelIntersectionManager + routeController.tunnelIntersectionManager = self.tunnelSetup.tunnelIntersectionManager routeController.tunnelIntersectionManager.delegate = routeController // Step after a tunnel intersection @@ -158,9 +157,9 @@ class TunnelIntersectionManagerTests: XCTestCase { let intersectionLocation = tunnelExitIntersection.location let currentLocation = location(at: tunnelSetup.firstLocation.coordinate, - for: routeController, - intersection: tunnelExitIntersection, - distance: intersectionLocation.distance(to: tunnelSetup.firstLocation.coordinate)) + for: routeController, + intersection: tunnelExitIntersection, + distance: intersectionLocation.distance(to: self.tunnelSetup.firstLocation.coordinate)) routeController.locationManager(routeController.locationManager, didUpdateLocations: [currentLocation]) @@ -180,32 +179,29 @@ class TunnelIntersectionManagerTests: XCTestCase { routeController.tunnelIntersectionManager(routeController.tunnelIntersectionManager, willDisableAnimationAt: tunnelExitLocation) XCTAssertFalse(tunnelIntersectionManager.isAnimationEnabled, "Animation through tunnel should be disabled after 3 location updates.") } - } -extension TunnelIntersectionManagerTests { - - fileprivate func location(at coordinate: CLLocationCoordinate2D, - for routeController: RouteController, - intersection: Intersection, - distance: CLLocationDistance? = 200) -> CLLocation { - +private extension TunnelIntersectionManagerTests { + func location(at coordinate: CLLocationCoordinate2D, + for routeController: RouteController, + intersection: Intersection, + distance: CLLocationDistance? = 200) -> CLLocation { let polyline = Polyline(routeController.routeProgress.currentLegProgress.currentStep.coordinates!) let newLocation = CLLocationCoordinate2D(latitude: coordinate.latitude, - longitude: coordinate.longitude).coordinate( - at: distance!, - facing: (polyline.coordinates.first?.direction(to: intersection.location))! + longitude: coordinate.longitude).coordinate( + at: distance!, + facing: (polyline.coordinates.first?.direction(to: intersection.location))! ) - return location(at: newLocation) + return self.location(at: newLocation) } - fileprivate func location(at coordinate: CLLocationCoordinate2D, horizontalAccuracy: CLLocationAccuracy? = 258.20) -> CLLocation { - return CLLocation(coordinate: coordinate, - altitude: 5, - horizontalAccuracy: horizontalAccuracy!, - verticalAccuracy: 200, - course: 20, - speed: 15, - timestamp: Date()) + func location(at coordinate: CLLocationCoordinate2D, horizontalAccuracy: CLLocationAccuracy? = 258.20) -> CLLocation { + CLLocation(coordinate: coordinate, + altitude: 5, + horizontalAccuracy: horizontalAccuracy!, + verticalAccuracy: 200, + course: 20, + speed: 15, + timestamp: Date()) } } diff --git a/MapboxNavigation/BottomBannerView.swift b/MapboxNavigation/BottomBannerView.swift index c395c4ef3..d6083acee 100644 --- a/MapboxNavigation/BottomBannerView.swift +++ b/MapboxNavigation/BottomBannerView.swift @@ -1,6 +1,6 @@ -import UIKit import MapboxCoreNavigation import MapboxDirections +import UIKit protocol BottomBannerViewDelegate: AnyObject { func didCancel() @@ -10,7 +10,6 @@ protocol BottomBannerViewDelegate: AnyObject { @IBDesignable @objc(MBBottomBannerView) open class BottomBannerView: UIView { - weak var timeRemainingLabel: TimeRemainingLabel! weak var distanceRemainingLabel: DistanceRemainingLabel! weak var arrivalTimeLabel: ArrivalTimeLabel! @@ -31,71 +30,71 @@ open class BottomBannerView: UIView { var congestionLevel: CongestionLevel = .unknown { didSet { - switch congestionLevel { + switch self.congestionLevel { case .unknown: - timeRemainingLabel.textColor = timeRemainingLabel.trafficUnknownColor + self.timeRemainingLabel.textColor = self.timeRemainingLabel.trafficUnknownColor case .low: - timeRemainingLabel.textColor = timeRemainingLabel.trafficLowColor + self.timeRemainingLabel.textColor = self.timeRemainingLabel.trafficLowColor case .moderate: - timeRemainingLabel.textColor = timeRemainingLabel.trafficModerateColor + self.timeRemainingLabel.textColor = self.timeRemainingLabel.trafficModerateColor case .heavy: - timeRemainingLabel.textColor = timeRemainingLabel.trafficHeavyColor + self.timeRemainingLabel.textColor = self.timeRemainingLabel.trafficHeavyColor case .severe: - timeRemainingLabel.textColor = timeRemainingLabel.trafficSevereColor + self.timeRemainingLabel.textColor = self.timeRemainingLabel.trafficSevereColor } } } override init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { - dateFormatter.timeStyle = .short - dateComponentsFormatter.allowedUnits = [.hour, .minute] - dateComponentsFormatter.unitsStyle = .abbreviated + self.dateFormatter.timeStyle = .short + self.dateComponentsFormatter.allowedUnits = [.hour, .minute] + self.dateComponentsFormatter.unitsStyle = .abbreviated setupViews() - cancelButton.addTarget(self, action: #selector(BottomBannerView.cancel(_:)), for: .touchUpInside) + self.cancelButton.addTarget(self, action: #selector(BottomBannerView.cancel(_:)), for: .touchUpInside) } @IBAction func cancel(_ sender: Any) { - delegate?.didCancel() + self.delegate?.didCancel() } override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() - timeRemainingLabel.text = "22 min" - distanceRemainingLabel.text = "4 mi" - arrivalTimeLabel.text = "10:09" + self.timeRemainingLabel.text = "22 min" + self.distanceRemainingLabel.text = "4 mi" + self.arrivalTimeLabel.text = "10:09" } func updateETA(routeProgress: RouteProgress) { guard let arrivalDate = NSCalendar.current.date(byAdding: .second, value: Int(routeProgress.durationRemaining), to: Date()) else { return } - arrivalTimeLabel.text = dateFormatter.string(from: arrivalDate) + self.arrivalTimeLabel.text = self.dateFormatter.string(from: arrivalDate) if routeProgress.durationRemaining < 5 { - distanceRemainingLabel.text = nil + self.distanceRemainingLabel.text = nil } else { - distanceRemainingLabel.text = distanceFormatter.string(from: routeProgress.distanceRemaining) + self.distanceRemainingLabel.text = self.distanceFormatter.string(from: routeProgress.distanceRemaining) } - dateComponentsFormatter.unitsStyle = routeProgress.durationRemaining < 3600 ? .short : .abbreviated + self.dateComponentsFormatter.unitsStyle = routeProgress.durationRemaining < 3600 ? .short : .abbreviated if let hardcodedTime = dateComponentsFormatter.string(from: 61), routeProgress.durationRemaining < 60 { - timeRemainingLabel.text = String.localizedStringWithFormat(NSLocalizedString("LESS_THAN", bundle: .mapboxNavigation, value: "<%@", comment: "Format string for a short distance or time less than a minimum threshold; 1 = duration remaining"), hardcodedTime) + self.timeRemainingLabel.text = String.localizedStringWithFormat(NSLocalizedString("LESS_THAN", bundle: .mapboxNavigation, value: "<%@", comment: "Format string for a short distance or time less than a minimum threshold; 1 = duration remaining"), hardcodedTime) } else { - timeRemainingLabel.text = dateComponentsFormatter.string(from: routeProgress.durationRemaining) + self.timeRemainingLabel.text = self.dateComponentsFormatter.string(from: routeProgress.durationRemaining) } guard let congestionForRemainingLeg = routeProgress.averageCongestionLevelRemainingOnLeg else { return } - congestionLevel = congestionForRemainingLeg + self.congestionLevel = congestionForRemainingLeg } } diff --git a/MapboxNavigation/BottomBannerViewLayout.swift b/MapboxNavigation/BottomBannerViewLayout.swift index 2fd7719e7..4a25fe583 100644 --- a/MapboxNavigation/BottomBannerViewLayout.swift +++ b/MapboxNavigation/BottomBannerViewLayout.swift @@ -1,9 +1,7 @@ import UIKit extension BottomBannerView { - func setupViews() { - let timeRemainingLabel = TimeRemainingLabel() timeRemainingLabel.translatesAutoresizingMaskIntoConstraints = false timeRemainingLabel.font = .systemFont(ofSize: 28, weight: .medium) @@ -30,22 +28,22 @@ extension BottomBannerView { let verticalDivider = SeparatorView() verticalDivider.translatesAutoresizingMaskIntoConstraints = false addSubview(verticalDivider) - self.verticalDividerView = verticalDivider + verticalDividerView = verticalDivider let horizontalDividerView = SeparatorView() horizontalDividerView.translatesAutoresizingMaskIntoConstraints = false addSubview(horizontalDividerView) self.horizontalDividerView = horizontalDividerView - setupConstraints() + self.setupConstraints() } - fileprivate func setupConstraints() { - setupVerticalCompactLayout(&verticalCompactConstraints) - setupVerticalRegularLayout(&verticalRegularConstraints) + private func setupConstraints() { + self.setupVerticalCompactLayout(&verticalCompactConstraints) + self.setupVerticalRegularLayout(&verticalRegularConstraints) } - fileprivate func setupVerticalCompactLayout(_ c: inout [NSLayoutConstraint]) { + private func setupVerticalCompactLayout(_ c: inout [NSLayoutConstraint]) { c.append(heightAnchor.constraint(equalToConstant: 50)) c.append(cancelButton.widthAnchor.constraint(equalTo: heightAnchor)) @@ -73,7 +71,7 @@ extension BottomBannerView { c.append(arrivalTimeLabel.centerYAnchor.constraint(equalTo: cancelButton.centerYAnchor)) } - fileprivate func setupVerticalRegularLayout(_ c: inout [NSLayoutConstraint]) { + private func setupVerticalRegularLayout(_ c: inout [NSLayoutConstraint]) { c.append(heightAnchor.constraint(equalToConstant: 80)) c.append(timeRemainingLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10)) @@ -101,7 +99,7 @@ extension BottomBannerView { c.append(arrivalTimeLabel.trailingAnchor.constraint(equalTo: verticalDividerView.leadingAnchor, constant: -10)) } - open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + override open func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) verticalCompactConstraints.forEach { $0.isActive = traitCollection.verticalSizeClass == .compact } verticalRegularConstraints.forEach { $0.isActive = traitCollection.verticalSizeClass != .compact } diff --git a/MapboxNavigation/Bundle.swift b/MapboxNavigation/Bundle.swift index a970ae6d8..9160fd69b 100644 --- a/MapboxNavigation/Bundle.swift +++ b/MapboxNavigation/Bundle.swift @@ -1,20 +1,15 @@ import UIKit extension Bundle { - - class var mapboxNavigation: Bundle { - get { .module } - } + class var mapboxNavigation: Bundle { .module } func image(named: String) -> UIImage? { - return UIImage(named: named, in: self, compatibleWith: nil) + UIImage(named: named, in: self, compatibleWith: nil) } var microphoneUsageDescription: String? { - get { - let para = "NSMicrophoneUsageDescription" - let key = "Privacy - Microphone Usage Description" - return object(forInfoDictionaryKey: para) as? String ?? object(forInfoDictionaryKey: key) as? String - } + let para = "NSMicrophoneUsageDescription" + let key = "Privacy - Microphone Usage Description" + return object(forInfoDictionaryKey: para) as? String ?? object(forInfoDictionaryKey: key) as? String } } diff --git a/MapboxNavigation/CGPoint.swift b/MapboxNavigation/CGPoint.swift index 957b11b9b..f400d8094 100644 --- a/MapboxNavigation/CGPoint.swift +++ b/MapboxNavigation/CGPoint.swift @@ -1,10 +1,10 @@ import Foundation -extension CGPoint { +public extension CGPoint { /** Calculates the straight line distance between two `CGPoint`. */ - public func distance(to: CGPoint) -> CGFloat { - return sqrt((self.x - to.x) * (self.x - to.x) + (self.y - to.y) * (self.y - to.y)) + func distance(to: CGPoint) -> CGFloat { + sqrt((x - to.x) * (x - to.x) + (y - to.y) * (y - to.y)) } } diff --git a/MapboxNavigation/CGSize.swift b/MapboxNavigation/CGSize.swift index 333b60f8e..4dfdcb959 100644 --- a/MapboxNavigation/CGSize.swift +++ b/MapboxNavigation/CGSize.swift @@ -13,6 +13,6 @@ extension CGSize: ExpressibleByFloatLiteral { } var aspectRatio: CGFloat { - return width / height + width / height } } diff --git a/MapboxNavigation/CPMapTemplate.swift b/MapboxNavigation/CPMapTemplate.swift index 1c638f2b2..f9910daac 100644 --- a/MapboxNavigation/CPMapTemplate.swift +++ b/MapboxNavigation/CPMapTemplate.swift @@ -6,7 +6,7 @@ import CarPlay @available(iOS 12.0, *) extension CLLocationDirection { init?(panDirection: CPMapTemplate.PanDirection) { - var horizontalBias: Double? = nil + var horizontalBias: Double? if panDirection.contains(.right) { horizontalBias = 90 } else if panDirection.contains(.left) { @@ -33,4 +33,3 @@ extension CLLocationDirection { } } #endif - diff --git a/MapboxNavigation/Cache.swift b/MapboxNavigation/Cache.swift index 888126d1b..14e6c2d18 100644 --- a/MapboxNavigation/Cache.swift +++ b/MapboxNavigation/Cache.swift @@ -29,10 +29,10 @@ public protocol BimodalDataCache: BimodalCache { func data(forKey: String?) -> Data? } - /** +/** A general purpose on-disk cache used by both the ImageCache and DataCache implementations */ -internal class FileCache { +class FileCache { let diskCacheURL: URL = { let fileManager = FileManager.default let basePath = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first! @@ -44,8 +44,8 @@ internal class FileCache { var fileManager: FileManager? init() { - diskAccessQueue.sync { - fileManager = FileManager() + self.diskAccessQueue.sync { + self.fileManager = FileManager() } } @@ -53,12 +53,12 @@ internal class FileCache { Stores data in the file cache for the given key, and calls the completion handler when finished. */ public func store(_ data: Data, forKey key: String, completion: CompletionHandler?) { - guard let fileManager = fileManager else { + guard let fileManager else { completion?() return } - diskAccessQueue.async { + self.diskAccessQueue.async { self.createCacheDirIfNeeded(self.diskCacheURL, fileManager: fileManager) let cacheURL = self.cacheURLWithKey(key) @@ -69,19 +69,18 @@ internal class FileCache { } completion?() } - } /* Returns data from the file cache for the given key, if any. */ public func dataFromFileCache(forKey key: String?) -> Data? { - guard let key = key else { + guard let key else { return nil } do { - return try Data.init(contentsOf: cacheURLWithKey(key)) + return try Data(contentsOf: self.cacheURLWithKey(key)) } catch { return nil } @@ -91,7 +90,7 @@ internal class FileCache { Clears the disk cache by removing and recreating the cache directory, and calls the completion handler when finished. */ public func clearDisk(completion: CompletionHandler?) { - guard let fileManager = fileManager else { + guard let fileManager else { return } @@ -110,17 +109,17 @@ internal class FileCache { } func cachePathWithKey(_ key: String) -> String { - let cacheKey = cacheKeyForKey(key) - return cacheURLWithKey(cacheKey).absoluteString + let cacheKey = self.cacheKeyForKey(key) + return self.cacheURLWithKey(cacheKey).absoluteString } func cacheURLWithKey(_ key: String) -> URL { - let cacheKey = cacheKeyForKey(key) - return diskCacheURL.appendingPathComponent(cacheKey) + let cacheKey = self.cacheKeyForKey(key) + return self.diskCacheURL.appendingPathComponent(cacheKey) } func cacheKeyForKey(_ key: String) -> String { - return key.md5() + key.md5() } private func createCacheDirIfNeeded(_ url: URL, fileManager: FileManager) { diff --git a/MapboxNavigation/CarPlayManager+Search.swift b/MapboxNavigation/CarPlayManager+Search.swift index 01baf13e9..9bd3346f3 100644 --- a/MapboxNavigation/CarPlayManager+Search.swift +++ b/MapboxNavigation/CarPlayManager+Search.swift @@ -1,12 +1,11 @@ #if canImport(CarPlay) && canImport(MapboxGeocoder) -import Foundation import CarPlay -import MapboxGeocoder +import Foundation import MapboxDirections +import MapboxGeocoder @available(iOS 12.0, *) extension CarPlayManager: CPSearchTemplateDelegate { - public static let CarPlayGeocodedPlacemarkKey: String = "MBGecodedPlacemark" static var recentItems = RecentItem.loadDefaults() @@ -44,8 +43,7 @@ extension CarPlayManager: CPSearchTemplateDelegate { } func searchTemplateButton(searchTemplate: CPSearchTemplate, interfaceController: CPInterfaceController, traitCollection: UITraitCollection) -> CPBarButton { - - let searchTemplateButton = CPBarButton(type: .image) { [weak self] button in + let searchTemplateButton = CPBarButton(type: .image) { [weak self] _ in guard let strongSelf = self else { return } @@ -68,26 +66,25 @@ extension CarPlayManager: CPSearchTemplateDelegate { CarPlayManager.shared.recentSearchText = searchText // Append recent searches - var items = recentSearches(searchText) + var items = self.recentSearches(searchText) // Search for placemarks using MapboxGeocoder.swift let shouldSearch = searchText.count > 2 if shouldSearch { - let options = CarPlayManager.forwardGeocodeOptions(searchText) - Geocoder.shared.geocode(options, completionHandler: { (placemarks, attribution, error) in - guard let placemarks = placemarks else { - completionHandler(CarPlayManager.resultsOrNoResults(items, limit: MaximumInitialSearchResults)) + Geocoder.shared.geocode(options, completionHandler: { placemarks, _, _ in + guard let placemarks else { + completionHandler(CarPlayManager.resultsOrNoResults(items, limit: self.MaximumInitialSearchResults)) return } let results = placemarks.map { $0.listItem() } items.append(contentsOf: results) - completionHandler(CarPlayManager.resultsOrNoResults(results, limit: MaximumInitialSearchResults)) + completionHandler(CarPlayManager.resultsOrNoResults(results, limit: self.MaximumInitialSearchResults)) }) } else { - completionHandler(CarPlayManager.resultsOrNoResults(items, limit: MaximumInitialSearchResults)) + completionHandler(CarPlayManager.resultsOrNoResults(items, limit: self.MaximumInitialSearchResults)) } } @@ -106,16 +103,15 @@ extension CarPlayManager: CPSearchTemplateDelegate { @available(iOS 12.0, *) public static func carPlayManager(_ searchTemplate: CPSearchTemplate, selectedResult item: CPListItem, completionHandler: @escaping () -> Void) { - guard let userInfo = item.userInfo as? [String: Any], - let placemark = userInfo[CarPlayGeocodedPlacemarkKey] as? GeocodedPlacemark, - let location = placemark.location else { - completionHandler() - return + let placemark = userInfo[CarPlayGeocodedPlacemarkKey] as? GeocodedPlacemark, + let location = placemark.location else { + completionHandler() + return } - recentItems.add(RecentItem(placemark)) - recentItems.save() + self.recentItems.add(RecentItem(placemark)) + self.recentItems.save() let destinationWaypoint = Waypoint(location: location, heading: nil, name: placemark.formattedName) CarPlayManager.shared.calculateRouteAndStart(to: destinationWaypoint, completionHandler: completionHandler) @@ -124,9 +120,9 @@ extension CarPlayManager: CPSearchTemplateDelegate { @available(iOS 12.0, *) static func recentSearches(_ searchText: String) -> [CPListItem] { if searchText.isEmpty { - return recentItems.map { $0.geocodedPlacemark.listItem() } + return self.recentItems.map { $0.geocodedPlacemark.listItem() } } - return recentItems.filter { $0.matches(searchText) }.map { $0.geocodedPlacemark.listItem() } + return self.recentItems.filter { $0.matches(searchText) }.map { $0.geocodedPlacemark.listItem() } } @available(iOS 12.0, *) @@ -134,8 +130,8 @@ extension CarPlayManager: CPSearchTemplateDelegate { CarPlayManager.shared.recentSearchItems = items if items.count > 0 { - if let limit = limit { - return Array(items.prefix(Int(limit))) + if let limit { + return [CPListItem](items.prefix(Int(limit))) } return items @@ -148,7 +144,6 @@ extension CarPlayManager: CPSearchTemplateDelegate { } extension GeocodedPlacemark { - @available(iOS 12.0, *) func listItem() -> CPListItem { let item = CPListItem(text: formattedName, detailText: subtitle, image: nil, showsDisclosureIndicator: true) @@ -157,7 +152,7 @@ extension GeocodedPlacemark { } var subtitle: String? { - if let addressDictionary = addressDictionary, var lines = addressDictionary["formattedAddressLines"] as? [String] { + if let addressDictionary, var lines = addressDictionary["formattedAddressLines"] as? [String] { // Chinese addresses have no commas and are reversed. if scope == .address { if qualifiedName?.contains(", ") ?? false { @@ -168,7 +163,7 @@ extension GeocodedPlacemark { } if let regionCode = administrativeRegion?.code, - let abbreviatedRegion = regionCode.components(separatedBy: "-").last, (abbreviatedRegion as NSString).intValue == 0 { + let abbreviatedRegion = regionCode.components(separatedBy: "-").last, (abbreviatedRegion as NSString).intValue == 0 { // Cut off country and postal code and add abbreviated state/region code at the end. let stitle = lines.prefix(2).joined(separator: NSLocalizedString("ADDRESS_LINE_SEPARATOR", value: ", ", comment: "Delimiter between lines in an address when displayed inline")) diff --git a/MapboxNavigation/CarPlayManager.swift b/MapboxNavigation/CarPlayManager.swift index 1a8ec3439..e600264fa 100644 --- a/MapboxNavigation/CarPlayManager.swift +++ b/MapboxNavigation/CarPlayManager.swift @@ -3,9 +3,9 @@ import CarPlay #if canImport(MapboxGeocoder) import MapboxGeocoder #endif -import Turf import MapboxCoreNavigation import MapboxDirections +import Turf /** The activity during which a `CPTemplate` is displayed. This enumeration is used to distinguish between different templates during different phases of user interaction. @@ -29,7 +29,6 @@ public enum CarPlayActivity: Int { @available(iOS 12.0, *) @objc(MBCarPlayManagerDelegate) public protocol CarPlayManagerDelegate { - /** Offers the delegate an opportunity to provide a customized list of leading bar buttons. @@ -117,14 +116,14 @@ public protocol CarPlayManagerDelegate { - parameter routeController: The route controller that has begun managing location updates for a navigation session. */ @objc(carPlayManager:didBeginNavigationWithRouteController:) - func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith routeController: RouteController) -> () + func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith routeController: RouteController) /** Called when navigation ends so that the containing app can update accordingly. - parameter carPlayManager: The shared CarPlay manager. */ - @objc func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) -> () + @objc func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) /** Called when the carplay manager will disable the idle timer. @@ -136,6 +135,7 @@ public protocol CarPlayManagerDelegate { */ @objc optional func carplayManagerShouldDisableIdleTimer(_ carPlayManager: CarPlayManager) -> Bool } + /** `CarPlayManager` is the main object responsible for orchestrating interactions with a Mapbox map on CarPlay. @@ -146,7 +146,6 @@ public protocol CarPlayManagerDelegate { @available(iOS 12.0, *) @objc(MBCarPlayManager) public class CarPlayManager: NSObject { - public fileprivate(set) var interfaceController: CPInterfaceController? public fileprivate(set) var carWindow: UIWindow? public fileprivate(set) var routeController: RouteController? @@ -177,7 +176,7 @@ public class CarPlayManager: NSObject { public static let CarPlayWaypointKey: String = "MBCarPlayWaypoint" public static func resetSharedInstance() { - shared = CarPlayManager() + self.shared = CarPlayManager() } /** @@ -220,12 +219,11 @@ public class CarPlayManager: NSObject { } // MARK: CPApplicationDelegate + @available(iOS 12.0, *) extension CarPlayManager: CPApplicationDelegate { - public func application(_ application: UIApplication, didConnectCarInterfaceController interfaceController: CPInterfaceController, to window: CPWindow) { - - isConnectedToCarPlay = true + self.isConnectedToCarPlay = true interfaceController.delegate = self self.interfaceController = interfaceController @@ -239,15 +237,15 @@ extension CarPlayManager: CPApplicationDelegate { window.rootViewController = viewController self.carWindow = window - let mapTemplate = self.mapTemplate(for: interfaceController, viewController: viewController) - mainMapTemplate = mapTemplate + let mapTemplate = mapTemplate(for: interfaceController, viewController: viewController) + self.mainMapTemplate = mapTemplate interfaceController.setRootTemplate(mapTemplate, animated: false) } public func application(_ application: UIApplication, didDisconnectCarInterfaceController interfaceController: CPInterfaceController, from window: CPWindow) { - isConnectedToCarPlay = false + self.isConnectedToCarPlay = false self.interfaceController = nil - carWindow?.isHidden = true + self.carWindow?.isHidden = true if let shouldDisableIdleTimer = delegate?.carplayManagerShouldDisableIdleTimer?(self) { UIApplication.shared.isIdleTimerDisabled = !shouldDisableIdleTimer @@ -257,7 +255,6 @@ extension CarPlayManager: CPApplicationDelegate { } func mapTemplate(for interfaceController: CPInterfaceController, viewController: UIViewController) -> CPMapTemplate { - let traitCollection = viewController.traitCollection let mapTemplate = CPMapTemplate() @@ -282,14 +279,14 @@ extension CarPlayManager: CPApplicationDelegate { if let mapButtons = delegate?.carPlayManager?(self, mapButtonsCompatibleWith: traitCollection, in: mapTemplate, for: .browsing) { mapTemplate.mapButtons = mapButtons } else if let vc = viewController as? CarPlayMapViewController { - mapTemplate.mapButtons = [vc.recenterButton, panMapButton(for: mapTemplate, traitCollection: traitCollection), vc.zoomInButton(), vc.zoomOutButton()] + mapTemplate.mapButtons = [vc.recenterButton, self.panMapButton(for: mapTemplate, traitCollection: traitCollection), vc.zoomInButton(), vc.zoomOutButton()] } return mapTemplate } func panMapButton(for mapTemplate: CPMapTemplate, traitCollection: UITraitCollection) -> CPMapButton { - let panButton = CPMapButton { [weak self] (button) in + let panButton = CPMapButton { [weak self] _ in guard let strongSelf = self else { return } @@ -309,7 +306,7 @@ extension CarPlayManager: CPApplicationDelegate { } func dismissPanButton(for mapTemplate: CPMapTemplate, traitCollection: UITraitCollection) -> CPMapButton { - let closeButton = CPMapButton { [weak self] button in + let closeButton = CPMapButton { [weak self] _ in guard let strongSelf = self, let mapButtons = strongSelf.defaultMapButtons else { return } @@ -333,63 +330,60 @@ extension CarPlayManager: CPApplicationDelegate { } // MARK: CPInterfaceControllerDelegate + @available(iOS 12.0, *) extension CarPlayManager: CPInterfaceControllerDelegate { public func templateWillAppear(_ template: CPTemplate, animated: Bool) { - if template == interfaceController?.rootTemplate, let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController { + if template == self.interfaceController?.rootTemplate, let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController { carPlayMapViewController.recenterButton.isHidden = true } } public func templateDidAppear(_ template: CPTemplate, animated: Bool) { - guard interfaceController?.topTemplate == mainMapTemplate else { return } - if template == interfaceController?.rootTemplate, let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController { - - + guard self.interfaceController?.topTemplate == self.mainMapTemplate else { return } + if template == self.interfaceController?.rootTemplate, let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController { let mapView = carPlayMapViewController.mapView mapView.removeRoutes() mapView.removeWaypoints() mapView.setUserTrackingMode(.followWithCourse, animated: true, completionHandler: nil) } } - public func templateWillDisappear(_ template: CPTemplate, animated: Bool) { + public func templateWillDisappear(_ template: CPTemplate, animated: Bool) { let isCorrectType = type(of: template) == CPSearchTemplate.self || type(of: template) == CPMapTemplate.self guard let interface = interfaceController, let top = interface.topTemplate, - type(of: top) == CPSearchTemplate.self || interface.templates.count == 1, - isCorrectType, - let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController else { return } - if type(of: template) == CPSearchTemplate.self { - carPlayMapViewController.isOverviewingRoutes = false - } - carPlayMapViewController.resetCamera(animated: false) - + type(of: top) == CPSearchTemplate.self || interface.templates.count == 1, + isCorrectType, + let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController else { return } + if type(of: template) == CPSearchTemplate.self { + carPlayMapViewController.isOverviewingRoutes = false + } + carPlayMapViewController.resetCamera(animated: false) } } // MARK: CPListTemplateDelegate + @available(iOS 12.0, *) extension CarPlayManager: CPListTemplateDelegate { - public func listTemplate(_ listTemplate: CPListTemplate, didSelect item: CPListItem, completionHandler: @escaping () -> Void) { - // Selected a search item from the extended list? #if canImport(CarPlay) && canImport(MapboxGeocoder) if let userInfo = item.userInfo as? [String: Any], - let placemark = userInfo[CarPlayManager.CarPlayGeocodedPlacemarkKey] as? GeocodedPlacemark, - let location = placemark.location { + let placemark = userInfo[CarPlayManager.CarPlayGeocodedPlacemarkKey] as? GeocodedPlacemark, + let location = placemark.location { let destinationWaypoint = Waypoint(location: location) - interfaceController?.popTemplate(animated: false) - calculateRouteAndStart(to: destinationWaypoint, completionHandler: completionHandler) + self.interfaceController?.popTemplate(animated: false) + self.calculateRouteAndStart(to: destinationWaypoint, completionHandler: completionHandler) return } #endif // Selected a favorite? or any item with a waypoint. if let userInfo = item.userInfo as? [String: Any], - let waypoint = userInfo[CarPlayManager.CarPlayWaypointKey] as? Waypoint { - calculateRouteAndStart(to: waypoint, completionHandler: completionHandler) + let waypoint = userInfo[CarPlayManager.CarPlayWaypointKey] as? Waypoint { + self.calculateRouteAndStart(to: waypoint, completionHandler: completionHandler) return } @@ -397,28 +391,28 @@ extension CarPlayManager: CPListTemplateDelegate { } public func calculateRouteAndStart(from fromWaypoint: Waypoint? = nil, to toWaypoint: Waypoint, completionHandler: @escaping () -> Void) { - guard let rootViewController = self.carWindow?.rootViewController as? CarPlayMapViewController, - let mapTemplate = self.interfaceController?.rootTemplate as? CPMapTemplate, - let userLocation = rootViewController.mapView.userLocation, - let location = userLocation.location, - let interfaceController = interfaceController else { - completionHandler() - return + guard let rootViewController = carWindow?.rootViewController as? CarPlayMapViewController, + let mapTemplate = interfaceController?.rootTemplate as? CPMapTemplate, + let userLocation = rootViewController.mapView.userLocation, + let location = userLocation.location, + let interfaceController else { + completionHandler() + return } let name = NSLocalizedString("CARPLAY_CURRENT_LOCATION", bundle: .mapboxNavigation, value: "Current Location", comment: "Name of the waypoint associated with the current location") let originWaypoint = fromWaypoint ?? Waypoint(location: location, heading: userLocation.heading, name: name) let routeOptions = NavigationRouteOptions(waypoints: [originWaypoint, toWaypoint]) - Directions.shared.calculate(routeOptions) { [weak self, weak mapTemplate] (waypoints, routes, error) in + Directions.shared.calculate(routeOptions) { [weak self, weak mapTemplate] waypoints, routes, error in defer { completionHandler() } - guard let `self` = self, let mapTemplate = mapTemplate else { + guard let self, let mapTemplate else { return } - if let error = error { + if let error { let okTitle = NSLocalizedString("CARPLAY_OK", bundle: .mapboxNavigation, value: "OK", comment: "CPNavigationAlert OK button title") let okAction = CPAlertAction(title: okTitle, style: .default) { _ in interfaceController.popToRootTemplate(animated: true) @@ -431,11 +425,11 @@ extension CarPlayManager: CPListTemplateDelegate { duration: 0) mapTemplate.present(navigationAlert: alert, animated: true) } - guard let waypoints = waypoints, let routes = routes else { + guard let waypoints, let routes else { return } - let routeChoices = routes.map { (route) -> CPRouteChoice in + let routeChoices = routes.map { route -> CPRouteChoice in let summaryVariants = [ self.fullDateComponentsFormatter.string(from: route.expectedTravelTime)!, self.shortDateComponentsFormatter.string(from: route.expectedTravelTime)!, @@ -478,23 +472,22 @@ extension CarPlayManager: CPListTemplateDelegate { } // MARK: CPMapTemplateDelegate + @available(iOS 12.0, *) extension CarPlayManager: CPMapTemplateDelegate { - public func mapTemplate(_ mapTemplate: CPMapTemplate, startedTrip trip: CPTrip, using routeChoice: CPRouteChoice) { - guard let interfaceController = interfaceController, - let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController else { + guard let interfaceController, + let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController else { return } mapTemplate.hideTripPreviews() let route = routeChoice.userInfo as! Route - let routeController: RouteController - if let routeControllerFromDelegate = delegate?.carPlayManager?(self, routeControllerAlong: route) { - routeController = routeControllerFromDelegate + let routeController: RouteController = if let routeControllerFromDelegate = delegate?.carPlayManager?(self, routeControllerAlong: route) { + routeControllerFromDelegate } else { - routeController = createRouteController(with: route) + self.createRouteController(with: route) } interfaceController.popToRootTemplate(animated: false) @@ -506,7 +499,7 @@ extension CarPlayManager: CPMapTemplateDelegate { interfaceController: interfaceController) navigationViewController.startNavigationSession(for: trip) navigationViewController.carPlayNavigationDelegate = self - currentNavigator = navigationViewController + self.currentNavigator = navigationViewController carPlayMapViewController.isOverviewingRoutes = false carPlayMapViewController.present(navigationViewController, animated: true, completion: nil) @@ -515,19 +508,19 @@ extension CarPlayManager: CPMapTemplateDelegate { mapView.removeRoutes() mapView.removeWaypoints() - delegate?.carPlayManager(self, didBeginNavigationWith: routeController) + self.delegate?.carPlayManager(self, didBeginNavigationWith: routeController) } func mapTemplate(forNavigating trip: CPTrip) -> CPMapTemplate { let mapTemplate = CPMapTemplate() mapTemplate.mapDelegate = self - let showFeedbackButton = CPMapButton { [weak self] (button) in + let showFeedbackButton = CPMapButton { [weak self] _ in self?.currentNavigator?.showFeedback() } showFeedbackButton.image = UIImage(named: "carplay_feedback", in: .mapboxNavigation, compatibleWith: nil) - let overviewButton = CPMapButton { [weak self] (button) in + let overviewButton = CPMapButton { [weak self] button in guard let navigationViewController = self?.currentNavigator else { return } @@ -540,8 +533,8 @@ extension CarPlayManager: CPMapTemplateDelegate { mapTemplate.mapButtons = [overviewButton, showFeedbackButton] - if let rootViewController = self.carWindow?.rootViewController as? CarPlayMapViewController, - let leadingButtons = delegate?.carPlayManager?(self, leadingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .navigating) { + if let rootViewController = carWindow?.rootViewController as? CarPlayMapViewController, + let leadingButtons = delegate?.carPlayManager?(self, leadingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .navigating) { mapTemplate.leadingNavigationBarButtons = leadingButtons } @@ -555,11 +548,11 @@ extension CarPlayManager: CPMapTemplateDelegate { muteButton.title = NavigationSettings.shared.voiceMuted ? unmuteTitle : muteTitle mapTemplate.leadingNavigationBarButtons.insert(muteButton, at: 0) - if let rootViewController = self.carWindow?.rootViewController as? CarPlayMapViewController, - let trailingButtons = delegate?.carPlayManager?(self, trailingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .navigating) { + if let rootViewController = carWindow?.rootViewController as? CarPlayMapViewController, + let trailingButtons = delegate?.carPlayManager?(self, trailingNavigationBarButtonsCompatibleWith: rootViewController.traitCollection, in: mapTemplate, for: .navigating) { mapTemplate.trailingNavigationBarButtons = trailingButtons } - let exitButton = CPBarButton(type: .text) { [weak self] (button: CPBarButton) in + let exitButton = CPBarButton(type: .text) { [weak self] (_: CPBarButton) in self?.currentNavigator?.exitNavigation(byCanceling: true) } exitButton.title = NSLocalizedString("CARPLAY_END", bundle: .mapboxNavigation, value: "End", comment: "Title for end navigation button") @@ -576,7 +569,7 @@ extension CarPlayManager: CPMapTemplateDelegate { let mapView = carPlayMapViewController.mapView let route = routeChoice.userInfo as! Route - //FIXME: Unable to tilt map during route selection -- https://github.com/mapbox/mapbox-gl-native/issues/2259 + // FIXME: Unable to tilt map during route selection -- https://github.com/mapbox/mapbox-gl-native/issues/2259 let topDownCamera = mapView.camera topDownCamera.pitch = 0 mapView.setCamera(topDownCamera, animated: false) @@ -592,7 +585,7 @@ extension CarPlayManager: CPMapTemplateDelegate { let mapView = carPlayMapViewController.mapView mapView.removeRoutes() mapView.removeWaypoints() - delegate?.carPlayManagerDidEndNavigation(self) + self.delegate?.carPlayManagerDidEndNavigation(self) } public func mapTemplateDidBeginPanGesture(_ mapTemplate: CPMapTemplate) { @@ -602,22 +595,22 @@ extension CarPlayManager: CPMapTemplateDelegate { } public func mapTemplate(_ mapTemplate: CPMapTemplate, didEndPanGestureWithVelocity velocity: CGPoint) { - if mapTemplate == interfaceController?.rootTemplate, let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController { + if mapTemplate == self.interfaceController?.rootTemplate, let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController { carPlayMapViewController.recenterButton.isHidden = carPlayMapViewController.mapView.userTrackingMode != .none } - //We want the panning surface to have "friction". If the user did not "flick" fast/hard enough, do not update the map with a final animation. + // We want the panning surface to have "friction". If the user did not "flick" fast/hard enough, do not update the map with a final animation. guard sqrtf(Float(velocity.x * velocity.x + velocity.y * velocity.y)) > 100 else { return } let decelerationRate: CGFloat = 0.9 let offset = CGPoint(x: velocity.x * decelerationRate / 4, y: velocity.y * decelerationRate / 4) - updatePan(by: offset, mapTemplate: mapTemplate, animated: true) + self.updatePan(by: offset, mapTemplate: mapTemplate, animated: true) } public func mapTemplateWillDismissPanningInterface(_ mapTemplate: CPMapTemplate) { - guard let carPlayMapViewController = self.carWindow?.rootViewController as? CarPlayMapViewController else { + guard let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController else { return } @@ -629,34 +622,31 @@ extension CarPlayManager: CPMapTemplateDelegate { let mapView: NavigationMapView if let navigationViewController = currentNavigator, mapTemplate == navigationViewController.mapTemplate { mapView = navigationViewController.mapView! - } else if let carPlayMapViewController = self.carWindow?.rootViewController as? CarPlayMapViewController { + } else if let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController { mapView = carPlayMapViewController.mapView } else { return } - mapView.setContentInset(mapView.safeArea, animated: false, completionHandler: nil) //make sure this is always up to date in-case safe area changes during gesture - updatePan(by: translation, mapTemplate: mapTemplate, animated: false) - - + mapView.setContentInset(mapView.safeArea, animated: false, completionHandler: nil) // make sure this is always up to date in-case safe area changes during gesture + self.updatePan(by: translation, mapTemplate: mapTemplate, animated: false) } private func updatePan(by offset: CGPoint, mapTemplate: CPMapTemplate, animated: Bool) { let mapView: NavigationMapView if let navigationViewController = currentNavigator, mapTemplate == navigationViewController.mapTemplate { mapView = navigationViewController.mapView! - } else if let carPlayMapViewController = self.carWindow?.rootViewController as? CarPlayMapViewController { + } else if let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController { mapView = carPlayMapViewController.mapView } else { return } - let coordinate = self.coordinate(of: offset, in: mapView) + let coordinate = coordinate(of: offset, in: mapView) mapView.setCenter(coordinate, animated: animated) } func coordinate(of offset: CGPoint, in mapView: NavigationMapView) -> CLLocationCoordinate2D { - let contentFrame = mapView.bounds.inset(by: mapView.safeArea) let centerPoint = CGPoint(x: contentFrame.midX, y: contentFrame.midY) let endCameraPoint = CGPoint(x: centerPoint.x - offset.x, y: centerPoint.y - offset.y) @@ -665,7 +655,7 @@ extension CarPlayManager: CPMapTemplateDelegate { } public func mapTemplate(_ mapTemplate: CPMapTemplate, panWith direction: CPMapTemplate.PanDirection) { - guard let carPlayMapViewController = self.carWindow?.rootViewController as? CarPlayMapViewController else { + guard let carPlayMapViewController = carWindow?.rootViewController as? CarPlayMapViewController else { return } @@ -700,18 +690,19 @@ extension CarPlayManager: CPMapTemplateDelegate { } // MARK: CarPlayNavigationDelegate + @available(iOS 12.0, *) extension CarPlayManager: CarPlayNavigationDelegate { public func carPlayNavigationViewControllerDidArrive(_: CarPlayNavigationViewController) { - delegate?.carPlayManagerDidEndNavigation(self) + self.delegate?.carPlayManagerDidEndNavigation(self) } public func carPlayNavigationViewControllerDidDismiss(_ carPlayNavigationViewController: CarPlayNavigationViewController, byCanceling canceled: Bool) { - if let mainMapTemplate = mainMapTemplate { - interfaceController?.setRootTemplate(mainMapTemplate, animated: true) + if let mainMapTemplate { + self.interfaceController?.setRootTemplate(mainMapTemplate, animated: true) } - interfaceController?.popToRootTemplate(animated: true) - delegate?.carPlayManagerDidEndNavigation(self) + self.interfaceController?.popToRootTemplate(animated: true) + self.delegate?.carPlayManagerDidEndNavigation(self) } } #else diff --git a/MapboxNavigation/CarPlayMapViewController.swift b/MapboxNavigation/CarPlayMapViewController.swift index f4ecb7e0b..79e4a6a78 100644 --- a/MapboxNavigation/CarPlayMapViewController.swift +++ b/MapboxNavigation/CarPlayMapViewController.swift @@ -5,7 +5,6 @@ import CarPlay @available(iOS 12.0, *) class CarPlayMapViewController: UIViewController, MLNMapViewDelegate { - static let defaultAltitude: CLLocationDistance = 16000 var styleManager: StyleManager! @@ -19,9 +18,7 @@ class CarPlayMapViewController: UIViewController, MLNMapViewDelegate { var isOverviewingRoutes: Bool = false var mapView: NavigationMapView { - get { - return self.view as! NavigationMapView - } + view as! NavigationMapView } lazy var recenterButton: CPMapButton = { @@ -46,21 +43,21 @@ class CarPlayMapViewController: UIViewController, MLNMapViewDelegate { mapView.logoView.isHidden = true mapView.attributionButton.isHidden = true - self.view = mapView + view = mapView } override func viewDidLoad() { super.viewDidLoad() - styleManager = StyleManager(self) - styleManager.styles = [DayStyle(), NightStyle()] + self.styleManager = StyleManager(self) + self.styleManager.styles = [DayStyle(), NightStyle()] - resetCamera(animated: false, altitude: CarPlayMapViewController.defaultAltitude) - mapView.setUserTrackingMode(.followWithCourse, animated: true, completionHandler: nil) + self.resetCamera(animated: false, altitude: CarPlayMapViewController.defaultAltitude) + self.mapView.setUserTrackingMode(.followWithCourse, animated: true, completionHandler: nil) } public func zoomInButton() -> CPMapButton { - let zoomInButton = CPMapButton { [weak self] (button) in + let zoomInButton = CPMapButton { [weak self] _ in guard let strongSelf = self else { return } @@ -72,7 +69,7 @@ class CarPlayMapViewController: UIViewController, MLNMapViewDelegate { } public func zoomOutButton() -> CPMapButton { - let zoomInOut = CPMapButton { [weak self] (button) in + let zoomInOut = CPMapButton { [weak self] _ in guard let strongSelf = self else { return } @@ -83,7 +80,6 @@ class CarPlayMapViewController: UIViewController, MLNMapViewDelegate { return zoomInOut } - // MARK: - MLNMapViewDelegate func mapView(_ mapView: MLNMapView, didFinishLoading style: MLNStyle) { @@ -93,51 +89,48 @@ class CarPlayMapViewController: UIViewController, MLNMapViewDelegate { } func resetCamera(animated: Bool = false, altitude: CLLocationDistance? = nil) { - let camera = mapView.camera - if let altitude = altitude { + let camera = self.mapView.camera + if let altitude { camera.altitude = altitude } camera.pitch = 60 - mapView.setCamera(camera, animated: animated) - + self.mapView.setCamera(camera, animated: animated) } override func viewSafeAreaInsetsDidChange() { - mapView.setContentInset(mapView.safeArea, animated: false, completionHandler: nil) + self.mapView.setContentInset(self.mapView.safeArea, animated: false, completionHandler: nil) - guard isOverviewingRoutes else { + guard self.isOverviewingRoutes else { super.viewSafeAreaInsetsDidChange() return } - guard let routes = mapView.routes, - let active = routes.first else { - super.viewSafeAreaInsetsDidChange() - return + let active = routes.first else { + super.viewSafeAreaInsetsDidChange() + return } - mapView.fit(to: active, animated: false) + self.mapView.fit(to: active, animated: false) } } @available(iOS 12.0, *) extension CarPlayMapViewController: StyleManagerDelegate { func locationFor(styleManager: StyleManager) -> CLLocation? { - return mapView.userLocationForCourseTracking ?? mapView.userLocation?.location ?? coarseLocationManager.location + self.mapView.userLocationForCourseTracking ?? self.mapView.userLocation?.location ?? self.coarseLocationManager.location } func styleManager(_ styleManager: StyleManager, didApply style: Style) { let styleURL = style.previewMapStyleURL - if mapView.styleURL != styleURL { - mapView.style?.transition = MLNTransition(duration: 0.5, delay: 0) - mapView.styleURL = styleURL + if self.mapView.styleURL != styleURL { + self.mapView.style?.transition = MLNTransition(duration: 0.5, delay: 0) + self.mapView.styleURL = styleURL } } func styleManagerDidRefreshAppearance(_ styleManager: StyleManager) { - mapView.reloadStyle(self) + self.mapView.reloadStyle(self) } } #endif - diff --git a/MapboxNavigation/CarPlayNavigationViewController.swift b/MapboxNavigation/CarPlayNavigationViewController.swift index 30b6b1ed4..7789e3809 100644 --- a/MapboxNavigation/CarPlayNavigationViewController.swift +++ b/MapboxNavigation/CarPlayNavigationViewController.swift @@ -1,7 +1,7 @@ import Foundation -import MapLibre -import MapboxDirections import MapboxCoreNavigation +import MapboxDirections +import MapLibre #if canImport(CarPlay) import CarPlay @@ -34,7 +34,7 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega let distanceFormatter = DistanceFormatter(approximate: true) var edgePadding: UIEdgeInsets { - let padding:CGFloat = 15 + let padding: CGFloat = 15 return UIEdgeInsets(top: view.safeAreaInsets.top + padding, left: view.safeAreaInsets.left + padding, bottom: view.safeAreaInsets.bottom + padding, @@ -62,6 +62,7 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega routeController.delegate = self } + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -83,23 +84,23 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega self.mapView = mapView view.addSubview(mapView) - styleManager = StyleManager(self) - styleManager.styles = [DayStyle(), NightStyle()] + self.styleManager = StyleManager(self) + self.styleManager.styles = [DayStyle(), NightStyle()] - resumeNotifications() - routeController.resume() + self.resumeNotifications() + self.routeController.resume() mapView.recenterMap() } override public func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - suspendNotifications() + self.suspendNotifications() } func resumeNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .routeControllerProgressDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(rerouted(_:)), name: .routeControllerDidReroute, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(visualInstructionDidChange(_:)), name: .routeControllerDidPassVisualInstructionPoint, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.progressDidChange(_:)), name: .routeControllerProgressDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.rerouted(_:)), name: .routeControllerDidReroute, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.visualInstructionDidChange(_:)), name: .routeControllerDidPassVisualInstructionPoint, object: nil) } func suspendNotifications() { @@ -108,12 +109,12 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega NotificationCenter.default.removeObserver(self, name: .routeControllerDidPassVisualInstructionPoint, object: nil) } - public override func viewSafeAreaInsetsDidChange() { + override public func viewSafeAreaInsetsDidChange() { super.viewSafeAreaInsetsDidChange() - if let previousSafeAreaInsets = previousSafeAreaInsets { + if let previousSafeAreaInsets { let navigationBarIsOpen = view.safeAreaInsets > previousSafeAreaInsets - mapView?.compassView.isHidden = navigationBarIsOpen + self.mapView?.compassView.isHidden = navigationBarIsOpen } previousSafeAreaInsets = view.safeAreaInsets @@ -126,7 +127,7 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega */ @objc(startNavigationSessionForTrip:) public func startNavigationSession(for trip: CPTrip) { - carSession = mapTemplate.startNavigationSession(for: trip) + self.carSession = self.mapTemplate.startNavigationSession(for: trip) } /** @@ -136,16 +137,16 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega */ @objc(exitNavigationByCanceling:) public func exitNavigation(byCanceling canceled: Bool = false) { - carSession.finishTrip() + self.carSession.finishTrip() dismiss(animated: true, completion: nil) - carPlayNavigationDelegate?.carPlayNavigationViewControllerDidDismiss(self, byCanceling: canceled) + self.carPlayNavigationDelegate?.carPlayNavigationViewControllerDidDismiss(self, byCanceling: canceled) } /** Shows the interface for providing feedback about the route. */ @objc public func showFeedback() { - carInterfaceController.pushTemplate(self.carFeedbackTemplate, animated: true) + self.carInterfaceController.pushTemplate(self.carFeedbackTemplate, animated: true) } /** @@ -155,41 +156,41 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega */ @objc public var tracksUserCourse: Bool { get { - return mapView?.tracksUserCourse ?? false + self.mapView?.tracksUserCourse ?? false } set { - if !tracksUserCourse && newValue { - mapView?.recenterMap() - mapView?.addArrow(route: routeController.routeProgress.route, - legIndex: routeController.routeProgress.legIndex, - stepIndex: routeController.routeProgress.currentLegProgress.stepIndex + 1) - } else if tracksUserCourse && !newValue { - guard let userLocation = self.routeController.locationManager.location?.coordinate else { + if !tracksUserCourse, newValue { + self.mapView?.recenterMap() + self.mapView?.addArrow(route: self.routeController.routeProgress.route, + legIndex: self.routeController.routeProgress.legIndex, + stepIndex: self.routeController.routeProgress.currentLegProgress.stepIndex + 1) + } else if tracksUserCourse, !newValue { + guard let userLocation = routeController.locationManager.location?.coordinate else { return } - mapView?.enableFrameByFrameCourseViewTracking(for: 3) - mapView?.setOverheadCameraView(from: userLocation, along: routeController.routeProgress.route.coordinates!, for: self.edgePadding) + self.mapView?.enableFrameByFrameCourseViewTracking(for: 3) + self.mapView?.setOverheadCameraView(from: userLocation, along: self.routeController.routeProgress.route.coordinates!, for: self.edgePadding) } } } public func beginPanGesture() { - mapView?.tracksUserCourse = false - mapView?.enableFrameByFrameCourseViewTracking(for: 1) + self.mapView?.tracksUserCourse = false + self.mapView?.enableFrameByFrameCourseViewTracking(for: 1) } public func mapView(_ mapView: MLNMapView, didFinishLoading style: MLNStyle) { - self.mapView?.addArrow(route: routeController.routeProgress.route, legIndex: routeController.routeProgress.legIndex, stepIndex: routeController.routeProgress.currentLegProgress.stepIndex + 1) - self.mapView?.showRoutes([routeController.routeProgress.route]) - self.mapView?.showWaypoints(routeController.routeProgress.route) + self.mapView?.addArrow(route: self.routeController.routeProgress.route, legIndex: self.routeController.routeProgress.legIndex, stepIndex: self.routeController.routeProgress.currentLegProgress.stepIndex + 1) + self.mapView?.showRoutes([self.routeController.routeProgress.route]) + self.mapView?.showWaypoints(self.routeController.routeProgress.route) self.mapView?.recenterMap() } @objc func visualInstructionDidChange(_ notification: NSNotification) { let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as! RouteProgress - updateManeuvers(for: routeProgress) - mapView?.showWaypoints(routeProgress.route) - mapView?.addArrow(route: routeProgress.route, legIndex: routeProgress.legIndex, stepIndex: routeProgress.currentLegProgress.stepIndex + 1) + self.updateManeuvers(for: routeProgress) + self.mapView?.showWaypoints(routeProgress.route) + self.mapView?.addArrow(route: routeProgress.route, legIndex: routeProgress.legIndex, stepIndex: routeProgress.currentLegProgress.stepIndex + 1) } @objc func progressDidChange(_ notification: NSNotification) { @@ -198,39 +199,39 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega // Update the user puck let camera = MLNMapCamera(lookingAtCenter: location.coordinate, acrossDistance: 120, pitch: 60, heading: location.course) - mapView?.updateCourseTracking(location: location, camera: camera, animated: true) + self.mapView?.updateCourseTracking(location: location, camera: camera, animated: true) let congestionLevel = routeProgress.averageCongestionLevelRemainingOnLeg ?? .unknown guard let maneuver = carSession.upcomingManeuvers.first else { return } let legProgress = routeProgress.currentLegProgress - let legDistance = distanceFormatter.measurement(of: legProgress.distanceRemaining) + let legDistance = self.distanceFormatter.measurement(of: legProgress.distanceRemaining) let legEstimates = CPTravelEstimates(distanceRemaining: legDistance, timeRemaining: legProgress.durationRemaining) - mapTemplate.update(legEstimates, for: carSession.trip, with: congestionLevel.asCPTimeRemainingColor) + self.mapTemplate.update(legEstimates, for: self.carSession.trip, with: congestionLevel.asCPTimeRemainingColor) let stepProgress = legProgress.currentStepProgress - let stepDistance = distanceFormatter.measurement(of: stepProgress.distanceRemaining) + let stepDistance = self.distanceFormatter.measurement(of: stepProgress.distanceRemaining) let stepEstimates = CPTravelEstimates(distanceRemaining: stepDistance, timeRemaining: stepProgress.durationRemaining) - carSession.updateEstimates(stepEstimates, for: maneuver) + self.carSession.updateEstimates(stepEstimates, for: maneuver) } @objc func rerouted(_ notification: NSNotification) { - updateRouteOnMap() + self.updateRouteOnMap() self.mapView?.recenterMap() } func updateRouteOnMap() { - mapView?.addArrow(route: routeController.routeProgress.route, legIndex: routeController.routeProgress.legIndex, stepIndex: routeController.routeProgress.currentLegProgress.stepIndex + 1) - mapView?.showRoutes([routeController.routeProgress.route], legIndex: routeController.routeProgress.legIndex) - mapView?.showWaypoints(routeController.routeProgress.route, legIndex: routeController.routeProgress.legIndex) + self.mapView?.addArrow(route: self.routeController.routeProgress.route, legIndex: self.routeController.routeProgress.legIndex, stepIndex: self.routeController.routeProgress.currentLegProgress.stepIndex + 1) + self.mapView?.showRoutes([self.routeController.routeProgress.route], legIndex: self.routeController.routeProgress.legIndex) + self.mapView?.showWaypoints(self.routeController.routeProgress.route, legIndex: self.routeController.routeProgress.legIndex) } func updateManeuvers(for routeProgress: RouteProgress) { guard let visualInstruction = routeProgress.currentLegProgress.currentStepProgress.currentVisualInstruction else { return } - let step = routeController.routeProgress.currentLegProgress.currentStep + let step = self.routeController.routeProgress.currentLegProgress.currentStep let primaryManeuver = CPManeuver() - let distance = distanceFormatter.measurement(of: step.distance) + let distance = self.distanceFormatter.measurement(of: step.distance) primaryManeuver.initialTravelEstimates = CPTravelEstimates(distanceRemaining: distance, timeRemaining: step.expectedTravelTime) // Just incase, set some default text @@ -278,19 +279,19 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega } if let upcomingStep = routeController.routeProgress.currentLegProgress.upComingStep { - let distance = distanceFormatter.measurement(of: upcomingStep.distance) + let distance = self.distanceFormatter.measurement(of: upcomingStep.distance) tertiaryManeuver.initialTravelEstimates = CPTravelEstimates(distanceRemaining: distance, timeRemaining: upcomingStep.expectedTravelTime) } maneuvers.append(tertiaryManeuver) } - carSession.upcomingManeuvers = maneuvers + self.carSession.upcomingManeuvers = maneuvers } func endOfRouteFeedbackTemplate() -> CPGridTemplate { - let buttonHandler: (_: CPGridButton) -> Void = { [weak self] (button) in - //TODO: no such method exists, and the replacement candidate ignores the feedback sent, so ... ? + let buttonHandler: (_: CPGridButton) -> Void = { [weak self] _ in + // TODO: no such method exists, and the replacement candidate ignores the feedback sent, so ... ? // self?.routeController.setEndOfRoute(rating: Int(button.titleVariants.first!.components(separatedBy: CharacterSet.decimalDigits.inverted).joined())!, comment: nil) self?.carInterfaceController.popTemplate(animated: true) self?.exitNavigation() @@ -298,7 +299,7 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega var buttons: [CPGridButton] = [] let starImage = UIImage(named: "star", in: .mapboxNavigation, compatibleWith: nil)! - for i in 1...5 { + for i in 1 ... 5 { let button = CPGridButton(titleVariants: ["\(i) star\(i == 1 ? "" : "s")"], image: starImage, handler: buttonHandler) buttons.append(button) } @@ -309,18 +310,18 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega func presentArrivalUI() { let exitTitle = NSLocalizedString("CARPLAY_EXIT_NAVIGATION", bundle: .mapboxNavigation, value: "Exit navigation", comment: "Title on the exit button in the arrival form") - let exitAction = CPAlertAction(title: exitTitle, style: .cancel) { (action) in + let exitAction = CPAlertAction(title: exitTitle, style: .cancel) { _ in self.exitNavigation() self.dismiss(animated: true, completion: nil) } let rateTitle = NSLocalizedString("CARPLAY_RATE_TRIP", bundle: .mapboxNavigation, value: "Rate your trip", comment: "Title on rate button in CarPlay") - let rateAction = CPAlertAction(title: rateTitle, style: .default) { (action) in + let rateAction = CPAlertAction(title: rateTitle, style: .default) { _ in self.carInterfaceController.pushTemplate(self.endOfRouteFeedbackTemplate(), animated: true) } let arrivalTitle = NSLocalizedString("CARPLAY_ARRIVED", bundle: .mapboxNavigation, value: "You have arrived", comment: "Title on arrival action sheet") let arrivalMessage = NSLocalizedString("CARPLAY_ARRIVED_MESSAGE", bundle: .mapboxNavigation, value: "What would you like to do?", comment: "Message on arrival action sheet") let alert = CPActionSheetTemplate(title: arrivalTitle, message: arrivalMessage, actions: [rateAction, exitAction]) - carInterfaceController.presentTemplate(alert, animated: true) + self.carInterfaceController.presentTemplate(alert, animated: true) } func presentWayointArrivalUI(for waypoint: Waypoint) { @@ -330,32 +331,32 @@ public class CarPlayNavigationViewController: UIViewController, MLNMapViewDelega } let continueTitle = NSLocalizedString("CARPLAY_CONTINUE", bundle: .mapboxNavigation, value: "Continue", comment: "Title on continue button in CarPlay") - let continueAlert = CPAlertAction(title: continueTitle, style: .default) { (action) in + let continueAlert = CPAlertAction(title: continueTitle, style: .default) { _ in self.routeController.routeProgress.legIndex += 1 self.carInterfaceController.dismissTemplate(animated: true) self.updateRouteOnMap() } let waypointArrival = CPAlertTemplate(titleVariants: [title], actions: [continueAlert]) - carInterfaceController.presentTemplate(waypointArrival, animated: true) + self.carInterfaceController.presentTemplate(waypointArrival, animated: true) } } @available(iOS 12.0, *) extension CarPlayNavigationViewController: StyleManagerDelegate { public func locationFor(styleManager: StyleManager) -> CLLocation? { - return routeController.locationManager.location + self.routeController.locationManager.location } public func styleManager(_ styleManager: StyleManager, didApply style: Style) { - if mapView?.styleURL != style.mapStyleURL { - mapView?.style?.transition = MLNTransition(duration: 0.5, delay: 0) - mapView?.styleURL = style.mapStyleURL + if self.mapView?.styleURL != style.mapStyleURL { + self.mapView?.style?.transition = MLNTransition(duration: 0.5, delay: 0) + self.mapView?.styleURL = style.mapStyleURL } } public func styleManagerDidRefreshAppearance(_ styleManager: StyleManager) { - mapView?.reloadStyle(self) + self.mapView?.reloadStyle(self) } } @@ -363,10 +364,10 @@ extension CarPlayNavigationViewController: StyleManagerDelegate { extension CarPlayNavigationViewController: RouteControllerDelegate { public func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool { if routeController.routeProgress.isFinalLeg { - presentArrivalUI() - carPlayNavigationDelegate?.carPlayNavigationViewControllerDidArrive(self) + self.presentArrivalUI() + self.carPlayNavigationDelegate?.carPlayNavigationViewControllerDidArrive(self) } else { - presentWayointArrivalUI(for: waypoint) + self.presentWayointArrivalUI(for: waypoint) } return false } diff --git a/MapboxNavigation/ConfigManager.swift b/MapboxNavigation/ConfigManager.swift index 89b8b881a..65c376cdb 100644 --- a/MapboxNavigation/ConfigManager.swift +++ b/MapboxNavigation/ConfigManager.swift @@ -31,6 +31,5 @@ public struct MNConfig { public var routeArrowColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) public var routeArrowCasingColor = #colorLiteral(red: 0, green: 0.4980392157, blue: 0.9098039216, alpha: 1) - public init() { - } + public init() {} } diff --git a/MapboxNavigation/CongestionLevel.swift b/MapboxNavigation/CongestionLevel.swift index e9fa7c031..364d0e6b8 100644 --- a/MapboxNavigation/CongestionLevel.swift +++ b/MapboxNavigation/CongestionLevel.swift @@ -3,23 +3,23 @@ import MapboxDirections #if canImport(CarPlay) import CarPlay -extension CongestionLevel { +public extension CongestionLevel { /** Converts a CongestionLevel to a CPTimeRemainingColor. */ @available(iOS 12.0, *) - public var asCPTimeRemainingColor: CPTimeRemainingColor { + var asCPTimeRemainingColor: CPTimeRemainingColor { switch self { case .unknown: - return .default + .default case .low: - return .green + .green case .moderate: - return .orange + .orange case .heavy: - return .red + .red case .severe: - return .red + .red } } } diff --git a/MapboxNavigation/DashedLineView.swift b/MapboxNavigation/DashedLineView.swift index 0525b2dba..7821a69b6 100644 --- a/MapboxNavigation/DashedLineView.swift +++ b/MapboxNavigation/DashedLineView.swift @@ -4,29 +4,28 @@ import UIKit @IBDesignable @objc(MBDashedLineView) public class DashedLineView: LineView { - - @IBInspectable public var dashedLength: CGFloat = 4 { didSet { updateProperties() } } - @IBInspectable public var dashedGap: CGFloat = 4 { didSet { updateProperties() } } + @IBInspectable public var dashedLength: CGFloat = 4 { didSet { self.updateProperties() } } + @IBInspectable public var dashedGap: CGFloat = 4 { didSet { self.updateProperties() } } let dashedLineLayer = CAShapeLayer() override public func layoutSubviews() { - if dashedLineLayer.superlayer == nil { - layer.addSublayer(dashedLineLayer) + if self.dashedLineLayer.superlayer == nil { + layer.addSublayer(self.dashedLineLayer) } - updateProperties() + self.updateProperties() } func updateProperties() { let path = UIBezierPath() - path.move(to: CGPoint(x: 0, y: bounds.height/2)) - path.addLine(to: CGPoint(x: bounds.width, y: bounds.height/2)) - dashedLineLayer.path = path.cgPath + path.move(to: CGPoint(x: 0, y: bounds.height / 2)) + path.addLine(to: CGPoint(x: bounds.width, y: bounds.height / 2)) + self.dashedLineLayer.path = path.cgPath - dashedLineLayer.frame = bounds - dashedLineLayer.fillColor = UIColor.clear.cgColor - dashedLineLayer.strokeColor = lineColor.cgColor - dashedLineLayer.lineWidth = bounds.height - dashedLineLayer.lineDashPattern = [dashedLength as NSNumber, dashedGap as NSNumber] + self.dashedLineLayer.frame = bounds + self.dashedLineLayer.fillColor = UIColor.clear.cgColor + self.dashedLineLayer.strokeColor = lineColor.cgColor + self.dashedLineLayer.lineWidth = bounds.height + self.dashedLineLayer.lineDashPattern = [self.dashedLength as NSNumber, self.dashedGap as NSNumber] } } diff --git a/MapboxNavigation/DataCache.swift b/MapboxNavigation/DataCache.swift index efa9843b6..d8608f5ad 100644 --- a/MapboxNavigation/DataCache.swift +++ b/MapboxNavigation/DataCache.swift @@ -5,32 +5,35 @@ public class DataCache: NSObject, BimodalDataCache { let memoryCache: NSCache let fileCache = FileCache() - public override init() { - memoryCache = NSCache() - memoryCache.name = "In-Memory Data Cache" + override public init() { + self.memoryCache = NSCache() + self.memoryCache.name = "In-Memory Data Cache" super.init() NotificationCenter.default.addObserver(self, selector: #selector(DataCache.clearMemory), name: UIApplication.didReceiveMemoryWarningNotification, object: nil) } - // MARK: Data cache /* - Stores data in the cache for the given key. If `toDisk` is set to `true`, the completion handler is called following writing the data to disk, otherwise it is called immediately upon storing the data in the memory cache. - */ + Stores data in the cache for the given key. If `toDisk` is set to `true`, the completion handler is called following writing the data to disk, otherwise it is called immediately upon storing the data in the memory cache. + */ public func store(_ data: Data, forKey key: String, toDisk: Bool, completion: CompletionHandler?) { - storeDataInMemoryCache(data, forKey: key) + self.storeDataInMemoryCache(data, forKey: key) - toDisk == true ? fileCache.store(data, forKey: key, completion: completion) : completion?() + if toDisk { + self.fileCache.store(data, forKey: key, completion: completion) + } else { + completion?() + } } /* Returns data from the cache for the given key, if any. The memory cache is consulted first, followed by the disk cache. If data is found on disk which isn't in memory, it is added to the memory cache. */ public func data(forKey key: String?) -> Data? { - guard let key = key else { + guard let key else { return nil } @@ -39,7 +42,7 @@ public class DataCache: NSObject, BimodalDataCache { } if let data = fileCache.dataFromFileCache(forKey: key) { - storeDataInMemoryCache(data, forKey: key) + self.storeDataInMemoryCache(data, forKey: key) return data } @@ -50,18 +53,18 @@ public class DataCache: NSObject, BimodalDataCache { Clears out the memory cache. */ public func clearMemory() { - memoryCache.removeAllObjects() + self.memoryCache.removeAllObjects() } /* Clears the disk cache and calls the completion handler when finished. */ public func clearDisk(completion: CompletionHandler?) { - fileCache.clearDisk(completion: completion) + self.fileCache.clearDisk(completion: completion) } private func storeDataInMemoryCache(_ data: Data, forKey key: String) { - memoryCache.setObject(data as NSData, forKey: key as NSString) + self.memoryCache.setObject(data as NSData, forKey: key as NSString) } private func dataFromMemoryCache(forKey key: String) -> Data? { @@ -70,5 +73,4 @@ public class DataCache: NSObject, BimodalDataCache { } return nil } - } diff --git a/MapboxNavigation/DayStyle.swift b/MapboxNavigation/DayStyle.swift index 09abef5d9..20c6acbfe 100644 --- a/MapboxNavigation/DayStyle.swift +++ b/MapboxNavigation/DayStyle.swift @@ -1,7 +1,6 @@ import Foundation import MapLibre - extension UIColor { class var defaultRouteLine: UIColor { ConfigManager.shared.config.routeLineColor } class var defaultRouteLineAlternative: UIColor { ConfigManager.shared.config.routeLineAlternativeColor } @@ -9,36 +8,36 @@ extension UIColor { class var defaultRouteLineCasing: UIColor { ConfigManager.shared.config.routeLineCasingColor } class var defaultRouteLineCasingAlternative: UIColor { ConfigManager.shared.config.routeLineCasingAlternativeColor } - class var defaultRouteLayer: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1) } } + class var defaultRouteLayer: UIColor { #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1) } class var defaultManeuverArrowStroke: UIColor { ConfigManager.shared.config.routeArrowCasingColor } class var defaultManeuverArrow: UIColor { ConfigManager.shared.config.routeArrowColor } - class var defaultTurnArrowPrimary: UIColor { get { return #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) } } - class var defaultTurnArrowSecondary: UIColor { get { return #colorLiteral(red: 0.6196078431, green: 0.6196078431, blue: 0.6196078431, alpha: 1) } } + class var defaultTurnArrowPrimary: UIColor { #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) } + class var defaultTurnArrowSecondary: UIColor { #colorLiteral(red: 0.6196078431, green: 0.6196078431, blue: 0.6196078431, alpha: 1) } - class var defaultLaneArrowPrimary: UIColor { get { return #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) } } - class var defaultLaneArrowSecondary: UIColor { get { return #colorLiteral(red: 0.6196078431, green: 0.6196078431, blue: 0.6196078431, alpha: 1) } } + class var defaultLaneArrowPrimary: UIColor { #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) } + class var defaultLaneArrowSecondary: UIColor { #colorLiteral(red: 0.6196078431, green: 0.6196078431, blue: 0.6196078431, alpha: 1) } - class var trafficUnknown: UIColor { get { return defaultRouteLayer } } - class var trafficLow: UIColor { get { return defaultRouteLayer } } - class var trafficModerate: UIColor { get { return #colorLiteral(red: 0.9529411765, green: 0.6509803922, blue: 0.3098039216, alpha: 1) } } - class var trafficHeavy: UIColor { get { return #colorLiteral(red: 0.9137254902, green: 0.2, blue: 0.2509803922, alpha: 1) } } - class var trafficSevere: UIColor { get { return #colorLiteral(red: 0.5411764706, green: 0.05882352941, blue: 0.2196078431, alpha: 1) } } - class var trafficAlternateLow: UIColor { get { return #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1) } } + class var trafficUnknown: UIColor { defaultRouteLayer } + class var trafficLow: UIColor { defaultRouteLayer } + class var trafficModerate: UIColor { #colorLiteral(red: 0.9529411765, green: 0.6509803922, blue: 0.3098039216, alpha: 1) } + class var trafficHeavy: UIColor { #colorLiteral(red: 0.9137254902, green: 0.2, blue: 0.2509803922, alpha: 1) } + class var trafficSevere: UIColor { #colorLiteral(red: 0.5411764706, green: 0.05882352941, blue: 0.2196078431, alpha: 1) } + class var trafficAlternateLow: UIColor { #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1) } } -extension UIColor { +private extension UIColor { // General styling - fileprivate class var defaultTint: UIColor { get { return #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1) } } - fileprivate class var defaultTintStroke: UIColor { get { return #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1) } } - fileprivate class var defaultPrimaryText: UIColor { get { return #colorLiteral(red: 45.0/255.0, green: 45.0/255.0, blue: 45.0/255.0, alpha: 1) } } - fileprivate class var defaultSecondaryText: UIColor { get { return #colorLiteral(red: 0.4509803922, green: 0.4509803922, blue: 0.4509803922, alpha: 1) } } + class var defaultTint: UIColor { #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1) } + class var defaultTintStroke: UIColor { #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1) } + class var defaultPrimaryText: UIColor { #colorLiteral(red: 45.0 / 255.0, green: 45.0 / 255.0, blue: 45.0 / 255.0, alpha: 1) } + class var defaultSecondaryText: UIColor { #colorLiteral(red: 0.4509803922, green: 0.4509803922, blue: 0.4509803922, alpha: 1) } } -extension UIFont { +private extension UIFont { // General styling - fileprivate class var defaultPrimaryText: UIFont { get { return UIFont.systemFont(ofSize: 26) } } - fileprivate class var defaultSecondaryText: UIFont { get { return UIFont.systemFont(ofSize: 16) } } + class var defaultPrimaryText: UIFont { UIFont.systemFont(ofSize: 26) } + class var defaultSecondaryText: UIFont { UIFont.systemFont(ofSize: 16) } } /** @@ -46,7 +45,6 @@ extension UIFont { */ @objc(MBDayStyle) open class DayStyle: Style { - public required init() { super.init() mapStyleURL = MLNStyle.navigationGuidanceDayStyleURL @@ -54,7 +52,7 @@ open class DayStyle: Style { statusBarStyle = .default } - open override func apply() { + override open func apply() { super.apply() // General styling @@ -113,17 +111,17 @@ open class DayStyle: Style { ManeuverView.appearance(whenContainedInInstancesOf: [NextBannerView.self]).secondaryColor = .defaultTurnArrowSecondary ManeuverView.appearance(whenContainedInInstancesOf: [StepInstructionsView.self]).primaryColor = .defaultTurnArrowPrimary ManeuverView.appearance(whenContainedInInstancesOf: [StepInstructionsView.self]).secondaryColor = .defaultTurnArrowSecondary - NavigationMapView.appearance().maneuverArrowColor = .defaultManeuverArrow - NavigationMapView.appearance().maneuverArrowStrokeColor = .defaultManeuverArrowStroke - NavigationMapView.appearance().routeLineColor = .defaultRouteLine - NavigationMapView.appearance().routeLineAlternativeColor = .defaultRouteLineAlternative - NavigationMapView.appearance().routeLineCasingColor = .defaultRouteLineCasing - NavigationMapView.appearance().routeLineCasingAlternativeColor = .defaultRouteLineCasingAlternative - NavigationMapView.appearance().trafficHeavyColor = .trafficHeavy - NavigationMapView.appearance().trafficLowColor = .trafficLow - NavigationMapView.appearance().trafficModerateColor = .trafficModerate - NavigationMapView.appearance().trafficSevereColor = .trafficSevere - NavigationMapView.appearance().trafficUnknownColor = .trafficUnknown + NavigationMapView.appearance().maneuverArrowColor = .defaultManeuverArrow + NavigationMapView.appearance().maneuverArrowStrokeColor = .defaultManeuverArrowStroke + NavigationMapView.appearance().routeLineColor = .defaultRouteLine + NavigationMapView.appearance().routeLineAlternativeColor = .defaultRouteLineAlternative + NavigationMapView.appearance().routeLineCasingColor = .defaultRouteLineCasing + NavigationMapView.appearance().routeLineCasingAlternativeColor = .defaultRouteLineCasingAlternative + NavigationMapView.appearance().trafficHeavyColor = .trafficHeavy + NavigationMapView.appearance().trafficLowColor = .trafficLow + NavigationMapView.appearance().trafficModerateColor = .trafficModerate + NavigationMapView.appearance().trafficSevereColor = .trafficSevere + NavigationMapView.appearance().trafficUnknownColor = .trafficUnknown NavigationView.appearance().backgroundColor = #colorLiteral(red: 0.764706, green: 0.752941, blue: 0.733333, alpha: 1) NextBannerView.appearance().backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1) NextInstructionLabel.appearance().font = UIFont.systemFont(ofSize: 20, weight: .medium).adjustedFont @@ -143,20 +141,20 @@ open class DayStyle: Style { SecondaryLabel.appearance(whenContainedInInstancesOf: [InstructionsBannerView.self]).normalTextColor = #colorLiteral(red: 0.2156862745, green: 0.2156862745, blue: 0.2156862745, alpha: 1) SecondaryLabel.appearance(whenContainedInInstancesOf: [StepInstructionsView.self]).normalTextColor = #colorLiteral(red: 0.2156862745, green: 0.2156862745, blue: 0.2156862745, alpha: 1) SeparatorView.appearance().backgroundColor = #colorLiteral(red: 0.737254902, green: 0.7960784314, blue: 0.8705882353, alpha: 1) - StatusView.appearance().backgroundColor = UIColor.black.withAlphaComponent(2.0/3.0) + StatusView.appearance().backgroundColor = UIColor.black.withAlphaComponent(2.0 / 3.0) StepInstructionsView.appearance().backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1) StepListIndicatorView.appearance().gradientColors = [#colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.431372549, alpha: 1), #colorLiteral(red: 0.6274509804, green: 0.6274509804, blue: 0.6274509804, alpha: 1), #colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.431372549, alpha: 1)] StepTableViewCell.appearance().backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1) StepsBackgroundView.appearance().backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1) TimeRemainingLabel.appearance().font = UIFont.systemFont(ofSize: 28, weight: .medium).adjustedFont TimeRemainingLabel.appearance().normalTextColor = .defaultPrimaryText - TimeRemainingLabel.appearance().trafficHeavyColor = #colorLiteral(red:0.91, green:0.20, blue:0.25, alpha:1.0) + TimeRemainingLabel.appearance().trafficHeavyColor = #colorLiteral(red: 0.91, green: 0.20, blue: 0.25, alpha: 1.0) TimeRemainingLabel.appearance().trafficLowColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1) - TimeRemainingLabel.appearance().trafficModerateColor = #colorLiteral(red:0.95, green:0.65, blue:0.31, alpha:1.0) + TimeRemainingLabel.appearance().trafficModerateColor = #colorLiteral(red: 0.95, green: 0.65, blue: 0.31, alpha: 1.0) TimeRemainingLabel.appearance().trafficSevereColor = #colorLiteral(red: 0.7705719471, green: 0.1753477752, blue: 0.1177056804, alpha: 1) TimeRemainingLabel.appearance().trafficUnknownColor = .defaultPrimaryText UserPuckCourseView.appearance().puckColor = #colorLiteral(red: 0.149, green: 0.239, blue: 0.341, alpha: 1) - WayNameLabel.appearance().normalFont = UIFont.systemFont(ofSize:20, weight: .medium).adjustedFont + WayNameLabel.appearance().normalFont = UIFont.systemFont(ofSize: 20, weight: .medium).adjustedFont WayNameLabel.appearance().normalTextColor = #colorLiteral(red: 0.968627451, green: 0.968627451, blue: 0.968627451, alpha: 1) WayNameView.appearance().backgroundColor = UIColor.defaultRouteLayer.withAlphaComponent(0.85) WayNameView.appearance().borderColor = UIColor.defaultRouteLineCasing.withAlphaComponent(0.8) @@ -168,7 +166,6 @@ open class DayStyle: Style { */ @objc(MBNightStyle) open class NightStyle: DayStyle { - public required init() { super.init() mapStyleURL = MLNStyle.navigationGuidanceNightStyleURL @@ -177,7 +174,7 @@ open class NightStyle: DayStyle { statusBarStyle = .lightContent } - open override func apply() { + override open func apply() { super.apply() let backgroundColor = #colorLiteral(red: 0.1493228376, green: 0.2374534607, blue: 0.333029449, alpha: 1) diff --git a/MapboxNavigation/DialogViewController.swift b/MapboxNavigation/DialogViewController.swift index 33a4e8f3a..32a5600ba 100644 --- a/MapboxNavigation/DialogViewController.swift +++ b/MapboxNavigation/DialogViewController.swift @@ -43,24 +43,24 @@ class DialogViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - setupViews() - setupConstraints() + self.setupViews() + self.setupConstraints() } func setupViews() { - view.addGestureRecognizer(tapRecognizer) - view.addSubview(dialogView) - dialogView.addSubview(stackView) - stackView.addArrangedSubviews([imageView, label]) + view.addGestureRecognizer(self.tapRecognizer) + view.addSubview(self.dialogView) + self.dialogView.addSubview(self.stackView) + self.stackView.addArrangedSubviews([self.imageView, self.label]) } func setupConstraints() { - let dialogWidth = dialogView.widthAnchor.constraint(equalToConstant: Constants.dialogSize.width) - let dialogHeight = dialogView.heightAnchor.constraint(equalToConstant: Constants.dialogSize.height) - let dialogCenterX = dialogView.centerXAnchor.constraint(equalTo: view.centerXAnchor) - let dialogCenterY = dialogView.centerYAnchor.constraint(equalTo: view.centerYAnchor) - let stackCenterX = stackView.centerXAnchor.constraint(equalTo: dialogView.centerXAnchor) - let stackCenterY = stackView.centerYAnchor.constraint(equalTo: dialogView.centerYAnchor) + let dialogWidth = self.dialogView.widthAnchor.constraint(equalToConstant: Constants.dialogSize.width) + let dialogHeight = self.dialogView.heightAnchor.constraint(equalToConstant: Constants.dialogSize.height) + let dialogCenterX = self.dialogView.centerXAnchor.constraint(equalTo: view.centerXAnchor) + let dialogCenterY = self.dialogView.centerYAnchor.constraint(equalTo: view.centerYAnchor) + let stackCenterX = self.stackView.centerXAnchor.constraint(equalTo: self.dialogView.centerXAnchor) + let stackCenterY = self.stackView.centerYAnchor.constraint(equalTo: self.dialogView.centerYAnchor) let constraints = [dialogWidth, dialogHeight, dialogCenterX, dialogCenterY, @@ -78,11 +78,11 @@ class DialogViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - perform(#selector(dismissAnimated), with: nil, afterDelay: 0.5) + perform(#selector(self.dismissAnimated), with: nil, afterDelay: 0.5) } @objc func dismissAnimated() { - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(dismissAnimated), object: nil) + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.dismissAnimated), object: nil) dismiss(animated: true, completion: nil) } } diff --git a/MapboxNavigation/Dictionary.swift b/MapboxNavigation/Dictionary.swift index 912502c9a..a9b58f885 100644 --- a/MapboxNavigation/Dictionary.swift +++ b/MapboxNavigation/Dictionary.swift @@ -1,25 +1,25 @@ import Foundation -extension Dictionary where Key == Int, Value: NSExpression { +public extension Dictionary where Key == Int, Value: NSExpression { /** Returns a copy of the stop dictionary with each value multiplied by the given factor. */ - public func multiplied(by factor: Double) -> Dictionary { + func multiplied(by factor: Double) -> Dictionary { var newCameraStop: [Int: NSExpression] = [:] for stop in self { let currentValue = stop.value.constantValue as! Double - let newValue = currentValue * factor + let newValue = currentValue * factor newCameraStop[stop.key] = NSExpression(forConstantValue: newValue) } - return newCameraStop as! Dictionary + return newCameraStop as! [Key: Value] } } -extension Dictionary where Key == Int, Value == Double { - /** - Returns a copy of the stop dictionary with each value multiplied by the given factor. - */ - func multiplied(by factor: Double) -> Dictionary { - return self.mapValues { $0 * factor } - } +extension [Int: Double] { + /** + Returns a copy of the stop dictionary with each value multiplied by the given factor. + */ + func multiplied(by factor: Double) -> Dictionary { + mapValues { $0 * factor } + } } diff --git a/MapboxNavigation/EndOfRouteViewController.swift b/MapboxNavigation/EndOfRouteViewController.swift index 50db96f94..31b026dc0 100644 --- a/MapboxNavigation/EndOfRouteViewController.swift +++ b/MapboxNavigation/EndOfRouteViewController.swift @@ -1,12 +1,12 @@ -import UIKit import MapboxDirections +import UIKit -fileprivate enum ConstraintSpacing: CGFloat { +private enum ConstraintSpacing: CGFloat { case closer = 8.0 case further = 65.0 } -fileprivate enum ContainerHeight: CGFloat { +private enum ContainerHeight: CGFloat { case normal = 200 case commentShowing = 260 } @@ -33,82 +33,86 @@ open class EndOfRouteButton: StylableButton {} @objc(MBEndOfRouteViewController) class EndOfRouteViewController: UIViewController { - // MARK: - IBOutlets - @IBOutlet weak var labelContainer: UIView! - @IBOutlet weak var staticYouHaveArrived: EndOfRouteStaticLabel! - @IBOutlet weak var primary: UILabel! - @IBOutlet weak var endNavigationButton: UIButton! - @IBOutlet weak var stars: RatingControl! - @IBOutlet weak var commentView: UITextView! - @IBOutlet weak var commentViewContainer: UIView! - @IBOutlet weak var showCommentView: NSLayoutConstraint! - @IBOutlet weak var hideCommentView: NSLayoutConstraint! - @IBOutlet weak var ratingCommentsSpacing: NSLayoutConstraint! + + @IBOutlet var labelContainer: UIView! + @IBOutlet var staticYouHaveArrived: EndOfRouteStaticLabel! + @IBOutlet var primary: UILabel! + @IBOutlet var endNavigationButton: UIButton! + @IBOutlet var stars: RatingControl! + @IBOutlet var commentView: UITextView! + @IBOutlet var commentViewContainer: UIView! + @IBOutlet var showCommentView: NSLayoutConstraint! + @IBOutlet var hideCommentView: NSLayoutConstraint! + @IBOutlet var ratingCommentsSpacing: NSLayoutConstraint! // MARK: - Properties + lazy var placeholder: String = NSLocalizedString("END_OF_ROUTE_TITLE", bundle: .mapboxNavigation, value: "How can we improve?", comment: "Comment Placeholder Text") lazy var endNavigation: String = NSLocalizedString("END_NAVIGATION", bundle: .mapboxNavigation, value: "End Navigation", comment: "End Navigation Button Text") - typealias DismissHandler = ((Int, String?) -> Void) + typealias DismissHandler = (Int, String?) -> Void var dismissHandler: DismissHandler? var comment: String? var rating: Int = 0 { didSet { - rating == 0 ? hideComments() : showComments() + self.rating == 0 ? self.hideComments() : self.showComments() } } open var destination: Waypoint? { didSet { guard isViewLoaded else { return } - updateInterface() + self.updateInterface() } } // MARK: - Lifecycle Methods + override func viewDidLoad() { super.viewDidLoad() - clearInterface() - stars.didChangeRating = { [weak self] (new) in self?.rating = new } - setPlaceholderText() - styleCommentView() - commentViewContainer.alpha = 0.0 //setting initial hidden state + self.clearInterface() + self.stars.didChangeRating = { [weak self] new in self?.rating = new } + self.setPlaceholderText() + self.styleCommentView() + self.commentViewContainer.alpha = 0.0 // setting initial hidden state } override func viewWillAppear(_ animated: Bool) { super.viewWillDisappear(animated) view.roundCorners([.topLeft, .topRight]) - preferredContentSize.height = height(for: .normal) - updateInterface() + preferredContentSize.height = self.height(for: .normal) + self.updateInterface() } // MARK: - IBActions + @IBAction func endNavigationPressed(_ sender: Any) { - dismissView() + self.dismissView() } // MARK: - Private Functions + private func styleCommentView() { - commentView.layer.cornerRadius = 6.0 - commentView.layer.borderColor = UIColor.lightGray.cgColor - commentView.layer.borderWidth = 1.0 - commentView.textContainerInset = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) + self.commentView.layer.cornerRadius = 6.0 + self.commentView.layer.borderColor = UIColor.lightGray.cgColor + self.commentView.layer.borderWidth = 1.0 + self.commentView.textContainerInset = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) } fileprivate func dismissView() { let dismissal: () -> Void = { self.dismissHandler?(self.rating, self.comment) } - guard commentView.isFirstResponder else { return _ = dismissal() } - commentView.resignFirstResponder() - let fireTime = DispatchTime.now() + 0.3 //Not ideal, but works for now + guard self.commentView.isFirstResponder else { return _ = dismissal() } + self.commentView.resignFirstResponder() + let fireTime = DispatchTime.now() + 0.3 // Not ideal, but works for now DispatchQueue.main.asyncAfter(deadline: fireTime, execute: dismissal) } private func showComments(animated: Bool = true) { - showCommentView.isActive = true - hideCommentView.isActive = false - ratingCommentsSpacing.constant = ConstraintSpacing.closer.rawValue - preferredContentSize.height = height(for: .commentShowing) + self.showCommentView.isActive = true + self.hideCommentView.isActive = false + self.ratingCommentsSpacing.constant = ConstraintSpacing.closer.rawValue + preferredContentSize.height = self.height(for: .commentShowing) let animate = { self.view.layoutIfNeeded() @@ -116,17 +120,21 @@ class EndOfRouteViewController: UIViewController { self.labelContainer.alpha = 0.0 } - let completion: (Bool) -> Void = { _ in self.labelContainer.isHidden = true} - let noAnimate = { animate() ; completion(true) } - animated ? UIView.animate(withDuration: 0.3, animations: animate, completion: nil) : noAnimate() + let completion: (Bool) -> Void = { _ in self.labelContainer.isHidden = true } + let noAnimate = { animate(); completion(true) } + if animated { + UIView.animate(withDuration: 0.3, animations: animate, completion: nil) + } else { + noAnimate() + } } private func hideComments(animated: Bool = true) { - labelContainer.isHidden = false - showCommentView.isActive = false - hideCommentView.isActive = true - ratingCommentsSpacing.constant = ConstraintSpacing.further.rawValue - preferredContentSize.height = height(for: .normal) + self.labelContainer.isHidden = false + self.showCommentView.isActive = false + self.hideCommentView.isActive = true + self.ratingCommentsSpacing.constant = ConstraintSpacing.further.rawValue + preferredContentSize.height = self.height(for: .normal) let animate = { self.view.layoutIfNeeded() @@ -135,8 +143,10 @@ class EndOfRouteViewController: UIViewController { } let completion: (Bool) -> Void = { _ in self.commentViewContainer.isHidden = true } - let noAnimation = { animate(); completion(true)} - animated ? UIView.animate(withDuration: 0.3, animations: animate, completion: nil) : noAnimation() + let noAnimation = { animate(); completion(true) } + if animated { + UIView.animate(withDuration: 0.3, animations: animate, completion: nil) + } else { noAnimation() } } private func height(for height: ContainerHeight) -> CGFloat { @@ -146,26 +156,27 @@ class EndOfRouteViewController: UIViewController { } private func updateInterface() { - guard let name = destination?.name?.nonEmptyString else { return styleForUnnamedDestination() } - primary.text = name + guard let name = destination?.name?.nonEmptyString else { return self.styleForUnnamedDestination() } + self.primary.text = name } private func clearInterface() { - primary.text = nil - stars.rating = 0 + self.primary.text = nil + self.stars.rating = 0 } private func styleForUnnamedDestination() { - staticYouHaveArrived.alpha = 0.0 - primary.text = NSLocalizedString("END_OF_ROUTE_ARRIVED", bundle: .mapboxNavigation, value:"You have arrived", comment:"Title used for arrival") + self.staticYouHaveArrived.alpha = 0.0 + self.primary.text = NSLocalizedString("END_OF_ROUTE_ARRIVED", bundle: .mapboxNavigation, value: "You have arrived", comment: "Title used for arrival") } private func setPlaceholderText() { - commentView.text = placeholder + self.commentView.text = self.placeholder } } // MARK: - UITextViewDelegate + extension EndOfRouteViewController: UITextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard text.count == 1, text.rangeOfCharacter(from: CharacterSet.newlines) != nil else { return true } @@ -174,11 +185,11 @@ extension EndOfRouteViewController: UITextViewDelegate { } func textViewDidChange(_ textView: UITextView) { - comment = textView.text //Bind data model + self.comment = textView.text // Bind data model } func textViewDidBeginEditing(_ textView: UITextView) { - if textView.text == placeholder { + if textView.text == self.placeholder { textView.text = nil textView.alpha = 1.0 } @@ -187,7 +198,7 @@ extension EndOfRouteViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { if (textView.text?.isEmpty ?? true) == true { - textView.text = placeholder + textView.text = self.placeholder textView.alpha = 0.9 } } diff --git a/MapboxNavigation/Error.swift b/MapboxNavigation/Error.swift index 74c5fab32..2120f862c 100644 --- a/MapboxNavigation/Error.swift +++ b/MapboxNavigation/Error.swift @@ -11,7 +11,7 @@ extension NSError { var userInfo = [ NSLocalizedFailureReasonErrorKey: localizedFailureReason ] - if let spokenInstructionCode = spokenInstructionCode { + if let spokenInstructionCode { userInfo[MBSpokenInstructionErrorCodeKey] = String(spokenInstructionCode.rawValue) } self.init(domain: MBErrorDomain, code: code.rawValue, userInfo: userInfo) diff --git a/MapboxNavigation/ExitView.swift b/MapboxNavigation/ExitView.swift index 3c651de37..25bf80117 100644 --- a/MapboxNavigation/ExitView.swift +++ b/MapboxNavigation/ExitView.swift @@ -1,10 +1,10 @@ import UIKit -enum ExitSide: String{ +enum ExitSide: String { case left, right, other var exitImage: UIImage { - return self == .left ? ExitView.leftExitImage : ExitView.rightExitImage + self == .left ? ExitView.leftExitImage : ExitView.rightExitImage } } @@ -12,27 +12,27 @@ class ExitView: StylableView { static let leftExitImage = UIImage(named: "exit-left", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate) static let rightExitImage = UIImage(named: "exit-right", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate) - static let labelFontSizeScaleFactor: CGFloat = 2.0/3.0 + static let labelFontSizeScaleFactor: CGFloat = 2.0 / 3.0 @objc dynamic var foregroundColor: UIColor? { didSet { - layer.borderColor = foregroundColor?.cgColor - imageView.tintColor = foregroundColor - exitNumberLabel.textColor = foregroundColor + layer.borderColor = self.foregroundColor?.cgColor + self.imageView.tintColor = self.foregroundColor + self.exitNumberLabel.textColor = self.foregroundColor setNeedsDisplay() } } var side: ExitSide = .right { didSet { - populateExitImage() - rebuildConstraints() + self.populateExitImage() + self.rebuildConstraints() } } lazy var imageView: UIImageView = { let view = UIImageView(image: self.side.exitImage) - view.tintColor = foregroundColor + view.tintColor = self.foregroundColor view.translatesAutoresizingMaskIntoConstraints = false view.contentMode = .scaleAspectFit return view @@ -40,66 +40,64 @@ class ExitView: StylableView { lazy var exitNumberLabel: UILabel = { let label: UILabel = .forAutoLayout() - label.text = exitText + label.text = self.exitText label.textColor = .black - label.font = UIFont.boldSystemFont(ofSize: pointSize * ExitView.labelFontSizeScaleFactor) + label.font = UIFont.boldSystemFont(ofSize: self.pointSize * ExitView.labelFontSizeScaleFactor) return label }() var exitText: String? { didSet { - exitNumberLabel.text = exitText + self.exitNumberLabel.text = self.exitText invalidateIntrinsicContentSize() } } var pointSize: CGFloat { didSet { - exitNumberLabel.font = exitNumberLabel.font.withSize(pointSize * ExitView.labelFontSizeScaleFactor) - rebuildConstraints() + self.exitNumberLabel.font = self.exitNumberLabel.font.withSize(self.pointSize * ExitView.labelFontSizeScaleFactor) + self.rebuildConstraints() } } - func spacing(for side: ExitSide, direction: UIUserInterfaceLayoutDirection = UIApplication.shared.userInterfaceLayoutDirection) -> CGFloat { let space: (less: CGFloat, more: CGFloat) = (4.0, 6.0) let lessSide: ExitSide = (direction == .rightToLeft) ? .left : .right return side == lessSide ? space.less : space.more } - convenience init(pointSize: CGFloat, side: ExitSide = .right, text: String) { self.init(frame: .zero) self.pointSize = pointSize self.side = side self.exitText = text - commonInit() + self.commonInit() } override init(frame: CGRect) { - pointSize = 0.0 + self.pointSize = 0.0 super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { - pointSize = 0.0 + self.pointSize = 0.0 super.init(coder: aDecoder) - commonInit() + self.commonInit() } func rebuildConstraints() { - NSLayoutConstraint.deactivate(self.constraints) - buildConstraints() + NSLayoutConstraint.deactivate(constraints) + self.buildConstraints() } func commonInit() { translatesAutoresizingMaskIntoConstraints = false layer.masksToBounds = true - //build view hierarchy - [imageView, exitNumberLabel].forEach(addSubview(_:)) - buildConstraints() + // build view hierarchy + [self.imageView, self.exitNumberLabel].forEach(addSubview(_:)) + self.buildConstraints() setNeedsLayout() invalidateIntrinsicContentSize() @@ -107,38 +105,39 @@ class ExitView: StylableView { } func populateExitImage() { - imageView.image = self.side.exitImage + self.imageView.image = self.side.exitImage } func buildConstraints() { - let height = heightAnchor.constraint(equalToConstant: pointSize * 1.2) + let height = heightAnchor.constraint(equalToConstant: self.pointSize * 1.2) - let imageHeight = imageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.4) - let imageAspect = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: imageView.image?.size.aspectRatio ?? 1.0) + let imageHeight = self.imageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.4) + let imageAspect = self.imageView.widthAnchor.constraint(equalTo: self.imageView.heightAnchor, multiplier: self.imageView.image?.size.aspectRatio ?? 1.0) - let imageCenterY = imageView.centerYAnchor.constraint(equalTo: centerYAnchor) - let labelCenterY = exitNumberLabel.centerYAnchor.constraint(equalTo: centerYAnchor) + let imageCenterY = self.imageView.centerYAnchor.constraint(equalTo: centerYAnchor) + let labelCenterY = self.exitNumberLabel.centerYAnchor.constraint(equalTo: centerYAnchor) - let sideConstraints = self.side != .left ? rightExitConstraints() : leftExitConstraints() + let sideConstraints = self.side != .left ? self.rightExitConstraints() : self.leftExitConstraints() let constraints = [height, imageHeight, imageAspect, imageCenterY, labelCenterY] + sideConstraints addConstraints(constraints) } + func rightExitConstraints() -> [NSLayoutConstraint] { - let labelLeading = exitNumberLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8) - let spacing = self.spacing(for: .right) - let imageLabelSpacing = exitNumberLabel.trailingAnchor.constraint(equalTo: imageView.leadingAnchor, constant: -1 * spacing) - let imageTrailing = trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 8) + let labelLeading = self.exitNumberLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8) + let spacing = spacing(for: .right) + let imageLabelSpacing = self.exitNumberLabel.trailingAnchor.constraint(equalTo: self.imageView.leadingAnchor, constant: -1 * spacing) + let imageTrailing = trailingAnchor.constraint(equalTo: self.imageView.trailingAnchor, constant: 8) return [labelLeading, imageLabelSpacing, imageTrailing] } func leftExitConstraints() -> [NSLayoutConstraint] { - let imageLeading = imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8) - let spacing = self.spacing(for: .left) - let imageLabelSpacing = imageView.trailingAnchor.constraint(equalTo: exitNumberLabel.leadingAnchor, constant: -1 * spacing) - let labelTrailing = trailingAnchor.constraint(equalTo: exitNumberLabel.trailingAnchor, constant: 8) + let imageLeading = self.imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8) + let spacing = spacing(for: .left) + let imageLabelSpacing = self.imageView.trailingAnchor.constraint(equalTo: self.exitNumberLabel.leadingAnchor, constant: -1 * spacing) + let labelTrailing = trailingAnchor.constraint(equalTo: self.exitNumberLabel.trailingAnchor, constant: 8) return [imageLeading, imageLabelSpacing, labelTrailing] } @@ -148,6 +147,6 @@ class ExitView: StylableView { static func criticalHash(side: ExitSide, dataSource: DataSource) -> String { let proxy = ExitView.appearance() let criticalProperties: [AnyHashable?] = [side, dataSource.font.pointSize, proxy.backgroundColor, proxy.foregroundColor, proxy.borderWidth, proxy.cornerRadius] - return String(describing: criticalProperties.reduce(0, { $0 ^ ($1?.hashValue ?? 0)})) + return String(describing: criticalProperties.reduce(0) { $0 ^ ($1?.hashValue ?? 0) }) } } diff --git a/MapboxNavigation/FeedbackCollectionViewCell.swift b/MapboxNavigation/FeedbackCollectionViewCell.swift index 0500968c1..15c631cb0 100644 --- a/MapboxNavigation/FeedbackCollectionViewCell.swift +++ b/MapboxNavigation/FeedbackCollectionViewCell.swift @@ -1,11 +1,10 @@ import Foundation import UIKit - class FeedbackCollectionViewCell: UICollectionViewCell { static let defaultIdentifier = "MapboxFeedbackCell" - struct Constants { + enum Constants { static let imageSize: CGSize = 70.0 static let padding: CGFloat = 8 static let titleFont: UIFont = .systemFont(ofSize: 18.0) @@ -26,23 +25,23 @@ class FeedbackCollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { - setupViews() - setupConstraints() + self.setupViews() + self.setupConstraints() } override var isHighlighted: Bool { didSet { - if originalTransform == nil { - originalTransform = self.imageView.transform + if self.originalTransform == nil { + self.originalTransform = self.imageView.transform } UIView.defaultSpringAnimation(0.3, animations: { @@ -57,17 +56,17 @@ class FeedbackCollectionViewCell: UICollectionViewCell { } func setupViews() { - addSubview(imageView) - addSubview(titleLabel) + addSubview(self.imageView) + addSubview(self.titleLabel) } func setupConstraints() { - imageView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.padding).isActive = true - imageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true - imageView.heightAnchor.constraint(equalToConstant: Constants.imageSize.height).isActive = true - imageView.widthAnchor.constraint(equalToConstant: Constants.imageSize.width).isActive = true + self.imageView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.padding).isActive = true + self.imageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true + self.imageView.heightAnchor.constraint(equalToConstant: Constants.imageSize.height).isActive = true + self.imageView.widthAnchor.constraint(equalToConstant: Constants.imageSize.width).isActive = true - titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true - titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: Constants.padding).isActive = true + self.titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true + self.titleLabel.topAnchor.constraint(equalTo: self.imageView.bottomAnchor, constant: Constants.padding).isActive = true } } diff --git a/MapboxNavigation/FeedbackItem.swift b/MapboxNavigation/FeedbackItem.swift index 107b8a81d..e74974ec5 100644 --- a/MapboxNavigation/FeedbackItem.swift +++ b/MapboxNavigation/FeedbackItem.swift @@ -1,9 +1,9 @@ -import UIKit import MapboxCoreNavigation +import UIKit -extension UIImage { - fileprivate class func feedbackImage(named: String) -> UIImage { - return Bundle.mapboxNavigation.image(named: named)! +private extension UIImage { + class func feedbackImage(named: String) -> UIImage { + Bundle.mapboxNavigation.image(named: named)! } } @@ -36,21 +36,21 @@ public class FeedbackItem: NSObject { self.feedbackType = feedbackType } - static let closure = FeedbackItem(title: closureTitle, image: .feedbackImage(named:"feedback_closed"), feedbackType: .roadClosed) - static let turnNotAllowed = FeedbackItem(title: notAllowedTitle, image: .feedbackImage(named:"feedback_not_allowed"), feedbackType: .notAllowed) - static let reportTraffic = FeedbackItem(title: reportTrafficTitle, image: .feedbackImage(named:"feedback_traffic"), feedbackType: .reportTraffic) - static let confusingInstructions = FeedbackItem(title: confusingInstructionTitle, image: .feedbackImage(named:"feedback_confusing"), feedbackType: .confusingInstruction) - static let badRoute = FeedbackItem(title: badRouteTitle, image: .feedbackImage(named:"feedback_wrong"), feedbackType: .routingError) - static let missingRoad = FeedbackItem(title: missingExitTitle, image: .feedbackImage(named:"feedback-missing-road"), feedbackType: .missingRoad) - static let missingExit = FeedbackItem(title: missingRoadTitle, image: .feedbackImage(named:"feedback-exit"), feedbackType: .missingExit) - static let generalMapError = FeedbackItem(title: generalIssueTitle, image: .feedbackImage(named:"feedback_map_issue"), feedbackType: .mapIssue) + static let closure = FeedbackItem(title: closureTitle, image: .feedbackImage(named: "feedback_closed"), feedbackType: .roadClosed) + static let turnNotAllowed = FeedbackItem(title: notAllowedTitle, image: .feedbackImage(named: "feedback_not_allowed"), feedbackType: .notAllowed) + static let reportTraffic = FeedbackItem(title: reportTrafficTitle, image: .feedbackImage(named: "feedback_traffic"), feedbackType: .reportTraffic) + static let confusingInstructions = FeedbackItem(title: confusingInstructionTitle, image: .feedbackImage(named: "feedback_confusing"), feedbackType: .confusingInstruction) + static let badRoute = FeedbackItem(title: badRouteTitle, image: .feedbackImage(named: "feedback_wrong"), feedbackType: .routingError) + static let missingRoad = FeedbackItem(title: missingExitTitle, image: .feedbackImage(named: "feedback-missing-road"), feedbackType: .missingRoad) + static let missingExit = FeedbackItem(title: missingRoadTitle, image: .feedbackImage(named: "feedback-exit"), feedbackType: .missingExit) + static let generalMapError = FeedbackItem(title: generalIssueTitle, image: .feedbackImage(named: "feedback_map_issue"), feedbackType: .mapIssue) } -fileprivate let closureTitle = NSLocalizedString("FEEDBACK_ROAD_CLOSURE", bundle: .mapboxNavigation, value: "Road\nClosed", comment: "Feedback type for Road Closed") -fileprivate let notAllowedTitle = NSLocalizedString("FEEDBACK_NOT_ALLOWED", bundle: .mapboxNavigation, value: "Not\nAllowed", comment: "Feedback type for a maneuver that is Not Allowed") -fileprivate let reportTrafficTitle = NSLocalizedString("FEEDBACK_REPORT_TRAFFIC", bundle: .mapboxNavigation, value: "Report\nTraffic", comment: "Feedback type for Report Traffic") -fileprivate let confusingInstructionTitle = NSLocalizedString("FEEDBACK_CONFUSING_INSTRUCTION", bundle: .mapboxNavigation, value: "Confusing\nInstruction", comment: "Feedback type for Confusing Instruction") -fileprivate let badRouteTitle = NSLocalizedString("FEEDBACK_BAD_ROUTE", bundle: .mapboxNavigation, value: "Bad \nRoute", comment: "Feedback type for Bad Route") -fileprivate let missingExitTitle = NSLocalizedString("FEEDBACK_MISSING_EXIT", bundle: .mapboxNavigation, value: "Missing\nExit", comment: "Feedback type for Missing Exit") -fileprivate let missingRoadTitle = NSLocalizedString("FEEDBACK_MISSING_ROAD", bundle: .mapboxNavigation, value: "Missing\nRoad", comment: "Feedback type for Missing Road") -fileprivate let generalIssueTitle = NSLocalizedString("FEEDBACK_GENERAL_ISSUE", bundle: .mapboxNavigation, value: "Other\nMap Issue", comment: "Feedback type for Other Map Issue Issue") +private let closureTitle = NSLocalizedString("FEEDBACK_ROAD_CLOSURE", bundle: .mapboxNavigation, value: "Road\nClosed", comment: "Feedback type for Road Closed") +private let notAllowedTitle = NSLocalizedString("FEEDBACK_NOT_ALLOWED", bundle: .mapboxNavigation, value: "Not\nAllowed", comment: "Feedback type for a maneuver that is Not Allowed") +private let reportTrafficTitle = NSLocalizedString("FEEDBACK_REPORT_TRAFFIC", bundle: .mapboxNavigation, value: "Report\nTraffic", comment: "Feedback type for Report Traffic") +private let confusingInstructionTitle = NSLocalizedString("FEEDBACK_CONFUSING_INSTRUCTION", bundle: .mapboxNavigation, value: "Confusing\nInstruction", comment: "Feedback type for Confusing Instruction") +private let badRouteTitle = NSLocalizedString("FEEDBACK_BAD_ROUTE", bundle: .mapboxNavigation, value: "Bad \nRoute", comment: "Feedback type for Bad Route") +private let missingExitTitle = NSLocalizedString("FEEDBACK_MISSING_EXIT", bundle: .mapboxNavigation, value: "Missing\nExit", comment: "Feedback type for Missing Exit") +private let missingRoadTitle = NSLocalizedString("FEEDBACK_MISSING_ROAD", bundle: .mapboxNavigation, value: "Missing\nRoad", comment: "Feedback type for Missing Road") +private let generalIssueTitle = NSLocalizedString("FEEDBACK_GENERAL_ISSUE", bundle: .mapboxNavigation, value: "Other\nMap Issue", comment: "Feedback type for Other Map Issue Issue") diff --git a/MapboxNavigation/GenericRouteShield.swift b/MapboxNavigation/GenericRouteShield.swift index d0d91e700..7aa612993 100644 --- a/MapboxNavigation/GenericRouteShield.swift +++ b/MapboxNavigation/GenericRouteShield.swift @@ -5,40 +5,40 @@ import UIKit `GenericRouteShield` is a class to render routes that do not have route-shields. */ public class GenericRouteShield: StylableView { - static let labelFontSizeScaleFactor: CGFloat = 2.0/3.0 + static let labelFontSizeScaleFactor: CGFloat = 2.0 / 3.0 - //The color to use for the text and border. + // The color to use for the text and border. @objc dynamic var foregroundColor: UIColor? { didSet { - layer.borderColor = foregroundColor?.cgColor - routeLabel.textColor = foregroundColor + layer.borderColor = self.foregroundColor?.cgColor + self.routeLabel.textColor = self.foregroundColor setNeedsDisplay() } } - //The label that contains the route code. + // The label that contains the route code. lazy var routeLabel: UILabel = { let label: UILabel = .forAutoLayout() - label.text = routeText + label.text = self.routeText label.textColor = .black - label.font = UIFont.boldSystemFont(ofSize: pointSize * ExitView.labelFontSizeScaleFactor) + label.font = UIFont.boldSystemFont(ofSize: self.pointSize * ExitView.labelFontSizeScaleFactor) return label }() - //The text to put in the label + // The text to put in the label var routeText: String? { didSet { - routeLabel.text = routeText + self.routeLabel.text = self.routeText invalidateIntrinsicContentSize() } } - //The size of the text the view attachment is contained within. + // The size of the text the view attachment is contained within. var pointSize: CGFloat { didSet { - routeLabel.font = routeLabel.font.withSize(pointSize * ExitView.labelFontSizeScaleFactor) - rebuildConstraints() + self.routeLabel.font = self.routeLabel.font.withSize(self.pointSize * ExitView.labelFontSizeScaleFactor) + self.rebuildConstraints() } } @@ -46,32 +46,32 @@ public class GenericRouteShield: StylableView { self.init(frame: .zero) self.pointSize = pointSize self.routeText = text - commonInit() + self.commonInit() } override init(frame: CGRect) { - pointSize = 0.0 + self.pointSize = 0.0 super.init(frame: frame) } - required public init?(coder aDecoder: NSCoder) { - pointSize = 0.0 + public required init?(coder aDecoder: NSCoder) { + self.pointSize = 0.0 super.init(coder: aDecoder) - commonInit() + self.commonInit() } func rebuildConstraints() { - NSLayoutConstraint.deactivate(self.constraints) - buildConstraints() + NSLayoutConstraint.deactivate(constraints) + self.buildConstraints() } func commonInit() { translatesAutoresizingMaskIntoConstraints = false layer.masksToBounds = true - //build view hierarchy - addSubview(routeLabel) - buildConstraints() + // build view hierarchy + addSubview(self.routeLabel) + self.buildConstraints() setNeedsLayout() invalidateIntrinsicContentSize() @@ -79,12 +79,12 @@ public class GenericRouteShield: StylableView { } func buildConstraints() { - let height = heightAnchor.constraint(equalToConstant: pointSize * 1.2) + let height = heightAnchor.constraint(equalToConstant: self.pointSize * 1.2) - let labelCenterY = routeLabel.centerYAnchor.constraint(equalTo: centerYAnchor) + let labelCenterY = self.routeLabel.centerYAnchor.constraint(equalTo: centerYAnchor) - let labelLeading = routeLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8) - let labelTrailingSpacing = routeLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8) + let labelLeading = self.routeLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8) + let labelTrailingSpacing = self.routeLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8) let constraints = [height, labelCenterY, labelLeading, labelTrailingSpacing] @@ -97,6 +97,6 @@ public class GenericRouteShield: StylableView { static func criticalHash(dataSource: DataSource) -> String { let proxy = GenericRouteShield.appearance() let criticalProperties: [AnyHashable?] = [dataSource.font.pointSize, proxy.backgroundColor, proxy.foregroundColor, proxy.borderWidth, proxy.cornerRadius] - return String(describing: criticalProperties.reduce(0, { $0 ^ ($1?.hashValue ?? 0)})) + return String(describing: criticalProperties.reduce(0) { $0 ^ ($1?.hashValue ?? 0) }) } } diff --git a/MapboxNavigation/HighwayShield.swift b/MapboxNavigation/HighwayShield.swift index ee162a00d..f418f213f 100644 --- a/MapboxNavigation/HighwayShield.swift +++ b/MapboxNavigation/HighwayShield.swift @@ -1,7 +1,6 @@ import UIKit -struct HighwayShield { - +enum HighwayShield { enum RoadClass: String { case alternate, duplex, business, truck, bypass, b case oneB = "1b", twoA = "2a", twoB = "2b" @@ -49,7 +48,7 @@ struct HighwayShield { case let .communal(locale): return "\(locale)-communal" case let .interstate(locale, roadClass): - guard let currentRoadClass = roadClass else { return "\(locale.rawValue)-interstate"} + guard let currentRoadClass = roadClass else { return "\(locale.rawValue)-interstate" } return "\(locale.rawValue)-interstate-\(currentRoadClass.rawValue)" case let .metropolitan(locale): return "\(locale.rawValue)-metropolitan" @@ -59,7 +58,7 @@ struct HighwayShield { } init?(rawValue: RawValue) { - let fields = rawValue.split(separator: "-").compactMap(String.init(_:)) + let fields = rawValue.split(separator: "-").compactMap(String.init(_:)) switch fields.count { case 1 where rawValue == "default": self = .generic @@ -83,49 +82,49 @@ struct HighwayShield { private static func type(for identifier: String) -> RoadTypeForLocaleRoadClassClosure? { switch identifier { case "motorway": - return localeOnlyTransform(RoadType.motorway) + self.localeOnlyTransform(RoadType.motorway) case "expressway": - return localeOnlyTransform(RoadType.expressway) + self.localeOnlyTransform(RoadType.expressway) case "national": - return localeOnlyTransform(RoadType.national) + self.localeOnlyTransform(RoadType.national) case "federal": - return localeOnlyTransform(RoadType.federal) + self.localeOnlyTransform(RoadType.federal) case "main": - return localeOnlyTransform(RoadType.main) + self.localeOnlyTransform(RoadType.main) case "road": - return localeOnlyTransform(RoadType.road) + self.localeOnlyTransform(RoadType.road) case "primary": - return localeOnlyTransform(RoadType.primary) + self.localeOnlyTransform(RoadType.primary) case "secondary": - return localeOnlyTransform(RoadType.secondary) + self.localeOnlyTransform(RoadType.secondary) case "trunk": - return localeOnlyTransform(RoadType.trunk) + self.localeOnlyTransform(RoadType.trunk) case "regional": - return localeOnlyTransform(RoadType.regional) + self.localeOnlyTransform(RoadType.regional) case "voivodeship": - return localeOnlyTransform(RoadType.voivodeship) + self.localeOnlyTransform(RoadType.voivodeship) case "county": - return localeOnlyTransform(RoadType.county) + self.localeOnlyTransform(RoadType.county) case "communal": - return localeOnlyTransform(RoadType.communal) + self.localeOnlyTransform(RoadType.communal) case "provincial": - return localeOnlyTransform(RoadType.provincial) + self.localeOnlyTransform(RoadType.provincial) case "metropolitan": - return localeOnlyTransform(RoadType.metropolitan) + self.localeOnlyTransform(RoadType.metropolitan) case "state": - return RoadType.state + RoadType.state case "highway": - return RoadType.highway + RoadType.highway case "interstate": - return RoadType.interstate + RoadType.interstate default: - return nil + nil } } static func localeOnlyTransform(_ closure: @escaping RoadTypeForLocaleClosure) -> RoadTypeForLocaleRoadClassClosure { - return { locale, _ in - return closure(locale) + { locale, _ in + closure(locale) } } diff --git a/MapboxNavigation/ImageCache.swift b/MapboxNavigation/ImageCache.swift index 7f6cdeee9..50afd514e 100644 --- a/MapboxNavigation/ImageCache.swift +++ b/MapboxNavigation/ImageCache.swift @@ -1,14 +1,14 @@ import UIKit -internal class ImageCache: BimodalImageCache { +class ImageCache: BimodalImageCache { let memoryCache: NSCache let fileCache: FileCache init() { - memoryCache = NSCache() - memoryCache.name = "In-Memory Image Cache" + self.memoryCache = NSCache() + self.memoryCache.name = "In-Memory Image Cache" - fileCache = FileCache() + self.fileCache = FileCache() NotificationCenter.default.addObserver(self, selector: #selector(DataCache.clearMemory), name: UIApplication.didReceiveMemoryWarningNotification, object: nil) } @@ -19,21 +19,21 @@ internal class ImageCache: BimodalImageCache { Stores an image in the cache for the given key. If `toDisk` is set to `true`, the completion handler is called following writing the image to disk, otherwise it is called immediately upon storing the image in the memory cache. */ public func store(_ image: UIImage, forKey key: String, toDisk: Bool, completion: CompletionHandler?) { - storeImageInMemoryCache(image, forKey: key) + self.storeImageInMemoryCache(image, forKey: key) guard toDisk == true, let data = image.pngData() else { completion?() return } - fileCache.store(data, forKey: key, completion: completion) + self.fileCache.store(data, forKey: key, completion: completion) } /* Returns an image from the cache for the given key, if any. The memory cache is consulted first, followed by the disk cache. If an image is found on disk which isn't in memory, it is added to the memory cache. */ public func image(forKey key: String?) -> UIImage? { - guard let key = key else { + guard let key else { return nil } @@ -42,7 +42,7 @@ internal class ImageCache: BimodalImageCache { } if let image = imageFromDiskCache(forKey: key) { - storeImageInMemoryCache(image, forKey: key) + self.storeImageInMemoryCache(image, forKey: key) return image } @@ -53,14 +53,14 @@ internal class ImageCache: BimodalImageCache { Clears out the memory cache. */ public func clearMemory() { - memoryCache.removeAllObjects() + self.memoryCache.removeAllObjects() } /* Clears the disk cache and calls the completion handler when finished. */ public func clearDisk(completion: CompletionHandler?) { - fileCache.clearDisk(completion: completion) + self.fileCache.clearDisk(completion: completion) } private func cost(forImage image: UIImage) -> Int { @@ -70,11 +70,11 @@ internal class ImageCache: BimodalImageCache { } private func storeImageInMemoryCache(_ image: UIImage, forKey key: String) { - memoryCache.setObject(image, forKey: key as NSString, cost: cost(forImage: image)) + self.memoryCache.setObject(image, forKey: key as NSString, cost: self.cost(forImage: image)) } private func imageFromMemoryCache(forKey key: String) -> UIImage? { - return memoryCache.object(forKey: key as NSString) + self.memoryCache.object(forKey: key as NSString) } private func imageFromDiskCache(forKey key: String?) -> UIImage? { diff --git a/MapboxNavigation/ImageDownload.swift b/MapboxNavigation/ImageDownload.swift index 1450496ed..015ace1ca 100644 --- a/MapboxNavigation/ImageDownload.swift +++ b/MapboxNavigation/ImageDownload.swift @@ -14,15 +14,15 @@ protocol ImageDownload: URLSessionDataDelegate { class ImageDownloadOperation: Operation, ImageDownload { override var isConcurrent: Bool { - return true + true } override var isFinished: Bool { - return _finished + self._finished } override var isExecuting: Bool { - return _executing + self._executing } private var _finished = false { @@ -48,7 +48,7 @@ class ImageDownloadOperation: Operation, ImageDownload { private var dataTask: URLSessionDataTask? private var incomingData: Data? - private var completionBlocks: Array = [] + private var completionBlocks: [ImageDownloadCompletionBlock] = [] private let barrierQueue = DispatchQueue(label: Bundle.mapboxNavigation.bundleIdentifier! + ".ImageDownloadCompletionBarrierQueue", attributes: .concurrent) required init(request: URLRequest, in session: URLSession) { @@ -57,7 +57,7 @@ class ImageDownloadOperation: Operation, ImageDownload { } func addCompletion(_ completion: @escaping ImageDownloadCompletionBlock) { - barrierQueue.async(flags: .barrier) { + self.barrierQueue.async(flags: .barrier) { self.completionBlocks.append(completion) } } @@ -69,21 +69,21 @@ class ImageDownloadOperation: Operation, ImageDownload { } super.cancel() - if let dataTask = dataTask { + if let dataTask { dataTask.cancel() - incomingData = nil + self.incomingData = nil self.dataTask = nil } - if isExecuting { - _executing = false + if self.isExecuting { + self._executing = false } - if !isFinished { - _finished = true + if !self.isFinished { + self._finished = true } - barrierQueue.async { + self.barrierQueue.async { self.completionBlocks.removeAll() } } @@ -95,18 +95,17 @@ class ImageDownloadOperation: Operation, ImageDownload { } if isCancelled == true { - _finished = true + self._finished = true return } - dataTask = session.dataTask(with: self.request) - if let dataTask = dataTask { - _executing = true + dataTask = self.session.dataTask(with: self.request) + if let dataTask { + self._executing = true dataTask.resume() } else { - //fail and bail; connection failed or bad URL (client-side error) + // fail and bail; connection failed or bad URL (client-side error) } - } // MARK: URLSessionDataDelegate @@ -120,13 +119,13 @@ class ImageDownloadOperation: Operation, ImageDownload { self.incomingData = Data() completionHandler(.allow) } else { - fireAllCompletions(nil, data: nil, error: DownloadError.serverError) + self.fireAllCompletions(nil, data: nil, error: DownloadError.serverError) completionHandler(.cancel) } } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - if var localData: Data = self.incomingData { + if var localData: Data = incomingData { let bytes = [UInt8](data) localData.append(bytes, count: bytes.count) self.incomingData = localData @@ -135,26 +134,26 @@ class ImageDownloadOperation: Operation, ImageDownload { func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { guard error == nil else { - fireAllCompletions(nil, data: nil, error: DownloadError.clientError) + self.fireAllCompletions(nil, data: nil, error: DownloadError.clientError) return } - if let data = incomingData, let image = UIImage.init(data: data, scale: UIScreen.main.scale) { - fireAllCompletions(image, data: data, error: nil) + if let data = incomingData, let image = UIImage(data: data, scale: UIScreen.main.scale) { + self.fireAllCompletions(image, data: data, error: nil) } else { - fireAllCompletions(nil, data: incomingData, error: DownloadError.noImageData) + self.fireAllCompletions(nil, data: self.incomingData, error: DownloadError.noImageData) } - _finished = true - _executing = false - dataTask = nil - barrierQueue.async { + self._finished = true + self._executing = false + self.dataTask = nil + self.barrierQueue.async { self.completionBlocks.removeAll() } } private func fireAllCompletions(_ image: UIImage?, data: Data?, error: Error?) { - barrierQueue.sync { - completionBlocks.forEach { $0(image, data, error) } + self.barrierQueue.sync { + self.completionBlocks.forEach { $0(image, data, error) } } } } diff --git a/MapboxNavigation/ImageDownloader.swift b/MapboxNavigation/ImageDownloader.swift index ef009e9f0..2989795f1 100644 --- a/MapboxNavigation/ImageDownloader.swift +++ b/MapboxNavigation/ImageDownloader.swift @@ -3,17 +3,15 @@ import UIKit typealias ImageDownloadCompletionBlock = (UIImage?, Data?, Error?) -> Void protocol ReentrantImageDownloader { - func downloadImage(with url: URL, completion: ImageDownloadCompletionBlock?) -> Void + func downloadImage(with url: URL, completion: ImageDownloadCompletionBlock?) func activeOperation(with url: URL) -> ImageDownload? func setOperationType(_ operationType: ImageDownload.Type?) } class ImageDownloader: NSObject, ReentrantImageDownloader, URLSessionDataDelegate { - private var sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default + private var sessionConfiguration: URLSessionConfiguration = .default - lazy private var urlSession: URLSession = { - return URLSession.init(configuration: sessionConfiguration, delegate: self, delegateQueue: nil) - }() + private lazy var urlSession: URLSession = .init(configuration: sessionConfiguration, delegate: self, delegateQueue: nil) private var downloadQueue: OperationQueue private var accessQueue: DispatchQueue @@ -46,7 +44,7 @@ class ImageDownloader: NSObject, ReentrantImageDownloader, URLSessionDataDelegat } func downloadImage(with url: URL, completion: ImageDownloadCompletionBlock?) { - accessQueue.sync(flags: .barrier) { + self.accessQueue.sync(flags: .barrier) { let request: URLRequest = self.urlRequest(with: url) var operation: ImageDownload if let activeOperation = self.activeOperation(with: url) { @@ -58,7 +56,7 @@ class ImageDownloader: NSObject, ReentrantImageDownloader, URLSessionDataDelegat self.downloadQueue.addOperation(operation) } } - if let completion = completion { + if let completion { operation.addCompletion(completion) } } @@ -82,7 +80,7 @@ class ImageDownloader: NSObject, ReentrantImageDownloader, URLSessionDataDelegat } func setOperationType(_ operationType: ImageDownload.Type?) { - if let operationType = operationType { + if let operationType { self.operationType = operationType } else { self.operationType = ImageDownloadOperation.self @@ -111,9 +109,8 @@ class ImageDownloader: NSObject, ReentrantImageDownloader, URLSessionDataDelegat return } operation.urlSession?(session, task: task, didCompleteWithError: error) - accessQueue.async { + self.accessQueue.async { self.operations[url] = nil } } - } diff --git a/MapboxNavigation/ImageRepository.swift b/MapboxNavigation/ImageRepository.swift index 6ff6e4719..d80d64a89 100644 --- a/MapboxNavigation/ImageRepository.swift +++ b/MapboxNavigation/ImageRepository.swift @@ -1,37 +1,36 @@ import UIKit class ImageRepository { - - public var sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default { + public var sessionConfiguration: URLSessionConfiguration = .default { didSet { - imageDownloader = ImageDownloader(sessionConfiguration: sessionConfiguration) + self.imageDownloader = ImageDownloader(sessionConfiguration: self.sessionConfiguration) } } - public static let shared = ImageRepository.init() + public static let shared = ImageRepository() let imageCache: BimodalImageCache - fileprivate(set) var imageDownloader: ReentrantImageDownloader + private(set) var imageDownloader: ReentrantImageDownloader var useDiskCache: Bool required init(withDownloader downloader: ReentrantImageDownloader = ImageDownloader(), cache: BimodalImageCache = ImageCache(), useDisk: Bool = true) { - imageDownloader = downloader - imageCache = cache - useDiskCache = useDisk + self.imageDownloader = downloader + self.imageCache = cache + self.useDiskCache = useDisk } func resetImageCache(_ completion: CompletionHandler?) { - imageCache.clearMemory() - imageCache.clearDisk(completion: completion) + self.imageCache.clearMemory() + self.imageCache.clearDisk(completion: completion) } func storeImage(_ image: UIImage, forKey key: String, toDisk: Bool = true) { - imageCache.store(image, forKey: key, toDisk: toDisk, completion: nil) + self.imageCache.store(image, forKey: key, toDisk: toDisk, completion: nil) } func cachedImageForKey(_ key: String) -> UIImage? { - return imageCache.image(forKey: key) + self.imageCache.image(forKey: key) } func imageWithURL(_ imageURL: URL, cacheKey: String, completion: @escaping (UIImage?) -> Void) { @@ -40,8 +39,8 @@ class ImageRepository { return } - let _ = imageDownloader.downloadImage(with: imageURL, completion: { [weak self] (image, data, error) in - guard let strongSelf = self, let image = image else { + _ = self.imageDownloader.downloadImage(with: imageURL, completion: { [weak self] image, _, error in + guard let strongSelf = self, let image else { completion(nil) return } @@ -58,6 +57,6 @@ class ImageRepository { } func disableDiskCache() { - useDiskCache = false + self.useDiskCache = false } } diff --git a/MapboxNavigation/InstructionLabel.swift b/MapboxNavigation/InstructionLabel.swift index 2fd771fb2..f0b23adf0 100644 --- a/MapboxNavigation/InstructionLabel.swift +++ b/MapboxNavigation/InstructionLabel.swift @@ -1,6 +1,6 @@ -import UIKit import MapboxCoreNavigation import MapboxDirections +import UIKit /// :nodoc: @objc(MBInstructionLabel) @@ -16,22 +16,21 @@ open class InstructionLabel: StylableLabel, InstructionPresenterDataSource { var instruction: VisualInstruction? { didSet { - guard let instruction = instruction else { + guard let instruction else { text = nil - instructionPresenter = nil + self.instructionPresenter = nil return } - let update: InstructionPresenter.ShieldDownloadCompletion = { [weak self] (attributedText) in + let update: InstructionPresenter.ShieldDownloadCompletion = { [weak self] attributedText in self?.attributedText = attributedText self?.imageDownloadCompletion?() } - let presenter = InstructionPresenter(instruction, dataSource: self, imageRepository: imageRepository, downloadCompletion: update) let attributed = presenter.attributedText() - attributedText = instructionDelegate?.label?(self, willPresent: instruction, as: attributed) ?? attributed - instructionPresenter = presenter + attributedText = self.instructionDelegate?.label?(self, willPresent: instruction, as: attributed) ?? attributed + self.instructionPresenter = presenter } } @@ -43,7 +42,6 @@ open class InstructionLabel: StylableLabel, InstructionPresenterDataSource { */ @objc(MBVisualInstructionDelegate) public protocol VisualInstructionDelegate: AnyObject { - /** Called when an InstructionLabel will present a visual instruction. diff --git a/MapboxNavigation/InstructionPresenter.swift b/MapboxNavigation/InstructionPresenter.swift index 9c36b43a3..c50b8ba88 100644 --- a/MapboxNavigation/InstructionPresenter.swift +++ b/MapboxNavigation/InstructionPresenter.swift @@ -1,5 +1,5 @@ -import UIKit import MapboxDirections +import UIKit protocol InstructionPresenterDataSource: AnyObject { var availableBounds: (() -> CGRect)! { get } @@ -11,7 +11,6 @@ protocol InstructionPresenterDataSource: AnyObject { typealias DataSource = InstructionPresenterDataSource class InstructionPresenter { - private let instruction: VisualInstruction private weak var dataSource: DataSource? @@ -23,7 +22,7 @@ class InstructionPresenter { } typealias ImageDownloadCompletion = (UIImage?) -> Void - typealias ShieldDownloadCompletion = (NSAttributedString) -> () + typealias ShieldDownloadCompletion = (NSAttributedString) -> Void let onShieldDownload: ShieldDownloadCompletion? @@ -31,13 +30,13 @@ class InstructionPresenter { func attributedText() -> NSAttributedString { let string = NSMutableAttributedString() - fittedAttributedComponents().forEach { string.append($0) } + self.fittedAttributedComponents().forEach { string.append($0) } return string } func fittedAttributedComponents() -> [NSAttributedString] { - guard let source = self.dataSource else { return [] } - var attributedPairs = self.attributedPairs(for: instruction, dataSource: source, imageRepository: imageRepository, onImageDownload: completeShieldDownload) + guard let source = dataSource else { return [] } + var attributedPairs = attributedPairs(for: instruction, dataSource: source, imageRepository: imageRepository, onImageDownload: completeShieldDownload) let availableBounds = source.availableBounds() let totalWidth: CGFloat = attributedPairs.attributedStrings.map { $0.size() }.reduce(.zero, +).width let stringFits = totalWidth <= availableBounds.width @@ -53,7 +52,7 @@ class InstructionPresenter { guard component.component.type == .text else { continue } guard let abbreviation = component.component.abbreviation else { continue } - attributedPairs.attributedStrings[component.index] = NSAttributedString(string: joinChar + abbreviation, attributes: attributes(for: source)) + attributedPairs.attributedStrings[component.index] = NSAttributedString(string: joinChar + abbreviation, attributes: self.attributes(for: source)) let newWidth: CGFloat = attributedPairs.attributedStrings.map { $0.size() }.reduce(.zero, +).width if newWidth <= availableBounds.width { @@ -71,41 +70,40 @@ class InstructionPresenter { var strings: [NSAttributedString] = [] var processedComponents: [VisualInstructionComponent] = [] - for (index, component) in components.enumerated() { let isFirst = index == 0 let joinChar = isFirst ? "" : " " let joinString = NSAttributedString(string: joinChar, attributes: attributes(for: dataSource)) let initial = NSAttributedString() - //This is the closure that builds the string. - let build: (_: VisualInstructionComponent, _: [NSAttributedString]) -> Void = { (component, attributedStrings) in + // This is the closure that builds the string. + let build: (_: VisualInstructionComponent, _: [NSAttributedString]) -> Void = { component, attributedStrings in processedComponents.append(component) strings.append(attributedStrings.reduce(initial, +)) } - let isShield: (_: VisualInstructionComponent?) -> Bool = { (component) in + let isShield: (_: VisualInstructionComponent?) -> Bool = { component in guard let key = component?.cacheKey else { return false } return imageRepository.cachedImageForKey(key) != nil } let componentBefore = components.component(before: component) - let componentAfter = components.component(after: component) + let componentAfter = components.component(after: component) switch component.type { - //Throw away exit components. We know this is safe because we know that if there is an exit component, + // Throw away exit components. We know this is safe because we know that if there is an exit component, // there is an exit code component, and the latter contains the information we care about. case .exit: continue - //If we have a exit, in the first two components, lets handle that. - case .exitCode where 0...1 ~= index: - guard let exitString = self.attributedString(forExitComponent: component, maneuverDirection: instruction.maneuverDirection, dataSource: dataSource) else { fallthrough } + // If we have a exit, in the first two components, lets handle that. + case .exitCode where 0 ... 1 ~= index: + guard let exitString = attributedString(forExitComponent: component, maneuverDirection: instruction.maneuverDirection, dataSource: dataSource) else { fallthrough } build(component, [exitString]) - //if it's a delimiter, skip it if it's between two shields. + // if it's a delimiter, skip it if it's between two shields. case .delimiter where isShield(componentBefore) && isShield(componentAfter): continue - //If we have an icon component, lets turn it into a shield. + // If we have an icon component, lets turn it into a shield. case .image: if let shieldString = attributedString(forShieldComponent: component, repository: imageRepository, dataSource: dataSource, onImageDownload: onImageDownload) { build(component, [joinString, shieldString]) @@ -115,7 +113,7 @@ class InstructionPresenter { fallthrough } - //Otherwise, process as text component. + // Otherwise, process as text component. default: guard let componentString = attributedString(forTextComponent: component, dataSource: dataSource) else { continue } build(component, [joinString, componentString]) @@ -135,27 +133,27 @@ class InstructionPresenter { func attributedString(forGenericShield component: VisualInstructionComponent, dataSource: DataSource) -> NSAttributedString? { guard component.type == .image, let text = component.text else { return nil } - return genericShield(text: text, component: component, dataSource: dataSource) + return self.genericShield(text: text, component: component, dataSource: dataSource) } - func attributedString(forShieldComponent shield: VisualInstructionComponent, repository:ImageRepository, dataSource: DataSource, onImageDownload: @escaping ImageDownloadCompletion) -> NSAttributedString? { + func attributedString(forShieldComponent shield: VisualInstructionComponent, repository: ImageRepository, dataSource: DataSource, onImageDownload: @escaping ImageDownloadCompletion) -> NSAttributedString? { guard shield.imageURL != nil, let shieldKey = shield.cacheKey else { return nil } - //If we have the shield already cached, use that. + // If we have the shield already cached, use that. if let cachedImage = repository.cachedImageForKey(shieldKey) { - return attributedString(withFont: dataSource.font, shieldImage: cachedImage) + return self.attributedString(withFont: dataSource.font, shieldImage: cachedImage) } // Let's download the shield - shieldImageForComponent(shield, in: repository, height: dataSource.shieldHeight, completion: onImageDownload) + self.shieldImageForComponent(shield, in: repository, height: dataSource.shieldHeight, completion: onImageDownload) - //Return nothing in the meantime, triggering downstream behavior (generic shield or text) + // Return nothing in the meantime, triggering downstream behavior (generic shield or text) return nil } func attributedString(forTextComponent component: VisualInstructionComponent, dataSource: DataSource) -> NSAttributedString? { guard let text = component.text else { return nil } - return NSAttributedString(string: text, attributes: attributes(for: dataSource)) + return NSAttributedString(string: text, attributes: self.attributes(for: dataSource)) } private func shieldImageForComponent(_ component: VisualInstructionComponent, in repository: ImageRepository, height: CGFloat, completion: @escaping ImageDownloadCompletion) { @@ -163,11 +161,11 @@ class InstructionPresenter { return } - repository.imageWithURL(imageURL, cacheKey: shieldKey, completion: completion ) + repository.imageWithURL(imageURL, cacheKey: shieldKey, completion: completion) } private func instructionHasDownloadedAllShields() -> Bool { - let textComponents = instruction.components.compactMap { $0 as? VisualInstructionComponent } + let textComponents = self.instruction.components.compactMap { $0 as? VisualInstructionComponent } guard !textComponents.isEmpty else { return false } for component in textComponents { @@ -175,7 +173,7 @@ class InstructionPresenter { continue } - if imageRepository.cachedImageForKey(key) == nil { + if self.imageRepository.cachedImageForKey(key) == nil { return false } } @@ -183,7 +181,7 @@ class InstructionPresenter { } private func attributes(for dataSource: InstructionPresenterDataSource) -> [NSAttributedString.Key: Any] { - return [.font: dataSource.font as Any, .foregroundColor: dataSource.textColor as Any] + [.font: dataSource.font as Any, .foregroundColor: dataSource.textColor as Any] } private func attributedString(withFont font: UIFont, shieldImage: UIImage) -> NSAttributedString { @@ -205,7 +203,7 @@ class InstructionPresenter { } else { let view = GenericRouteShield(pointSize: dataSource.font.pointSize, text: text) guard let image = takeSnapshot(on: view) else { return nil } - imageRepository.storeImage(image, forKey: key, toDisk: false) + self.imageRepository.storeImage(image, forKey: key, toDisk: false) attachment.image = image } @@ -217,7 +215,6 @@ class InstructionPresenter { private func exitShield(side: ExitSide = .right, text: String, component: VisualInstructionComponent, dataSource: DataSource) -> NSAttributedString? { guard let cacheKey = component.cacheKey else { return nil } - let additionalKey = ExitView.criticalHash(side: side, dataSource: dataSource) let attachment = ExitAttachment() @@ -227,7 +224,7 @@ class InstructionPresenter { } else { let view = ExitView(pointSize: dataSource.font.pointSize, side: side, text: text) guard let image = takeSnapshot(on: view) else { return nil } - imageRepository.storeImage(image, forKey: key, toDisk: false) + self.imageRepository.storeImage(image, forKey: key, toDisk: false) attachment.image = image } @@ -237,16 +234,15 @@ class InstructionPresenter { } private func completeShieldDownload(_ image: UIImage?) { - //We *must* be on main thread here, because attributedText() looks at object properties only accessible on main thread. + // We *must* be on main thread here, because attributedText() looks at object properties only accessible on main thread. DispatchQueue.main.async { - self.onShieldDownload?(self.attributedText()) //FIXME: Can we work with the image directly? + self.onShieldDownload?(self.attributedText()) // FIXME: Can we work with the image directly? } } private func takeSnapshot(on view: UIView) -> UIImage? { - return view.imageRepresentation + view.imageRepresentation } - } protocol ImagePresenter: TextPresenter { @@ -259,11 +255,11 @@ protocol TextPresenter { } class ImageInstruction: NSTextAttachment, ImagePresenter { - var font: UIFont = UIFont.systemFont(ofSize: UIFont.systemFontSize) + var font: UIFont = .systemFont(ofSize: UIFont.systemFontSize) var text: String? override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect { - guard let image = image else { + guard let image else { return super.attachmentBounds(for: textContainer, proposedLineFragment: lineFrag, glyphPosition: position, characterIndex: charIndex) } let yOrigin = (font.capHeight - image.size.height).rounded() / 2 @@ -280,7 +276,7 @@ class RoadNameLabelAttachment: TextInstruction { var color: UIColor? var compositeImage: UIImage? { - guard let image = image, let text = text, let color = color, let scale = scale else { + guard let image, let text, let color, let scale else { return nil } @@ -299,40 +295,40 @@ class RoadNameLabelAttachment: TextInstruction { self.text = text self.color = color self.scale = scale - self.image = compositeImage ?? image + self.image = self.compositeImage ?? image } } -extension CGSize { - fileprivate static var greatestFiniteSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) +private extension CGSize { + static var greatestFiniteSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) - fileprivate static func +(lhs: CGSize, rhs: CGSize) -> CGSize { - return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) + static func + (lhs: CGSize, rhs: CGSize) -> CGSize { + CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) } } -fileprivate struct IndexedVisualInstructionComponent { +private struct IndexedVisualInstructionComponent { let component: Array.Element let index: Array.Index } -extension Array where Element == VisualInstructionComponent { - fileprivate func component(before component: VisualInstructionComponent) -> VisualInstructionComponent? { - guard let index = self.firstIndex(of: component) else { +private extension [VisualInstructionComponent] { + func component(before component: VisualInstructionComponent) -> VisualInstructionComponent? { + guard let index = firstIndex(of: component) else { return nil } if index > 0 { - return self[index-1] + return self[index - 1] } return nil } - fileprivate func component(after component: VisualInstructionComponent) -> VisualInstructionComponent? { - guard let index = self.firstIndex(of: component) else { + func component(after component: VisualInstructionComponent) -> VisualInstructionComponent? { + guard let index = firstIndex(of: component) else { return nil } - if index+1 < self.endIndex { - return self[index+1] + if index + 1 < endIndex { + return self[index + 1] } return nil } diff --git a/MapboxNavigation/InstructionsBannerView.swift b/MapboxNavigation/InstructionsBannerView.swift index 10f13db85..59f4cb5db 100644 --- a/MapboxNavigation/InstructionsBannerView.swift +++ b/MapboxNavigation/InstructionsBannerView.swift @@ -1,21 +1,19 @@ import CoreLocation -import UIKit import MapboxCoreNavigation import MapboxDirections +import UIKit /** `InstructionsBannerViewDelegate` provides methods for reacting to user interactions in `InstructionsBannerView`. */ @objc(MBInstructionsBannerViewDelegate) public protocol InstructionsBannerViewDelegate: AnyObject { - /** Called when the user taps the `InstructionsBannerView`. */ @objc(didTapInstructionsBanner:) optional func didTapInstructionsBanner(_ sender: BaseInstructionsBannerView) - /** Called when the user drags either up or down on the `InstructionsBannerView`. */ @@ -26,11 +24,10 @@ public protocol InstructionsBannerViewDelegate: AnyObject { /// :nodoc: @IBDesignable @objc(MBInstructionsBannerView) -open class InstructionsBannerView: BaseInstructionsBannerView { } +open class InstructionsBannerView: BaseInstructionsBannerView {} /// :nodoc: open class BaseInstructionsBannerView: UIControl { - weak var maneuverView: ManeuverView! weak var primaryLabel: PrimaryLabel! weak var secondaryLabel: SecondaryLabel! @@ -41,14 +38,14 @@ open class BaseInstructionsBannerView: UIControl { weak var stepListIndicatorView: StepListIndicatorView! public weak var delegate: InstructionsBannerViewDelegate? { didSet { - stepListIndicatorView.isHidden = false + self.stepListIndicatorView.isHidden = false } } weak var instructionDelegate: VisualInstructionDelegate? { didSet { - primaryLabel.instructionDelegate = instructionDelegate - secondaryLabel.instructionDelegate = instructionDelegate + self.primaryLabel.instructionDelegate = self.instructionDelegate + self.secondaryLabel.instructionDelegate = self.instructionDelegate } } @@ -59,24 +56,24 @@ open class BaseInstructionsBannerView: UIControl { var distance: CLLocationDistance? { didSet { - distanceLabel.attributedDistanceString = nil + self.distanceLabel.attributedDistanceString = nil - if let distance = distance { - distanceLabel.attributedDistanceString = distanceFormatter.attributedString(for: distance) + if let distance { + self.distanceLabel.attributedDistanceString = self.distanceFormatter.attributedString(for: distance) } else { - distanceLabel.text = nil + self.distanceLabel.text = nil } } } override init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { @@ -84,19 +81,19 @@ open class BaseInstructionsBannerView: UIControl { setupLayout() centerYAlignInstructions() setupAvailableBounds() - stepListIndicatorView.isHidden = true + self.stepListIndicatorView.isHidden = true } @objc func draggedInstructionsBanner(_ sender: Any) { - if let gestureRecognizer = sender as? UIPanGestureRecognizer, gestureRecognizer.state == .ended, let delegate = delegate { - stepListIndicatorView.isHidden = !stepListIndicatorView.isHidden + if let gestureRecognizer = sender as? UIPanGestureRecognizer, gestureRecognizer.state == .ended, let delegate { + self.stepListIndicatorView.isHidden = !self.stepListIndicatorView.isHidden delegate.didDragInstructionsBanner?(self) } } @objc func tappedInstructionsBanner(_ sender: Any) { - if let delegate = delegate { - stepListIndicatorView.isHidden = !stepListIndicatorView.isHidden + if let delegate { + self.stepListIndicatorView.isHidden = !self.stepListIndicatorView.isHidden delegate.didTapInstructionsBanner?(self) } } @@ -107,7 +104,7 @@ open class BaseInstructionsBannerView: UIControl { @objc(updateForVisualInstructionBanner:) public func update(for instruction: VisualInstructionBanner?) { let secondaryInstruction = instruction?.secondaryInstruction - primaryLabel.numberOfLines = secondaryInstruction == nil ? 2 : 1 + self.primaryLabel.numberOfLines = secondaryInstruction == nil ? 2 : 1 if secondaryInstruction == nil { centerYAlignInstructions() @@ -115,20 +112,20 @@ open class BaseInstructionsBannerView: UIControl { baselineAlignInstructions() } - primaryLabel.instruction = instruction?.primaryInstruction - maneuverView.visualInstruction = instruction?.primaryInstruction - maneuverView.drivingSide = instruction?.drivingSide ?? .right - secondaryLabel.instruction = secondaryInstruction + self.primaryLabel.instruction = instruction?.primaryInstruction + self.maneuverView.visualInstruction = instruction?.primaryInstruction + self.maneuverView.drivingSide = instruction?.drivingSide ?? .right + self.secondaryLabel.instruction = secondaryInstruction } override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() - maneuverView.isStart = true + self.maneuverView.isStart = true let component = VisualInstructionComponent(type: .text, text: "Primary text label", imageURL: nil, abbreviation: nil, abbreviationPriority: NSNotFound) let instruction = VisualInstruction(text: nil, maneuverType: .none, maneuverDirection: .none, components: [component]) - primaryLabel.instruction = instruction + self.primaryLabel.instruction = instruction - distance = 100 + self.distance = 100 } /** @@ -136,6 +133,6 @@ open class BaseInstructionsBannerView: UIControl { */ public func updateDistance(for currentStepProgress: RouteStepProgress) { let distanceRemaining = currentStepProgress.distanceRemaining - distance = distanceRemaining > 5 ? distanceRemaining : 0 + self.distance = distanceRemaining > 5 ? distanceRemaining : 0 } } diff --git a/MapboxNavigation/InstructionsBannerViewLayout.swift b/MapboxNavigation/InstructionsBannerViewLayout.swift index d87f66d4d..5e8d41636 100644 --- a/MapboxNavigation/InstructionsBannerViewLayout.swift +++ b/MapboxNavigation/InstructionsBannerViewLayout.swift @@ -1,7 +1,6 @@ import UIKit extension BaseInstructionsBannerView { - static let padding: CGFloat = 16 static let maneuverViewSize = CGSize(width: 38, height: 38) static let stepListIndicatorViewSize = CGSize(width: 30, height: 5) @@ -88,7 +87,7 @@ extension BaseInstructionsBannerView { // Primary Label primaryLabel.leadingAnchor.constraint(equalTo: dividerView.trailingAnchor).isActive = true primaryLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -18).isActive = true - baselineConstraints.append(primaryLabel.topAnchor.constraint(equalTo: maneuverView.topAnchor, constant: -BaseInstructionsBannerView.padding/2)) + baselineConstraints.append(primaryLabel.topAnchor.constraint(equalTo: maneuverView.topAnchor, constant: -BaseInstructionsBannerView.padding / 2)) centerYConstraints.append(primaryLabel.centerYAnchor.constraint(equalTo: centerYAnchor)) // Secondary Label @@ -143,14 +142,14 @@ extension BaseInstructionsBannerView { // Abbreviate if the instructions do not fit on one line primaryLabel.availableBounds = { [unowned self] in // Available width H:|-padding-maneuverView-padding-availableWidth-padding-| - let availableWidth = self.primaryLabel.viewForAvailableBoundsCalculation?.bounds.width ?? self.bounds.width - BaseInstructionsBannerView.maneuverViewSize.width - BaseInstructionsBannerView.padding * 3 - return CGRect(x: 0, y: 0, width: availableWidth, height: self.primaryLabel.font.lineHeight) + let availableWidth = primaryLabel.viewForAvailableBoundsCalculation?.bounds.width ?? bounds.width - BaseInstructionsBannerView.maneuverViewSize.width - BaseInstructionsBannerView.padding * 3 + return CGRect(x: 0, y: 0, width: availableWidth, height: primaryLabel.font.lineHeight) } secondaryLabel.availableBounds = { [unowned self] in // Available width H:|-padding-maneuverView-padding-availableWidth-padding-| - let availableWidth = self.secondaryLabel.viewForAvailableBoundsCalculation?.bounds.width ?? self.bounds.width - BaseInstructionsBannerView.maneuverViewSize.width - BaseInstructionsBannerView.padding * 3 - return CGRect(x: 0, y: 0, width: availableWidth, height: self.secondaryLabel.font.lineHeight) + let availableWidth = secondaryLabel.viewForAvailableBoundsCalculation?.bounds.width ?? bounds.width - BaseInstructionsBannerView.maneuverViewSize.width - BaseInstructionsBannerView.padding * 3 + return CGRect(x: 0, y: 0, width: availableWidth, height: secondaryLabel.font.lineHeight) } } } diff --git a/MapboxNavigation/LaneView.swift b/MapboxNavigation/LaneView.swift index 1f245de3a..55783c472 100644 --- a/MapboxNavigation/LaneView.swift +++ b/MapboxNavigation/LaneView.swift @@ -1,5 +1,5 @@ -import UIKit import MapboxDirections +import UIKit /// :nodoc: @objc(MBLaneView) @@ -13,7 +13,7 @@ open class LaneView: UIView { var isValid: Bool = false override open var intrinsicContentSize: CGSize { - return bounds.size + bounds.size } @objc public dynamic var primaryColor: UIColor = .defaultLaneArrowPrimary { @@ -29,138 +29,138 @@ open class LaneView: UIView { } var appropriatePrimaryColor: UIColor { - return isValid ? primaryColor : secondaryColor + self.isValid ? self.primaryColor : self.secondaryColor } - static let defaultFrame: CGRect = CGRect(origin: .zero, size: 30.0) + static let defaultFrame: CGRect = .init(origin: .zero, size: 30.0) convenience init(component: LaneIndicationComponent) { self.init(frame: LaneView.defaultFrame) backgroundColor = .clear - lane = Lane(indications: component.indications) - maneuverDirection = ManeuverDirection(description: component.indications.description) - isValid = component.isUsable + self.lane = Lane(indications: component.indications) + self.maneuverDirection = ManeuverDirection(description: component.indications.description) + self.isValid = component.isUsable } override open func draw(_ rect: CGRect) { super.draw(rect) - if let lane = lane { + if let lane { var flipLane: Bool if lane.indications.isSuperset(of: [.straightAhead, .sharpRight]) || lane.indications.isSuperset(of: [.straightAhead, .right]) || lane.indications.isSuperset(of: [.straightAhead, .slightRight]) { flipLane = false - if !isValid { + if !self.isValid { if lane.indications == .slightRight { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) } else { - LanesStyleKit.drawLane_straight_right(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_straight_right(primaryColor: self.appropriatePrimaryColor) } - alpha = invalidAlpha - } else if maneuverDirection == .straightAhead { - LanesStyleKit.drawLane_straight_only(primaryColor: appropriatePrimaryColor, secondaryColor: secondaryColor) - } else if maneuverDirection == .sharpLeft || maneuverDirection == .left || maneuverDirection == .slightLeft { + alpha = self.invalidAlpha + } else if self.maneuverDirection == .straightAhead { + LanesStyleKit.drawLane_straight_only(primaryColor: self.appropriatePrimaryColor, secondaryColor: self.secondaryColor) + } else if self.maneuverDirection == .sharpLeft || self.maneuverDirection == .left || self.maneuverDirection == .slightLeft { if lane.indications == .slightLeft { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) } else { - LanesStyleKit.drawLane_right_h(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_right_h(primaryColor: self.appropriatePrimaryColor) } flipLane = true } else { - LanesStyleKit.drawLane_right_only(primaryColor: appropriatePrimaryColor, secondaryColor: secondaryColor) + LanesStyleKit.drawLane_right_only(primaryColor: self.appropriatePrimaryColor, secondaryColor: self.secondaryColor) } } else if lane.indications.isSuperset(of: [.straightAhead, .sharpLeft]) || lane.indications.isSuperset(of: [.straightAhead, .left]) || lane.indications.isSuperset(of: [.straightAhead, .slightLeft]) { flipLane = true - if !isValid { + if !self.isValid { if lane.indications == .slightLeft { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) } else { - LanesStyleKit.drawLane_straight_right(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_straight_right(primaryColor: self.appropriatePrimaryColor) } - alpha = invalidAlpha - } else if maneuverDirection == .straightAhead { - LanesStyleKit.drawLane_straight_only(primaryColor: appropriatePrimaryColor, secondaryColor: secondaryColor) - } else if maneuverDirection == .sharpRight || maneuverDirection == .right { - LanesStyleKit.drawLane_right_h(primaryColor: appropriatePrimaryColor) + alpha = self.invalidAlpha + } else if self.maneuverDirection == .straightAhead { + LanesStyleKit.drawLane_straight_only(primaryColor: self.appropriatePrimaryColor, secondaryColor: self.secondaryColor) + } else if self.maneuverDirection == .sharpRight || self.maneuverDirection == .right { + LanesStyleKit.drawLane_right_h(primaryColor: self.appropriatePrimaryColor) flipLane = false - } else if maneuverDirection == .slightRight { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + } else if self.maneuverDirection == .slightRight { + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) flipLane = false } else { - LanesStyleKit.drawLane_right_only(primaryColor: appropriatePrimaryColor, secondaryColor: secondaryColor) + LanesStyleKit.drawLane_right_only(primaryColor: self.appropriatePrimaryColor, secondaryColor: self.secondaryColor) } } else if lane.indications.description.components(separatedBy: ",").count >= 2 { // Hack: // Account for a configuation where there is no straight lane // but there are at least 2 indications. // In this situation, just draw a left/right arrow - if maneuverDirection == .sharpRight || maneuverDirection == .right { - LanesStyleKit.drawLane_right_h(primaryColor: appropriatePrimaryColor) + if self.maneuverDirection == .sharpRight || self.maneuverDirection == .right { + LanesStyleKit.drawLane_right_h(primaryColor: self.appropriatePrimaryColor) flipLane = false - } else if maneuverDirection == .slightRight { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + } else if self.maneuverDirection == .slightRight { + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) flipLane = false } else { - LanesStyleKit.drawLane_right_h(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_right_h(primaryColor: self.appropriatePrimaryColor) flipLane = true } - alpha = isValid ? 1 : invalidAlpha + alpha = self.isValid ? 1 : self.invalidAlpha } else if lane.indications.isSuperset(of: [.sharpRight]) || lane.indications.isSuperset(of: [.right]) || lane.indications.isSuperset(of: [.slightRight]) { if lane.indications == .slightRight { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) } else { - LanesStyleKit.drawLane_right_h(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_right_h(primaryColor: self.appropriatePrimaryColor) } flipLane = false - alpha = isValid ? 1 : invalidAlpha + alpha = self.isValid ? 1 : self.invalidAlpha } else if lane.indications.isSuperset(of: [.sharpLeft]) || lane.indications.isSuperset(of: [.left]) || lane.indications.isSuperset(of: [.slightLeft]) { if lane.indications == .slightLeft { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) } else { - LanesStyleKit.drawLane_right_h(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_right_h(primaryColor: self.appropriatePrimaryColor) } flipLane = true - alpha = isValid ? 1 : invalidAlpha + alpha = self.isValid ? 1 : self.invalidAlpha } else if lane.indications.isSuperset(of: [.straightAhead]) { - LanesStyleKit.drawLane_straight(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_straight(primaryColor: self.appropriatePrimaryColor) flipLane = false - alpha = isValid ? 1 : invalidAlpha + alpha = self.isValid ? 1 : self.invalidAlpha } else if lane.indications.isSuperset(of: [.uTurn]) { - LanesStyleKit.drawLane_uturn(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_uturn(primaryColor: self.appropriatePrimaryColor) flipLane = false - alpha = isValid ? 1 : invalidAlpha - } else if lane.indications.isEmpty && isValid { + alpha = self.isValid ? 1 : self.invalidAlpha + } else if lane.indications.isEmpty, self.isValid { // If the lane indication is `none` and the maneuver modifier has a turn in it, // show the turn in the lane image. - if maneuverDirection == .sharpRight || maneuverDirection == .right || maneuverDirection == .slightRight { - if maneuverDirection == .slightRight { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + if self.maneuverDirection == .sharpRight || self.maneuverDirection == .right || self.maneuverDirection == .slightRight { + if self.maneuverDirection == .slightRight { + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) } else { - LanesStyleKit.drawLane_right_h(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_right_h(primaryColor: self.appropriatePrimaryColor) } flipLane = false - } else if maneuverDirection == .sharpLeft || maneuverDirection == .left || maneuverDirection == .slightLeft { - if maneuverDirection == .slightLeft { - LanesStyleKit.drawLane_slight_right(primaryColor: appropriatePrimaryColor) + } else if self.maneuverDirection == .sharpLeft || self.maneuverDirection == .left || self.maneuverDirection == .slightLeft { + if self.maneuverDirection == .slightLeft { + LanesStyleKit.drawLane_slight_right(primaryColor: self.appropriatePrimaryColor) } else { - LanesStyleKit.drawLane_right_h(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_right_h(primaryColor: self.appropriatePrimaryColor) } flipLane = true } else { - LanesStyleKit.drawLane_straight(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_straight(primaryColor: self.appropriatePrimaryColor) flipLane = false } } else { - LanesStyleKit.drawLane_straight(primaryColor: appropriatePrimaryColor) + LanesStyleKit.drawLane_straight(primaryColor: self.appropriatePrimaryColor) flipLane = false - alpha = isValid ? 1 : invalidAlpha + alpha = self.isValid ? 1 : self.invalidAlpha } transform = CGAffineTransform(scaleX: flipLane ? -1 : 1, y: 1) } #if TARGET_INTERFACE_BUILDER - isValid = true - LanesStyleKit.drawLane_right_only(primaryColor: appropriatePrimaryColor, secondaryColor: secondaryColor) + self.isValid = true + LanesStyleKit.drawLane_right_only(primaryColor: self.appropriatePrimaryColor, secondaryColor: self.secondaryColor) #endif } } diff --git a/MapboxNavigation/LanesStyleKit.swift b/MapboxNavigation/LanesStyleKit.swift index 2e1215a1f..c8c73eaf2 100644 --- a/MapboxNavigation/LanesStyleKit.swift +++ b/MapboxNavigation/LanesStyleKit.swift @@ -2,11 +2,9 @@ import UIKit @objc(MBLanesStyleKit) public class LanesStyleKit: NSObject { - //// Drawing Methods - @objc dynamic public class func drawLane_straight_right(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { - + @objc public dynamic class func drawLane_straight_right(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { //// Rectangle Drawing let rectanglePath = UIBezierPath(rect: CGRect(x: 9, y: 11.5, width: 4, height: 15.5)) primaryColor.setFill() @@ -82,8 +80,7 @@ public class LanesStyleKit: NSObject { bezier3Path.stroke() } - @objc dynamic public class func drawLane_straight_only(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000)) { - + @objc public dynamic class func drawLane_straight_only(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000)) { //// Bezier 2 Drawing let bezier2Path = UIBezierPath() bezier2Path.move(to: CGPoint(x: 18.05, y: 14.59)) @@ -159,8 +156,7 @@ public class LanesStyleKit: NSObject { bezierPath.fill() } - @objc dynamic public class func drawLane_right_h(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { - + @objc public dynamic class func drawLane_right_h(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { //// Bezier Drawing let bezierPath = UIBezierPath() bezierPath.move(to: CGPoint(x: 16.46, y: 4)) @@ -197,8 +193,7 @@ public class LanesStyleKit: NSObject { bezier2Path.stroke() } - @objc dynamic public class func drawLane_right_only(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000)) { - + @objc public dynamic class func drawLane_right_only(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000)) { //// Rectangle Drawing let rectanglePath = UIBezierPath(rect: CGRect(x: 9, y: 11, width: 4, height: 16)) secondaryColor.setFill() @@ -274,8 +269,7 @@ public class LanesStyleKit: NSObject { bezier3Path.stroke() } - @objc dynamic public class func drawLane_straight(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { - + @objc public dynamic class func drawLane_straight(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { //// Rectangle Drawing let rectanglePath = UIBezierPath(rect: CGRect(x: 13, y: 11, width: 4, height: 16)) primaryColor.setFill() @@ -315,8 +309,7 @@ public class LanesStyleKit: NSObject { bezierPath.fill() } - @objc dynamic public class func drawLane_uturn(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { - + @objc public dynamic class func drawLane_uturn(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { //// Bezier Drawing let bezierPath = UIBezierPath() bezierPath.move(to: CGPoint(x: 19, y: 20)) @@ -356,7 +349,7 @@ public class LanesStyleKit: NSObject { bezier2Path.fill() } - @objc dynamic public class func drawLane_slight_right(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), scale: CGFloat = 1) { + @objc public dynamic class func drawLane_slight_right(primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), scale: CGFloat = 1) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -379,7 +372,7 @@ public class LanesStyleKit: NSObject { //// Bezier Drawing context.saveGState() context.translateBy(x: -2.25, y: -11.1) - context.rotate(by: 49 * CGFloat.pi/180) + context.rotate(by: 49 * CGFloat.pi / 180) let bezierPath = UIBezierPath() bezierPath.move(to: CGPoint(x: 4.01, y: 9.92)) @@ -417,5 +410,4 @@ public class LanesStyleKit: NSObject { context.restoreGState() } - } diff --git a/MapboxNavigation/LanesView.swift b/MapboxNavigation/LanesView.swift index 8c4a9b45b..38fb2b5ac 100644 --- a/MapboxNavigation/LanesView.swift +++ b/MapboxNavigation/LanesView.swift @@ -1,6 +1,6 @@ -import UIKit import MapboxCoreNavigation import MapboxDirections +import UIKit /// :nodoc: @IBDesignable @@ -11,20 +11,20 @@ open class LanesView: UIView { override init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } - open override func prepareForInterfaceBuilder() { + override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() - for _ in 0...4 { - let laneView = laneArrowView() - stackView.addArrangedSubview(laneView) + for _ in 0 ... 4 { + let laneView = self.laneArrowView() + self.stackView.addArrangedSubview(laneView) } } @@ -71,24 +71,24 @@ open class LanesView: UIView { */ @objc(updateForVisualInstructionBanner:) public func update(for visualInstruction: VisualInstructionBanner?) { - clearLaneViews() + self.clearLaneViews() guard let tertiaryInstruction = visualInstruction?.tertiaryInstruction, - tertiaryInstruction.containsLaneIndications else { - hide() - return + tertiaryInstruction.containsLaneIndications else { + self.hide() + return } - let laneIndications: [LaneIndicationComponent]? = tertiaryInstruction.components.compactMap({ $0 as? LaneIndicationComponent }) + let laneIndications: [LaneIndicationComponent]? = tertiaryInstruction.components.compactMap { $0 as? LaneIndicationComponent } guard let lanes = laneIndications, !lanes.isEmpty else { - hide() + self.hide() return } let subviews = lanes.map { LaneView(component: $0) } - stackView.addArrangedSubviews(subviews) - show() + self.stackView.addArrangedSubviews(subviews) + self.show() } public func show(animated: Bool = true) { @@ -98,7 +98,7 @@ open class LanesView: UIView { self.isHidden = false }, completion: nil) } else { - self.isHidden = false + isHidden = false } } @@ -110,10 +110,9 @@ open class LanesView: UIView { } fileprivate func clearLaneViews() { - stackView.arrangedSubviews.forEach { - stackView.removeArrangedSubview($0) - $0.removeFromSuperview() + for arrangedSubview in self.stackView.arrangedSubviews { + self.stackView.removeArrangedSubview(arrangedSubview) + arrangedSubview.removeFromSuperview() } } - } diff --git a/MapboxNavigation/Locale.swift b/MapboxNavigation/Locale.swift index 9d6c59fe5..d1613f6bd 100644 --- a/MapboxNavigation/Locale.swift +++ b/MapboxNavigation/Locale.swift @@ -1,12 +1,10 @@ import Foundation - extension Locale { - // Returns Locale.autoupdatingCurrent unless its languageCode is equal to `en`, then nil will be returned. static var autoupdatingNonEnglish: Locale? { if let languageCode = Locale.autoupdatingCurrent.languageCode, - languageCode == "en" { + languageCode == "en" { return nil } diff --git a/MapboxNavigation/MLNMapView.swift b/MapboxNavigation/MLNMapView.swift index a97bdafb8..755e79c75 100644 --- a/MapboxNavigation/MLNMapView.swift +++ b/MapboxNavigation/MLNMapView.swift @@ -1,7 +1,7 @@ import Foundation -import MapLibre -import MapboxDirections import MapboxCoreNavigation +import MapboxDirections +import MapLibre /** An extension on `MLNMapView` that allows for toggling traffic on a map style that contains a [Mapbox Traffic source](https://www.mapbox.com/vector-tiles/mapbox-traffic-v1/). @@ -11,16 +11,14 @@ extension MLNMapView { Returns a set of source identifiers for tilesets that are or include the Mapbox Incidents source. */ func sourceIdentifiers(forTileSetIdentifier tileSetIdentifier: String) -> Set { - guard let style = style else { + guard let style else { return [] } return Set(style.sources.compactMap { $0 as? MLNVectorTileSource }.filter { $0.configurationURL?.host?.components(separatedBy: ",").contains(tileSetIdentifier) ?? false - }.map { - $0.identifier - }) + }.map(\.identifier)) } /** @@ -30,14 +28,14 @@ extension MLNMapView { - parameter layerIdentifier: Identifier of the layer in the tile set; in other words, a source layer identifier. Not to be confused with a style layer. */ func showsTileSet(withIdentifier tileSetIdentifier: String, layerIdentifier: String) -> Bool { - guard let style = style else { + guard let style else { return false } - let incidentsSourceIdentifiers = sourceIdentifiers(forTileSetIdentifier: tileSetIdentifier) + let incidentsSourceIdentifiers = self.sourceIdentifiers(forTileSetIdentifier: tileSetIdentifier) for layer in style.layers { if let layer = layer as? MLNVectorStyleLayer, let sourceIdentifier = layer.sourceIdentifier { - if incidentsSourceIdentifiers.contains(sourceIdentifier) && layer.sourceLayerIdentifier == layerIdentifier { + if incidentsSourceIdentifiers.contains(sourceIdentifier), layer.sourceLayerIdentifier == layerIdentifier { return layer.isVisible } } @@ -52,14 +50,14 @@ extension MLNMapView { - parameter layerIdentifier: Identifier of the layer in the tile set; in other words, a source layer identifier. Not to be confused with a style layer. */ func setShowsTileSet(_ isVisible: Bool, withIdentifier tileSetIdentifier: String, layerIdentifier: String) { - guard let style = style else { + guard let style else { return } - let incidentsSourceIdentifiers = sourceIdentifiers(forTileSetIdentifier: tileSetIdentifier) + let incidentsSourceIdentifiers = self.sourceIdentifiers(forTileSetIdentifier: tileSetIdentifier) for layer in style.layers { if let layer = layer as? MLNVectorStyleLayer, let sourceIdentifier = layer.sourceIdentifier { - if incidentsSourceIdentifiers.contains(sourceIdentifier) && layer.sourceLayerIdentifier == layerIdentifier { + if incidentsSourceIdentifiers.contains(sourceIdentifier), layer.sourceLayerIdentifier == layerIdentifier { layer.isVisible = isVisible } } @@ -71,10 +69,10 @@ extension MLNMapView { */ public var showsTraffic: Bool { get { - return showsTileSet(withIdentifier: "mapbox.mapbox-traffic-v1", layerIdentifier: "traffic") + self.showsTileSet(withIdentifier: "mapbox.mapbox-traffic-v1", layerIdentifier: "traffic") } set { - setShowsTileSet(newValue, withIdentifier: "mapbox.mapbox-traffic-v1", layerIdentifier: "traffic") + self.setShowsTileSet(newValue, withIdentifier: "mapbox.mapbox-traffic-v1", layerIdentifier: "traffic") } } @@ -83,10 +81,10 @@ extension MLNMapView { */ public var showsIncidents: Bool { get { - return showsTileSet(withIdentifier: "mapbox.mapbox-incidents-v1", layerIdentifier: "closures") + self.showsTileSet(withIdentifier: "mapbox.mapbox-incidents-v1", layerIdentifier: "closures") } set { - setShowsTileSet(newValue, withIdentifier: "mapbox.mapbox-incidents-v1", layerIdentifier: "closures") + self.setShowsTileSet(newValue, withIdentifier: "mapbox.mapbox-incidents-v1", layerIdentifier: "closures") } } } diff --git a/MapboxNavigation/MLNStyle.swift b/MapboxNavigation/MLNStyle.swift index 6d1d5987a..2bc900562 100644 --- a/MapboxNavigation/MLNStyle.swift +++ b/MapboxNavigation/MLNStyle.swift @@ -1,31 +1,25 @@ import Foundation import MapLibre - -extension MLNStyle { - +public extension MLNStyle { // The Mapbox China Day Style URL. - static let mapboxChinaDayStyleURL = URL(string:"mapbox://styles/mapbox/streets-zh-v1")! + internal static let mapboxChinaDayStyleURL = URL(string: "mapbox://styles/mapbox/streets-zh-v1")! // The Mapbox China Night Style URL. - static let mapboxChinaNightStyleURL = URL(string:"mapbox://styles/mapbox/dark-zh-v1")! + internal static let mapboxChinaNightStyleURL = URL(string: "mapbox://styles/mapbox/dark-zh-v1")! /** Returns the URL to the current version of the Mapbox Navigation Guidance Day style. */ - @objc public class var navigationGuidanceDayStyleURL: URL { - get { - return URL(string:"mapbox://styles/mapbox/navigation-guidance-day-v4")! - } + @objc class var navigationGuidanceDayStyleURL: URL { + URL(string: "mapbox://styles/mapbox/navigation-guidance-day-v4")! } /** Returns the URL to the current version of the Mapbox Navigation Guidance Night style. */ - @objc public class var navigationGuidanceNightStyleURL: URL { - get { - return URL(string:"mapbox://styles/mapbox/navigation-guidance-night-v4")! - } + @objc class var navigationGuidanceNightStyleURL: URL { + URL(string: "mapbox://styles/mapbox/navigation-guidance-night-v4")! } /** @@ -33,36 +27,31 @@ extension MLNStyle { We only have one version of navigation guidance style in China, so if you switch your endpoint to .cn, it will return the default day style. */ - @objc public class func navigationGuidanceDayStyleURL(version: Int) -> URL { - return URL(string:"mapbox://styles/mapbox/navigation-guidance-day-v\(version)")! + @objc class func navigationGuidanceDayStyleURL(version: Int) -> URL { + URL(string: "mapbox://styles/mapbox/navigation-guidance-day-v\(version)")! } - /** Returns the URL to the given version of the navigation guidance style. Available version are 2, 3, and 4. We only have one version of navigation guidance style in China, so if you switch your endpoint to .cn, it will return the default night style. */ - @objc public class func navigationGuidanceNightStyleURL(version: Int) -> URL { - return URL(string: "mapbox://styles/mapbox/navigation-guidance-night-v\(version)")! + @objc class func navigationGuidanceNightStyleURL(version: Int) -> URL { + URL(string: "mapbox://styles/mapbox/navigation-guidance-night-v\(version)")! } /** Returns the URL to the current version of the Mapbox Navigation Preview Day style. */ - @objc public class var navigationPreviewDayStyleURL: URL { - get { - return URL(string:"mapbox://styles/mapbox/navigation-preview-day-v4")! - } + @objc class var navigationPreviewDayStyleURL: URL { + URL(string: "mapbox://styles/mapbox/navigation-preview-day-v4")! } /** Returns the URL to the current version of the Mapbox Navigation Preview Night style. */ - @objc public class var navigationPreviewNightStyleURL: URL { - get { - return URL(string:"mapbox://styles/mapbox/navigation-preview-night-v4")! - } + @objc class var navigationPreviewNightStyleURL: URL { + URL(string: "mapbox://styles/mapbox/navigation-preview-night-v4")! } /** @@ -70,17 +59,16 @@ extension MLNStyle { We only have one version of Navigation Preview style in China, so if you switch your endpoint to .cn, it will return the default day style. */ - @objc public class func navigationPreviewDayStyleURL(version: Int) -> URL { - return URL(string:"mapbox://styles/mapbox/navigation-guidance-day-v\(version)")! + @objc class func navigationPreviewDayStyleURL(version: Int) -> URL { + URL(string: "mapbox://styles/mapbox/navigation-guidance-day-v\(version)")! } - /** Returns the URL to the given version of the Mapbox Navigation Preview Night style. Available versions are 2, 3, and 4. We only have one version of Navigation Preview style in China, so if you switch your endpoint to .cn, it will return the default night style. */ - @objc public class func navigationPreviewNightStyleURL(version: Int) -> URL { - return URL(string: "mapbox://styles/mapbox/navigation-guidance-night-v\(version)")! + @objc class func navigationPreviewNightStyleURL(version: Int) -> URL { + URL(string: "mapbox://styles/mapbox/navigation-guidance-night-v\(version)")! } } diff --git a/MapboxNavigation/MLNVectorTileSource.swift b/MapboxNavigation/MLNVectorTileSource.swift index 4550b41fc..1c5f76199 100644 --- a/MapboxNavigation/MLNVectorTileSource.swift +++ b/MapboxNavigation/MLNVectorTileSource.swift @@ -3,7 +3,7 @@ import MapLibre extension MLNVectorTileSource { var isMapboxStreets: Bool { - guard let configurationURL = configurationURL else { + guard let configurationURL else { return false } return configurationURL.scheme == "mapbox" && configurationURL.host!.components(separatedBy: ",").contains("mapbox.mapbox-streets-v7") diff --git a/MapboxNavigation/ManeuverDirection.swift b/MapboxNavigation/ManeuverDirection.swift index f8cc9d8f2..16cecf4c7 100644 --- a/MapboxNavigation/ManeuverDirection.swift +++ b/MapboxNavigation/ManeuverDirection.swift @@ -3,24 +3,23 @@ import MapboxDirections extension ManeuverDirection { init(angle: Int) { - var description: String - switch angle { - case -30..<30: - description = "straight" - case 30..<60: - description = "slight right" - case 60..<150: - description = "right" - case 150..<180: - description = "sharp right" - case -180..<(-150): - description = "sharp left" - case -150..<(-60): - description = "left" - case -50..<(-30): - description = "slight left" + let description = switch angle { + case -30 ..< 30: + "straight" + case 30 ..< 60: + "slight right" + case 60 ..< 150: + "right" + case 150 ..< 180: + "sharp right" + case -180 ..< -150: + "sharp left" + case -150 ..< -60: + "left" + case -50 ..< -30: + "slight left" default: - description = "straight" + "straight" } self.init(description: description)! } diff --git a/MapboxNavigation/ManeuverView.swift b/MapboxNavigation/ManeuverView.swift index 8104ad5bd..878c35461 100644 --- a/MapboxNavigation/ManeuverView.swift +++ b/MapboxNavigation/ManeuverView.swift @@ -1,13 +1,12 @@ -import UIKit -import MapboxDirections import MapboxCoreNavigation +import MapboxDirections import Turf +import UIKit /// :nodoc: @IBDesignable @objc(MBManeuverView) open class ManeuverView: UIView { - @objc public dynamic var primaryColor: UIColor = .defaultTurnArrowPrimary { didSet { setNeedsDisplay() @@ -64,20 +63,20 @@ open class ManeuverView: UIView { let resizing: ManeuversStyleKit.ResizingBehavior = .aspectFit #if TARGET_INTERFACE_BUILDER - ManeuversStyleKit.drawFork(frame: bounds, resizing: resizing, primaryColor: primaryColor, secondaryColor: secondaryColor) - return + ManeuversStyleKit.drawFork(frame: bounds, resizing: resizing, primaryColor: self.primaryColor, secondaryColor: self.secondaryColor) + return #endif - guard let visualInstruction = visualInstruction else { - if isStart { - ManeuversStyleKit.drawStarting(frame: bounds, resizing: resizing, primaryColor: primaryColor) - } else if isEnd { - ManeuversStyleKit.drawDestination(frame: bounds, resizing: resizing, primaryColor: primaryColor) + guard let visualInstruction else { + if self.isStart { + ManeuversStyleKit.drawStarting(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) + } else if self.isEnd { + ManeuversStyleKit.drawDestination(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) } return } - var flip: Bool = false + var flip = false let maneuverType = visualInstruction.maneuverType let maneuverDirection = visualInstruction.maneuverDirection @@ -86,53 +85,53 @@ open class ManeuverView: UIView { switch type { case .merge: - ManeuversStyleKit.drawMerge(frame: bounds, resizing: resizing, primaryColor: primaryColor, secondaryColor: secondaryColor) + ManeuversStyleKit.drawMerge(frame: bounds, resizing: resizing, primaryColor: self.primaryColor, secondaryColor: self.secondaryColor) flip = [.left, .slightLeft, .sharpLeft].contains(direction) case .takeOffRamp: - ManeuversStyleKit.drawOfframp(frame: bounds, resizing: resizing, primaryColor: primaryColor, secondaryColor: secondaryColor) + ManeuversStyleKit.drawOfframp(frame: bounds, resizing: resizing, primaryColor: self.primaryColor, secondaryColor: self.secondaryColor) flip = [.left, .slightLeft, .sharpLeft].contains(direction) case .reachFork: - ManeuversStyleKit.drawFork(frame: bounds, resizing: resizing, primaryColor: primaryColor, secondaryColor: secondaryColor) + ManeuversStyleKit.drawFork(frame: bounds, resizing: resizing, primaryColor: self.primaryColor, secondaryColor: self.secondaryColor) flip = [.left, .slightLeft, .sharpLeft].contains(direction) case .takeRoundabout, .turnAtRoundabout, .takeRotary: - ManeuversStyleKit.drawRoundabout(frame: bounds, resizing: resizing, primaryColor: primaryColor, secondaryColor: secondaryColor, roundabout_angle: CGFloat(visualInstruction.finalHeading)) - flip = drivingSide == .left + ManeuversStyleKit.drawRoundabout(frame: bounds, resizing: resizing, primaryColor: self.primaryColor, secondaryColor: self.secondaryColor, roundabout_angle: CGFloat(visualInstruction.finalHeading)) + flip = self.drivingSide == .left case .arrive: switch direction { case .right: - ManeuversStyleKit.drawArriveright(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArriveright(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) case .left: - ManeuversStyleKit.drawArriveright(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArriveright(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) flip = true default: - ManeuversStyleKit.drawArrive(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArrive(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) } default: switch direction { case .right: - ManeuversStyleKit.drawArrowright(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArrowright(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) flip = false case .slightRight: - ManeuversStyleKit.drawArrowslightright(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArrowslightright(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) flip = false case .sharpRight: - ManeuversStyleKit.drawArrowsharpright(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArrowsharpright(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) flip = false case .left: - ManeuversStyleKit.drawArrowright(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArrowright(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) flip = true case .slightLeft: - ManeuversStyleKit.drawArrowslightright(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArrowslightright(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) flip = true case .sharpLeft: - ManeuversStyleKit.drawArrowsharpright(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArrowsharpright(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) flip = true case .uTurn: - ManeuversStyleKit.drawArrow180right(frame: bounds, resizing: resizing, primaryColor: primaryColor) - flip = drivingSide == .left + ManeuversStyleKit.drawArrow180right(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) + flip = self.drivingSide == .left default: - ManeuversStyleKit.drawArrowstraight(frame: bounds, resizing: resizing, primaryColor: primaryColor) + ManeuversStyleKit.drawArrowstraight(frame: bounds, resizing: resizing, primaryColor: self.primaryColor) } } diff --git a/MapboxNavigation/ManeuversStyleKit.swift b/MapboxNavigation/ManeuversStyleKit.swift index 5302bb1fc..1d8f99c83 100644 --- a/MapboxNavigation/ManeuversStyleKit.swift +++ b/MapboxNavigation/ManeuversStyleKit.swift @@ -2,10 +2,9 @@ import UIKit @objc(MBManeuversStyleKit) public class ManeuversStyleKit: NSObject { - //// Drawing Methods - @objc dynamic public class func drawArrow180right(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawArrow180right(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -22,7 +21,7 @@ public class ManeuversStyleKit: NSObject { //// Group 2 context.saveGState() - context.translateBy(x: x, y: (y + 1)) + context.translateBy(x: x, y: y + 1) context.scaleBy(x: scale, y: scale) //// Bezier Drawing @@ -64,10 +63,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawArrowright(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawArrowright(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -84,7 +82,7 @@ public class ManeuversStyleKit: NSObject { //// Bezier Drawing context.saveGState() - context.translateBy(x: x, y: (y + 1)) + context.translateBy(x: x, y: y + 1) context.scaleBy(x: scale, y: scale) let bezierPath = UIBezierPath() @@ -115,10 +113,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawArrowslightright(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawArrowslightright(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -135,7 +132,7 @@ public class ManeuversStyleKit: NSObject { //// Group 3 context.saveGState() - context.translateBy(x: (x + 1), y: (y + 1)) + context.translateBy(x: x + 1, y: y + 1) context.scaleBy(x: scale, y: scale) //// Bezier 3 Drawing @@ -179,10 +176,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawArrowstraight(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawArrowstraight(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -199,7 +195,7 @@ public class ManeuversStyleKit: NSObject { //// Bezier Drawing context.saveGState() - context.translateBy(x: x, y: (y + 1)) + context.translateBy(x: x, y: y + 1) context.scaleBy(x: scale, y: scale) let bezierPath = UIBezierPath() @@ -228,10 +224,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawArrowsharpright(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawArrowsharpright(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -296,10 +291,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawArrive(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawArrive(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -316,7 +310,7 @@ public class ManeuversStyleKit: NSObject { //// Group 2 context.saveGState() - context.translateBy(x: x, y: (y + 1)) + context.translateBy(x: x, y: y + 1) context.scaleBy(x: scale, y: scale) //// Bezier Drawing @@ -363,10 +357,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawStarting(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawStarting(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -400,10 +393,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawDestination(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawDestination(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -445,10 +437,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawMerge(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawMerge(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -465,7 +456,7 @@ public class ManeuversStyleKit: NSObject { //// Group 3 context.saveGState() - context.translateBy(x: x, y: (y + 1)) + context.translateBy(x: x, y: y + 1) context.scaleBy(x: scale, y: scale) //// Bezier Drawing @@ -522,10 +513,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawFork(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawFork(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -542,7 +532,7 @@ public class ManeuversStyleKit: NSObject { //// Group 3 context.saveGState() - context.translateBy(x: (x + 2.99260816186), y: y) + context.translateBy(x: x + 2.99260816186, y: y) context.scaleBy(x: scale, y: scale) //// Bezier Drawing @@ -597,10 +587,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawOfframp(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawOfframp(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -617,7 +606,7 @@ public class ManeuversStyleKit: NSObject { //// Group 3 context.saveGState() - context.translateBy(x: (x + 3.38000011444), y: y) + context.translateBy(x: x + 3.38000011444, y: y) context.scaleBy(x: scale, y: scale) //// Bezier Drawing @@ -669,10 +658,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawArriveright(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawArriveright(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -736,10 +724,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawRoundabout(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32), roundabout_angle: CGFloat = 90, roundabout_radius: CGFloat = 6.5) { + @objc public dynamic class func drawRoundabout(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), secondaryColor: UIColor = UIColor(red: 0.618, green: 0.618, blue: 0.618, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32), roundabout_angle: CGFloat = 90, roundabout_radius: CGFloat = 6.5) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -751,15 +738,15 @@ public class ManeuversStyleKit: NSObject { //// Variable Declarations let scale: CGFloat = min(size.width / 32.0, size.height / 32.0) - let roundabout_arrow_height: CGFloat = scale * cos((roundabout_angle - 180) * CGFloat.pi/180) * 20 - let roundabout_arrow_width: CGFloat = scale * 0.75 * sin((roundabout_angle - 180) * CGFloat.pi/180) * 16 + let roundabout_arrow_height: CGFloat = scale * cos((roundabout_angle - 180) * CGFloat.pi / 180) * 20 + let roundabout_arrow_width: CGFloat = scale * 0.75 * sin((roundabout_angle - 180) * CGFloat.pi / 180) * 16 let roundabout_x: CGFloat = size.width / 2.0 + roundabout_arrow_width / 2.0 let roundabout_percentage: CGFloat = roundabout_angle / 360.0 * 2 * CGFloat.pi * roundabout_radius let roundabout_y: CGFloat = size.height - scale * (roundabout_radius * 2 + 4) + 1 + roundabout_arrow_height / 4.0 //// Group 3 context.saveGState() - context.translateBy(x: (roundabout_x - 0.00234436950684), y: (roundabout_y - 0.995999313354)) + context.translateBy(x: roundabout_x - 0.00234436950684, y: roundabout_y - 0.995999313354) context.scaleBy(x: scale, y: scale) //// Bezier Drawing @@ -789,7 +776,7 @@ public class ManeuversStyleKit: NSObject { //// Bezier 2 Drawing context.saveGState() context.translateBy(x: 0, y: 1) - context.rotate(by: -(roundabout_angle + 90) * CGFloat.pi/180) + context.rotate(by: -(roundabout_angle + 90) * CGFloat.pi / 180) let bezier2Path = UIBezierPath() bezier2Path.move(to: CGPoint(x: -9.47, y: -7.49)) @@ -821,7 +808,7 @@ public class ManeuversStyleKit: NSObject { //// Bezier 3 Drawing context.saveGState() context.translateBy(x: 1, y: 2) - context.rotate(by: -90 * CGFloat.pi/180) + context.rotate(by: -90 * CGFloat.pi / 180) context.scaleBy(x: -1, y: 1) let bezier3Path = UIBezierPath() @@ -848,10 +835,9 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } - @objc dynamic public class func drawArriveright2(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { + @objc public dynamic class func drawArriveright2(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 32, height: 32), resizing: ResizingBehavior = .aspectFit, primaryColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), size: CGSize = CGSize(width: 32, height: 32)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -930,7 +916,6 @@ public class ManeuversStyleKit: NSObject { context.restoreGState() context.restoreGState() - } @objc(ManeuversStyleKitResizingBehavior) @@ -950,17 +935,17 @@ public class ManeuversStyleKit: NSObject { scales.height = abs(target.height / rect.height) switch self { - case .aspectFit: - scales.width = min(scales.width, scales.height) - scales.height = scales.width - case .aspectFill: - scales.width = max(scales.width, scales.height) - scales.height = scales.width - case .stretch: - break - case .center: - scales.width = 1 - scales.height = 1 + case .aspectFit: + scales.width = min(scales.width, scales.height) + scales.height = scales.width + case .aspectFill: + scales.width = max(scales.width, scales.height) + scales.height = scales.width + case .stretch: + break + case .center: + scales.width = 1 + scales.height = 1 } var result = rect.standardized diff --git a/MapboxNavigation/NSAttributedString.swift b/MapboxNavigation/NSAttributedString.swift index cb05f3799..d68b56a3c 100644 --- a/MapboxNavigation/NSAttributedString.swift +++ b/MapboxNavigation/NSAttributedString.swift @@ -11,7 +11,7 @@ extension NSAttributedString { extension NSMutableAttributedString { func canonicalizeAttachments() { - enumerateAttribute(.attachment, in: NSRange(location: 0, length: length), options: []) { (value, range, stop) in + enumerateAttribute(.attachment, in: NSRange(location: 0, length: length), options: []) { value, range, _ in guard let attachment = value as? NSTextAttachment, type(of: attachment) != NSTextAttachment.self else { return } diff --git a/MapboxNavigation/NavigationMapView.swift b/MapboxNavigation/NavigationMapView.swift index 572b18885..8efbd9c3c 100644 --- a/MapboxNavigation/NavigationMapView.swift +++ b/MapboxNavigation/NavigationMapView.swift @@ -1,7 +1,7 @@ import Foundation -import MapLibre -import MapboxDirections import MapboxCoreNavigation +import MapboxDirections +import MapLibre import Turf /** @@ -9,10 +9,9 @@ import Turf */ @objc(MLNavigationMapView) open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { - // MARK: Class Constants - struct FrameIntervalOptions { + enum FrameIntervalOptions { fileprivate static let durationUntilNextManeuver: TimeInterval = 7 fileprivate static let durationSincePreviousManeuver: TimeInterval = 3 fileprivate static let defaultFramesPerSecond = MLNMapViewPreferredFramesPerSecond.maximum @@ -26,8 +25,8 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { @objc public var defaultAltitude: CLLocationDistance = 1000.0 /** - Returns the altitude the map conditionally zooms out to when user is on a motorway, and the maneuver length is sufficently long. - */ + Returns the altitude the map conditionally zooms out to when user is on a motorway, and the maneuver length is sufficently long. + */ @objc public var zoomedOutMotorwayAltitude: CLLocationDistance = 2000.0 /** @@ -53,6 +52,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let sourceOptions: [MLNShapeSourceOption: Any] = [.maximumZoomLevel: 16] // MARK: - Instance Properties + let sourceIdentifier = "routeSource" let sourceCasingIdentifier = "routeCasingSource" let routeLayerIdentifier = "routeLayer" @@ -71,17 +71,17 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let instructionLabel = "instructionLabel" let instructionCircle = "instructionCircle" - @objc dynamic public var trafficUnknownColor: UIColor = .trafficUnknown - @objc dynamic public var trafficLowColor: UIColor = .trafficLow - @objc dynamic public var trafficModerateColor: UIColor = .trafficModerate - @objc dynamic public var trafficHeavyColor: UIColor = .trafficHeavy - @objc dynamic public var trafficSevereColor: UIColor = .trafficSevere - @objc dynamic public var routeLineColor: UIColor = .defaultRouteLine - @objc dynamic public var routeLineAlternativeColor: UIColor = .defaultRouteLineAlternative - @objc dynamic public var routeLineCasingColor: UIColor = .defaultRouteLineCasing - @objc dynamic public var routeLineCasingAlternativeColor: UIColor = .defaultRouteLineCasingAlternative - @objc dynamic public var maneuverArrowColor: UIColor = .defaultManeuverArrow - @objc dynamic public var maneuverArrowStrokeColor: UIColor = .defaultManeuverArrowStroke + @objc public dynamic var trafficUnknownColor: UIColor = .trafficUnknown + @objc public dynamic var trafficLowColor: UIColor = .trafficLow + @objc public dynamic var trafficModerateColor: UIColor = .trafficModerate + @objc public dynamic var trafficHeavyColor: UIColor = .trafficHeavy + @objc public dynamic var trafficSevereColor: UIColor = .trafficSevere + @objc public dynamic var routeLineColor: UIColor = .defaultRouteLine + @objc public dynamic var routeLineAlternativeColor: UIColor = .defaultRouteLineAlternative + @objc public dynamic var routeLineCasingColor: UIColor = .defaultRouteLineCasing + @objc public dynamic var routeLineCasingAlternativeColor: UIColor = .defaultRouteLineCasingAlternative + @objc public dynamic var maneuverArrowColor: UIColor = .defaultManeuverArrow + @objc public dynamic var maneuverArrowStrokeColor: UIColor = .defaultManeuverArrowStroke var userLocationForCourseTracking: CLLocation? var animatesUserLocation: Bool = false @@ -91,41 +91,38 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { var shouldPositionCourseViewFrameByFrame = false { didSet { - if shouldPositionCourseViewFrameByFrame { + if self.shouldPositionCourseViewFrameByFrame { preferredFramesPerSecond = FrameIntervalOptions.defaultFramesPerSecond } } } var showsRoute: Bool { - get { - return style?.layer(withIdentifier: routeLayerIdentifier) != nil - } + style?.layer(withIdentifier: self.routeLayerIdentifier) != nil } - open override var showsUserLocation: Bool { + override open var showsUserLocation: Bool { get { - if tracksUserCourse || userLocationForCourseTracking != nil { - return !(userCourseView?.isHidden ?? true) + if self.tracksUserCourse || self.userLocationForCourseTracking != nil { + return !(self.userCourseView?.isHidden ?? true) } return super.showsUserLocation } set { - if tracksUserCourse || userLocationForCourseTracking != nil { + if self.tracksUserCourse || self.userLocationForCourseTracking != nil { super.showsUserLocation = false - if userCourseView == nil { - userCourseView = UserPuckCourseView(frame: CGRect(origin: .zero, size: CGSize(width: 75, height: 75))) + if self.userCourseView == nil { + self.userCourseView = UserPuckCourseView(frame: CGRect(origin: .zero, size: CGSize(width: 75, height: 75))) } - userCourseView?.isHidden = !newValue + self.userCourseView?.isHidden = !newValue } else { - userCourseView?.isHidden = true + self.userCourseView?.isHidden = true super.showsUserLocation = newValue } } } - /** Center point of the user course view in screen coordinates relative to the map view. - seealso: NavigationMapViewDelegate.navigationMapViewUserAnchorPoint(_:) @@ -136,12 +133,12 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } let contentFrame = bounds.inset(by: safeArea) - let courseViewWidth = userCourseView?.frame.width ?? 0 - let courseViewHeight = userCourseView?.frame.height ?? 0 - let edgePadding = UIEdgeInsets(top: (50 + courseViewHeight / 2), - left: (50 + courseViewWidth / 2), - bottom: (50 + courseViewHeight / 2), - right: (50 + courseViewWidth / 2)) + let courseViewWidth = self.userCourseView?.frame.width ?? 0 + let courseViewHeight = self.userCourseView?.frame.height ?? 0 + let edgePadding = UIEdgeInsets(top: 50 + courseViewHeight / 2, + left: 50 + courseViewWidth / 2, + bottom: 50 + courseViewHeight / 2, + right: 50 + courseViewWidth / 2) return CGPoint(x: max(min(contentFrame.midX, contentFrame.maxX - edgePadding.right), contentFrame.minX + edgePadding.left), @@ -157,16 +154,16 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { */ open var tracksUserCourse: Bool = false { didSet { - if tracksUserCourse { - enableFrameByFrameCourseViewTracking(for: 3) - altitude = defaultAltitude - showsUserLocation = true - courseTrackingDelegate?.navigationMapViewDidStartTrackingCourse?(self) + if self.tracksUserCourse { + self.enableFrameByFrameCourseViewTracking(for: 3) + self.altitude = self.defaultAltitude + self.showsUserLocation = true + self.courseTrackingDelegate?.navigationMapViewDidStartTrackingCourse?(self) } else { - courseTrackingDelegate?.navigationMapViewDidStopTrackingCourse?(self) + self.courseTrackingDelegate?.navigationMapViewDidStopTrackingCourse?(self) } if let location = userLocationForCourseTracking { - updateCourseTracking(location: location, animated: true) + self.updateCourseTracking(location: location, animated: true) } } } @@ -179,11 +176,11 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { @objc public var userCourseView: UIView? { didSet { oldValue?.removeFromSuperview() - if let userCourseView = userCourseView { + if let userCourseView { if let location = userLocationForCourseTracking { - updateCourseTracking(location: location, animated: false) + self.updateCourseTracking(location: location, animated: false) } else { - userCourseView.center = userAnchorPoint + userCourseView.center = self.userAnchorPoint } addSubview(userCourseView) } @@ -192,28 +189,28 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { private lazy var mapTapGesture = UITapGestureRecognizer(target: self, action: #selector(didRecieveTap(sender:))) - //MARK: - Initalizers + // MARK: - Initalizers - public override init(frame: CGRect) { - altitude = defaultAltitude + override public init(frame: CGRect) { + self.altitude = self.defaultAltitude super.init(frame: frame) - commonInit() + self.commonInit() } public required init?(coder decoder: NSCoder) { - altitude = defaultAltitude + self.altitude = self.defaultAltitude super.init(coder: decoder) - commonInit() + self.commonInit() } - public override init(frame: CGRect, styleURL: URL?) { - altitude = defaultAltitude + override public init(frame: CGRect, styleURL: URL?) { + self.altitude = self.defaultAltitude super.init(frame: frame, styleURL: styleURL) - commonInit() + self.commonInit() } public convenience init(frame: CGRect, styleURL: URL?, config: MNConfig? = nil) { - if let config = config { + if let config { ConfigManager.shared.config = config } @@ -221,19 +218,19 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } fileprivate func commonInit() { - makeGestureRecognizersRespectCourseTracking() - makeGestureRecognizersUpdateCourseView() + self.makeGestureRecognizersRespectCourseTracking() + self.makeGestureRecognizersUpdateCourseView() - resumeNotifications() + self.resumeNotifications() } deinit { suspendNotifications() } - //MARK: - Overrides + // MARK: - Overrides - open override func prepareForInterfaceBuilder() { + override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() let image = UIImage(named: "feedback-map-error", in: .mapboxNavigation, compatibleWith: nil) @@ -244,39 +241,39 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { addSubview(imageView) } - open override func layoutSubviews() { + override open func layoutSubviews() { super.layoutSubviews() - //If the map is in tracking mode, make sure we update the camera after the layout pass. - if (tracksUserCourse) { - updateCourseTracking(location: userLocationForCourseTracking, camera:self.camera, animated: false) + // If the map is in tracking mode, make sure we update the camera after the layout pass. + if self.tracksUserCourse { + self.updateCourseTracking(location: self.userLocationForCourseTracking, camera: camera, animated: false) } } - open override func anchorPoint(forGesture gesture: UIGestureRecognizer) -> CGPoint { - if tracksUserCourse { - return userAnchorPoint + override open func anchorPoint(forGesture gesture: UIGestureRecognizer) -> CGPoint { + if self.tracksUserCourse { + self.userAnchorPoint } else { - return super.anchorPoint(forGesture: gesture) + super.anchorPoint(forGesture: gesture) } } - open override func mapViewDidFinishRenderingFrameFullyRendered(_ fullyRendered: Bool) { + override open func mapViewDidFinishRenderingFrameFullyRendered(_ fullyRendered: Bool) { super.mapViewDidFinishRenderingFrameFullyRendered(fullyRendered) - guard shouldPositionCourseViewFrameByFrame else { return } + guard self.shouldPositionCourseViewFrameByFrame else { return } guard let location = userLocationForCourseTracking else { return } - userCourseView?.center = convert(location.coordinate, toPointTo: self) + self.userCourseView?.center = convert(location.coordinate, toPointTo: self) } // MARK: - Notifications func resumeNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .routeControllerProgressDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.progressDidChange(_:)), name: .routeControllerProgressDidChange, object: nil) let gestures = gestureRecognizers ?? [] - let mapTapGesture = self.mapTapGesture + let mapTapGesture = mapTapGesture mapTapGesture.requireFailure(of: gestures) addGestureRecognizer(mapTapGesture) } @@ -286,16 +283,16 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } @objc func progressDidChange(_ notification: Notification) { - guard tracksUserCourse else { return } + guard self.tracksUserCourse else { return } let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as! RouteProgress if let location = userLocationForCourseTracking { - let cameraUpdated = courseTrackingDelegate?.updateCamera?(self, location: location, routeProgress: routeProgress) ?? false + let cameraUpdated = self.courseTrackingDelegate?.updateCamera?(self, location: location, routeProgress: routeProgress) ?? false - if(!cameraUpdated){ - let newCamera = MLNMapCamera(lookingAtCenter: location.coordinate, acrossDistance: altitude, pitch: 45, heading: location.course) - let function: CAMediaTimingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + if !cameraUpdated { + let newCamera = MLNMapCamera(lookingAtCenter: location.coordinate, acrossDistance: self.altitude, pitch: 45, heading: location.course) + let function = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) setCamera(newCamera, withDuration: 1, animationTimingFunction: function, edgePadding: UIEdgeInsets.zero, completionHandler: nil) } } @@ -310,49 +307,44 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } if let upcomingStep = routeProgress.currentLegProgress.upComingStep, - upcomingStep.maneuverDirection == .straightAhead || upcomingStep.maneuverDirection == .slightLeft || upcomingStep.maneuverDirection == .slightRight { - preferredFramesPerSecond = shouldPositionCourseViewFrameByFrame ? FrameIntervalOptions.defaultFramesPerSecond : FrameIntervalOptions.decreasedFramesPerSecond - } else if durationUntilNextManeuver > FrameIntervalOptions.durationUntilNextManeuver && - durationSincePreviousManeuver > FrameIntervalOptions.durationSincePreviousManeuver { - preferredFramesPerSecond = shouldPositionCourseViewFrameByFrame ? FrameIntervalOptions.defaultFramesPerSecond : FrameIntervalOptions.decreasedFramesPerSecond + upcomingStep.maneuverDirection == .straightAhead || upcomingStep.maneuverDirection == .slightLeft || upcomingStep.maneuverDirection == .slightRight { + preferredFramesPerSecond = self.shouldPositionCourseViewFrameByFrame ? FrameIntervalOptions.defaultFramesPerSecond : FrameIntervalOptions.decreasedFramesPerSecond + } else if durationUntilNextManeuver > FrameIntervalOptions.durationUntilNextManeuver, + durationSincePreviousManeuver > FrameIntervalOptions.durationSincePreviousManeuver { + preferredFramesPerSecond = self.shouldPositionCourseViewFrameByFrame ? FrameIntervalOptions.defaultFramesPerSecond : FrameIntervalOptions.decreasedFramesPerSecond } else { preferredFramesPerSecond = FrameIntervalOptions.pluggedInFramesPerSecond } } - - - // Track position on a frame by frame basis. Used for first location update and when resuming tracking mode func enableFrameByFrameCourseViewTracking(for duration: TimeInterval) { - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(disableFrameByFramePositioning), object: nil) - perform(#selector(disableFrameByFramePositioning), with: nil, afterDelay: duration) - shouldPositionCourseViewFrameByFrame = true + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.disableFrameByFramePositioning), object: nil) + perform(#selector(self.disableFrameByFramePositioning), with: nil, afterDelay: duration) + self.shouldPositionCourseViewFrameByFrame = true } - //MARK: - User Tracking + // MARK: - User Tracking @objc fileprivate func disableFrameByFramePositioning() { - shouldPositionCourseViewFrameByFrame = false + self.shouldPositionCourseViewFrameByFrame = false } @objc private func disableUserCourseTracking() { - guard tracksUserCourse else { return } - tracksUserCourse = false + guard self.tracksUserCourse else { return } + self.tracksUserCourse = false } - @objc public func updateCourseTracking(location: CLLocation?, camera: MLNMapCamera? = nil, animated: Bool = false) { - // While animating to overhead mode, don't animate the puck. - let duration: TimeInterval = animated && !isAnimatingToOverheadMode ? 1 : 0 - animatesUserLocation = animated - userLocationForCourseTracking = location - guard let location = location, CLLocationCoordinate2DIsValid(location.coordinate) else { + let duration: TimeInterval = animated && !self.isAnimatingToOverheadMode ? 1 : 0 + self.animatesUserLocation = animated + self.userLocationForCourseTracking = location + guard let location, CLLocationCoordinate2DIsValid(location.coordinate) else { return } - if !tracksUserCourse || userAnchorPoint != userCourseView?.center ?? userAnchorPoint { + if !self.tracksUserCourse || self.userAnchorPoint != userCourseView?.center ?? self.userAnchorPoint { UIView.animate(withDuration: duration, delay: 0, options: [.curveLinear, .beginFromCurrentState], animations: { self.userCourseView?.center = self.convert(location.coordinate, toPointTo: self) }) @@ -362,45 +354,44 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { if let customTransformation = userCourseView.update?(location: location, pitch: self.camera.pitch, direction: direction, animated: animated, tracksUserCourse: tracksUserCourse) { customTransformation } else { - self.userCourseView?.applyDefaultUserPuckTransformation(location: location, pitch: self.camera.pitch, direction: direction, animated: animated, tracksUserCourse: tracksUserCourse) + self.userCourseView?.applyDefaultUserPuckTransformation(location: location, pitch: self.camera.pitch, direction: direction, animated: animated, tracksUserCourse: self.tracksUserCourse) } } else { - userCourseView?.applyDefaultUserPuckTransformation(location: location, pitch: self.camera.pitch, direction: direction, animated: animated, tracksUserCourse: tracksUserCourse) + userCourseView?.applyDefaultUserPuckTransformation(location: location, pitch: self.camera.pitch, direction: direction, animated: animated, tracksUserCourse: self.tracksUserCourse) } } - //MARK: - Gesture Recognizers + // MARK: - Gesture Recognizers /** Fired when NavigationMapView detects a tap not handled elsewhere by other gesture recognizers. */ @objc func didRecieveTap(sender: UITapGestureRecognizer) { - guard let routes = routes, let tapPoint = sender.point else { return } + guard let routes, let tapPoint = sender.point else { return } - let waypointTest = waypoints(on: routes, closeTo: tapPoint) //are there waypoints near the tapped location? - if let selected = waypointTest?.first { //test passes - navigationMapDelegate?.navigationMapView?(self, didSelect: selected) + let waypointTest = self.waypoints(on: routes, closeTo: tapPoint) // are there waypoints near the tapped location? + if let selected = waypointTest?.first { // test passes + self.navigationMapDelegate?.navigationMapView?(self, didSelect: selected) return } else if let routes = self.routes(closeTo: tapPoint) { guard let selectedRoute = routes.first else { return } - navigationMapDelegate?.navigationMapView?(self, didSelect: selectedRoute) + self.navigationMapDelegate?.navigationMapView?(self, didSelect: selectedRoute) } - } @objc func updateCourseView(_ sender: UIGestureRecognizer) { preferredFramesPerSecond = FrameIntervalOptions.defaultFramesPerSecond if sender.state == .ended { - altitude = self.camera.altitude - enableFrameByFrameCourseViewTracking(for: 2) + self.altitude = camera.altitude + self.enableFrameByFrameCourseViewTracking(for: 2) } // Capture altitude for double tap and two finger tap after animation finishes if sender is UITapGestureRecognizer, sender.state == .ended { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { self.altitude = self.camera.altitude - }) + } } if let pan = sender as? UIPanGestureRecognizer { @@ -408,40 +399,41 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let velocity = pan.velocity(in: self) let didFling = sqrt(velocity.x * velocity.x + velocity.y * velocity.y) > 100 if didFling { - enableFrameByFrameCourseViewTracking(for: 1) + self.enableFrameByFrameCourseViewTracking(for: 1) } } } if sender.state == .changed { guard let location = userLocationForCourseTracking else { return } - userCourseView?.layer.removeAllAnimations() - userCourseView?.center = convert(location.coordinate, toPointTo: self) + self.userCourseView?.layer.removeAllAnimations() + self.userCourseView?.center = convert(location.coordinate, toPointTo: self) } } // MARK: Feature Addition/Removal + /** - Showcases route array. Adds routes and waypoints to map, and sets camera to point encompassing the route. - */ - public static let defaultPadding: UIEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20) + Showcases route array. Adds routes and waypoints to map, and sets camera to point encompassing the route. + */ + public static let defaultPadding: UIEdgeInsets = .init(top: 10, left: 20, bottom: 10, right: 20) @objc public func showcase(_ routes: [Route], padding: UIEdgeInsets = NavigationMapView.defaultPadding, animated: Bool = false) { guard let active = routes.first, let coords = active.coordinates, - !coords.isEmpty else { return } //empty array + !coords.isEmpty else { return } // empty array - removeArrow() - removeRoutes() - removeWaypoints() + self.removeArrow() + self.removeRoutes() + self.removeWaypoints() - showRoutes(routes) - showWaypoints(active) + self.showRoutes(routes) + self.showWaypoints(active) - fit(to: active, facing: 0, padding: padding, animated: animated) + self.fit(to: active, facing: 0, padding: padding, animated: animated) } - func fit(to route: Route, facing direction:CLLocationDirection = 0, padding: UIEdgeInsets = NavigationMapView.defaultPadding, animated: Bool = false) { + func fit(to route: Route, facing direction: CLLocationDirection = 0, padding: UIEdgeInsets = NavigationMapView.defaultPadding, animated: Bool = false) { guard let coords = route.coordinates, !coords.isEmpty else { return } setUserTrackingMode(.none, animated: false, completionHandler: nil) @@ -451,20 +443,19 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { setCamera(camera, animated: false) } - /** Adds or updates both the route line and the route line casing */ @objc public func showRoutes(_ routes: [Route], legIndex: Int = 0) { - guard let style = style else { return } + guard let style else { return } guard let mainRoute = routes.first else { return } self.routes = routes - let polylines = navigationMapDelegate?.navigationMapView?(self, shapeFor: routes) ?? shape(for: routes, legIndex: legIndex) - let mainPolylineSimplified = navigationMapDelegate?.navigationMapView?(self, simplifiedShapeFor: mainRoute) ?? shape(forCasingOf: mainRoute, legIndex: legIndex) + let polylines = self.navigationMapDelegate?.navigationMapView?(self, shapeFor: routes) ?? self.shape(for: routes, legIndex: legIndex) + let mainPolylineSimplified = self.navigationMapDelegate?.navigationMapView?(self, simplifiedShapeFor: mainRoute) ?? self.shape(forCasingOf: mainRoute, legIndex: legIndex) if let source = style.source(withIdentifier: sourceIdentifier) as? MLNShapeSource, - let sourceSimplified = style.source(withIdentifier: sourceCasingIdentifier) as? MLNShapeSource { + let sourceSimplified = style.source(withIdentifier: sourceCasingIdentifier) as? MLNShapeSource { source.shape = polylines sourceSimplified.shape = mainPolylineSimplified } else { @@ -473,12 +464,12 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { style.addSource(lineSource) style.addSource(lineCasingSource) - let line = navigationMapDelegate?.navigationMapView?(self, routeStyleLayerWithIdentifier: routeLayerIdentifier, source: lineSource) ?? routeStyleLayer(identifier: routeLayerIdentifier, source: lineSource) - let lineCasing = navigationMapDelegate?.navigationMapView?(self, routeCasingStyleLayerWithIdentifier: routeLayerCasingIdentifier, source: lineCasingSource) ?? routeCasingStyleLayer(identifier: routeLayerCasingIdentifier, source: lineSource) + let line = self.navigationMapDelegate?.navigationMapView?(self, routeStyleLayerWithIdentifier: self.routeLayerIdentifier, source: lineSource) ?? self.routeStyleLayer(identifier: self.routeLayerIdentifier, source: lineSource) + let lineCasing = self.navigationMapDelegate?.navigationMapView?(self, routeCasingStyleLayerWithIdentifier: self.routeLayerCasingIdentifier, source: lineCasingSource) ?? self.routeCasingStyleLayer(identifier: self.routeLayerCasingIdentifier, source: lineSource) for layer in style.layers.reversed() { - if !(layer is MLNSymbolStyleLayer) && - layer.identifier != arrowLayerIdentifier && layer.identifier != arrowSymbolLayerIdentifier && layer.identifier != arrowCasingSymbolLayerIdentifier && layer.identifier != arrowLayerStrokeIdentifier && layer.identifier != waypointCircleIdentifier { + if !(layer is MLNSymbolStyleLayer), + layer.identifier != self.arrowLayerIdentifier, layer.identifier != self.arrowSymbolLayerIdentifier, layer.identifier != self.arrowCasingSymbolLayerIdentifier, layer.identifier != self.arrowLayerStrokeIdentifier, layer.identifier != self.waypointCircleIdentifier { style.insertLayer(line, below: layer) style.insertLayer(lineCasing, below: line) break @@ -491,7 +482,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { Removes route line and route line casing from map */ @objc public func removeRoutes() { - guard let style = style else { + guard let style else { return } @@ -516,24 +507,23 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { Adds the route waypoints to the map given the current leg index. Previous waypoints for completed legs will be omitted. */ @objc public func showWaypoints(_ route: Route, legIndex: Int = 0) { - guard let style = style else { + guard let style else { return } - let waypoints: [Waypoint] = Array(route.legs.map { $0.destination }.dropLast()) + let waypoints: [Waypoint] = Array(route.legs.map(\.destination).dropLast()) - let source = navigationMapDelegate?.navigationMapView?(self, shapeFor: waypoints, legIndex: legIndex) ?? shape(for: waypoints, legIndex: legIndex) - if route.routeOptions.waypoints.count > 2 { //are we on a multipoint route? - - routes = [route] //update the model + let source = self.navigationMapDelegate?.navigationMapView?(self, shapeFor: waypoints, legIndex: legIndex) ?? self.shape(for: waypoints, legIndex: legIndex) + if route.routeOptions.waypoints.count > 2 { // are we on a multipoint route? + self.routes = [route] // update the model if let waypointSource = style.source(withIdentifier: waypointSourceIdentifier) as? MLNShapeSource { waypointSource.shape = source } else { let sourceShape = MLNShapeSource(identifier: waypointSourceIdentifier, shape: source, options: sourceOptions) style.addSource(sourceShape) - let circles = navigationMapDelegate?.navigationMapView?(self, waypointStyleLayerWithIdentifier: waypointCircleIdentifier, source: sourceShape) ?? routeWaypointCircleStyleLayer(identifier: waypointCircleIdentifier, source: sourceShape) - let symbols = navigationMapDelegate?.navigationMapView?(self, waypointSymbolStyleLayerWithIdentifier: waypointSymbolIdentifier, source: sourceShape) ?? routeWaypointSymbolStyleLayer(identifier: waypointSymbolIdentifier, source: sourceShape) + let circles = self.navigationMapDelegate?.navigationMapView?(self, waypointStyleLayerWithIdentifier: self.waypointCircleIdentifier, source: sourceShape) ?? self.routeWaypointCircleStyleLayer(identifier: self.waypointCircleIdentifier, source: sourceShape) + let symbols = self.navigationMapDelegate?.navigationMapView?(self, waypointSymbolStyleLayerWithIdentifier: self.waypointSymbolIdentifier, source: sourceShape) ?? self.routeWaypointSymbolStyleLayer(identifier: self.waypointSymbolIdentifier, source: sourceShape) if let arrowLayer = style.layer(withIdentifier: arrowCasingSymbolLayerIdentifier) { style.insertLayer(circles, below: arrowLayer) @@ -545,18 +535,19 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } } - if let lastLeg = route.legs.last { + if let lastLeg = route.legs.last { removeAnnotations(annotations ?? []) let destination = MLNPointAnnotation() destination.coordinate = lastLeg.destination.coordinate addAnnotation(destination) } } + /** Removes all waypoints from the map. */ @objc public func removeWaypoints() { - guard let style = style else { return } + guard let style else { return } removeAnnotations(annotations ?? []) @@ -582,13 +573,13 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { */ @objc public func addArrow(route: Route, legIndex: Int, stepIndex: Int) { guard route.legs.indices.contains(legIndex), - route.legs[legIndex].steps.indices.contains(stepIndex) else { return } + route.legs[legIndex].steps.indices.contains(stepIndex) else { return } let step = route.legs[legIndex].steps[stepIndex] let maneuverCoordinate = step.maneuverLocation guard let routeCoordinates = route.coordinates else { return } - guard let style = style else { + guard let style else { return } @@ -603,7 +594,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let shaftLength = max(min(30 * metersPerPoint(atLatitude: maneuverCoordinate.latitude), 30), 10) let polyline = Polyline(routeCoordinates) let shaftCoordinates = Array(polyline.trimmed(from: maneuverCoordinate, distance: -shaftLength).coordinates.reversed() - + polyline.trimmed(from: maneuverCoordinate, distance: shaftLength).coordinates.suffix(from: 1)) + + polyline.trimmed(from: maneuverCoordinate, distance: shaftLength).coordinates.suffix(from: 1)) if shaftCoordinates.count > 1 { var shaftStrokeCoordinates = shaftCoordinates let shaftStrokePolyline = ArrowStrokePolyline(coordinates: &shaftStrokeCoordinates, count: UInt(shaftStrokeCoordinates.count)) @@ -629,7 +620,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { curveType: .linear, parameters: nil, stops: NSExpression(forConstantValue: MLNRouteLineWidthByZoomLevel.multiplied(by: 0.7))) - arrow.lineColor = NSExpression(forConstantValue: maneuverArrowColor) + arrow.lineColor = NSExpression(forConstantValue: self.maneuverArrowColor) style.addSource(arrowSource) style.addLayer(arrow) @@ -645,7 +636,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { curveType: .linear, parameters: nil, stops: NSExpression(forConstantValue: MLNRouteLineWidthByZoomLevel.multiplied(by: 0.8))) - arrowStroke.lineColor = NSExpression(forConstantValue: maneuverArrowStrokeColor) + arrowStroke.lineColor = NSExpression(forConstantValue: self.maneuverArrowStrokeColor) style.addSource(arrowSourceStroke) style.insertLayer(arrowStroke, below: arrow) @@ -668,7 +659,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let arrowSymbolLayer = MLNSymbolStyleLayer(identifier: arrowSymbolLayerIdentifier, source: arrowSymbolSource) arrowSymbolLayer.minimumZoomLevel = minimumZoomLevel arrowSymbolLayer.iconImageName = NSExpression(forConstantValue: "triangle-tip-navigation") - arrowSymbolLayer.iconColor = NSExpression(forConstantValue: maneuverArrowColor) + arrowSymbolLayer.iconColor = NSExpression(forConstantValue: self.maneuverArrowColor) arrowSymbolLayer.iconRotationAlignment = NSExpression(forConstantValue: "map") arrowSymbolLayer.iconRotation = NSExpression(forConstantValue: shaftDirection as NSNumber) arrowSymbolLayer.iconScale = NSExpression(forMLNInterpolating: .zoomLevelVariable, @@ -680,7 +671,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let arrowSymbolLayerCasing = MLNSymbolStyleLayer(identifier: arrowCasingSymbolLayerIdentifier, source: arrowSymbolSource) arrowSymbolLayerCasing.minimumZoomLevel = arrowSymbolLayer.minimumZoomLevel arrowSymbolLayerCasing.iconImageName = arrowSymbolLayer.iconImageName - arrowSymbolLayerCasing.iconColor = NSExpression(forConstantValue: maneuverArrowStrokeColor) + arrowSymbolLayerCasing.iconColor = NSExpression(forConstantValue: self.maneuverArrowStrokeColor) arrowSymbolLayerCasing.iconRotationAlignment = arrowSymbolLayer.iconRotationAlignment arrowSymbolLayerCasing.iconRotation = arrowSymbolLayer.iconRotation arrowSymbolLayerCasing.iconScale = NSExpression(forMLNInterpolating: .zoomLevelVariable, @@ -693,7 +684,6 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { style.insertLayer(arrowSymbolLayer, above: arrow) style.insertLayer(arrowSymbolLayerCasing, below: arrow) } - } } @@ -701,7 +691,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { Removes the step arrow from the map. */ @objc public func removeArrow() { - guard let style = style else { + guard let style else { return } @@ -740,35 +730,35 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { func makeGestureRecognizersRespectCourseTracking() { for gestureRecognizer in gestureRecognizers ?? [] where gestureRecognizer is UIPanGestureRecognizer || gestureRecognizer is UIRotationGestureRecognizer { - gestureRecognizer.addTarget(self, action: #selector(disableUserCourseTracking)) + gestureRecognizer.addTarget(self, action: #selector(disableUserCourseTracking)) } } func makeGestureRecognizersUpdateCourseView() { for gestureRecognizer in gestureRecognizers ?? [] { - gestureRecognizer.addTarget(self, action: #selector(updateCourseView(_:))) + gestureRecognizer.addTarget(self, action: #selector(self.updateCourseView(_:))) } } - //TODO: Change to point-based distance calculation + // TODO: Change to point-based distance calculation private func waypoints(on routes: [Route], closeTo point: CGPoint) -> [Waypoint]? { let tapCoordinate = convert(point, toCoordinateFrom: self) - let multipointRoutes = routes.filter { $0.routeOptions.waypoints.count >= 3} + let multipointRoutes = routes.filter { $0.routeOptions.waypoints.count >= 3 } guard multipointRoutes.count > 0 else { return nil } - let waypoints = multipointRoutes.flatMap({$0.routeOptions.waypoints}) + let waypoints = multipointRoutes.flatMap(\.routeOptions.waypoints) - //lets sort the array in order of closest to tap - let closest = waypoints.sorted { (left, right) -> Bool in + // lets sort the array in order of closest to tap + let closest = waypoints.sorted { left, right -> Bool in let leftDistance = left.coordinate.distance(to: tapCoordinate) let rightDistance = right.coordinate.distance(to: tapCoordinate) return leftDistance < rightDistance } - //lets filter to see which ones are under threshold - let candidates = closest.filter({ + // lets filter to see which ones are under threshold + let candidates = closest.filter { let coordinatePoint = self.convert($0.coordinate, toPointTo: self) - return coordinatePoint.distance(to: point) < tapGestureDistanceThreshold - }) + return coordinatePoint.distance(to: point) < self.tapGestureDistanceThreshold + } return candidates } @@ -776,13 +766,13 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { private func routes(closeTo point: CGPoint) -> [Route]? { let tapCoordinate = convert(point, toCoordinateFrom: self) - //do we have routes? If so, filter routes with at least 2 coordinates. + // do we have routes? If so, filter routes with at least 2 coordinates. guard let routes = routes?.filter({ $0.coordinates?.count ?? 0 > 1 }) else { return nil } - //Sort routes by closest distance to tap gesture. - let closest = routes.sorted { (left, right) -> Bool in + // Sort routes by closest distance to tap gesture. + let closest = routes.sorted { left, right -> Bool in - //existance has been assured through use of filter. + // existance has been assured through use of filter. let leftLine = Polyline(left.coordinates!) let rightLine = Polyline(right.coordinates!) let leftDistance = leftLine.closestCoordinate(to: tapCoordinate)!.distance @@ -791,12 +781,12 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { return leftDistance < rightDistance } - //filter closest coordinates by which ones are under threshold. + // filter closest coordinates by which ones are under threshold. let candidates = closest.filter { let closestCoordinate = Polyline($0.coordinates!).closestCoordinate(to: tapCoordinate)!.coordinate let closestPoint = self.convert(closestCoordinate, toPointTo: self) - return closestPoint.distance(to: point) < tapGestureDistanceThreshold + return closestPoint.distance(to: point) < self.tapGestureDistanceThreshold } return candidates } @@ -836,13 +826,13 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { return index == 0 ? stepCoordinates : allCoordinates + stepCoordinates.suffix(from: 1) } - let mergedCongestionSegments = combine(legCoordinates, with: legCongestion) + let mergedCongestionSegments = self.combine(legCoordinates, with: legCongestion) let lines: [MLNPolylineFeature] = mergedCongestionSegments.map { (congestionSegment: CongestionSegment) -> MLNPolylineFeature in let polyline = MLNPolylineFeature(coordinates: congestionSegment.0, count: UInt(congestionSegment.0.count)) polyline.attributes[MBCongestionAttribute] = String(describing: congestionSegment.1) polyline.attributes["isAlternateRoute"] = false - if let legIndex = legIndex { + if let legIndex { polyline.attributes[MBCurrentLegAttribute] = index == legIndex } else { polyline.attributes[MBCurrentLegAttribute] = index == 0 @@ -860,7 +850,6 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { var segments: [CongestionSegment] = [] segments.reserveCapacity(congestions.count) for (index, congestion) in congestions.enumerated() { - let congestionSegment: ([CLLocationCoordinate2D], CongestionLevel) = ([coordinates[index], coordinates[index + 1]], congestion) let coordinates = congestionSegment.0 let congestionLevel = congestionSegment.1 @@ -878,12 +867,10 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { var linesPerLeg: [MLNPolylineFeature] = [] for (index, leg) in route.legs.enumerated() { - let legCoordinates: [CLLocationCoordinate2D] = Array(leg.steps.compactMap { - $0.coordinates - }.joined()) + let legCoordinates: [CLLocationCoordinate2D] = Array(leg.steps.compactMap(\.coordinates).joined()) let polyline = MLNPolylineFeature(coordinates: legCoordinates, count: UInt(legCoordinates.count)) - if let legIndex = legIndex { + if let legIndex { polyline.attributes[MBCurrentLegAttribute] = index == legIndex } else { polyline.attributes[MBCurrentLegAttribute] = index == 0 @@ -914,7 +901,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { let circles = MLNCircleStyleLayer(identifier: waypointCircleIdentifier, source: source) let opacity = NSExpression(forConditional: NSPredicate(format: "waypointCompleted == true"), trueExpression: NSExpression(forConstantValue: 0.5), falseExpression: NSExpression(forConstantValue: 1)) - circles.circleColor = NSExpression(forConstantValue: UIColor(red:0.9, green:0.9, blue:0.9, alpha:1.0)) + circles.circleColor = NSExpression(forConstantValue: UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0)) circles.circleOpacity = opacity circles.circleRadius = NSExpression(forConstantValue: 10) circles.circleStrokeColor = NSExpression(forConstantValue: UIColor.black) @@ -944,8 +931,8 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { stops: NSExpression(forConstantValue: MLNRouteLineWidthByZoomLevel)) line.lineColor = NSExpression( forConditional: NSPredicate(format: "isAlternateRoute == true"), - trueExpression: NSExpression(forConstantValue: routeLineAlternativeColor), - falseExpression: NSExpression(forConstantValue: routeLineColor)) + trueExpression: NSExpression(forConstantValue: self.routeLineAlternativeColor), + falseExpression: NSExpression(forConstantValue: self.routeLineColor)) line.lineOpacity = NSExpression( forConditional: NSPredicate(format: "isAlternateRoute == true"), @@ -969,8 +956,8 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { lineCasing.lineColor = NSExpression( forConditional: NSPredicate(format: "isAlternateRoute == true"), - trueExpression: NSExpression(forConstantValue: routeLineCasingAlternativeColor), - falseExpression: NSExpression(forConstantValue: routeLineCasingColor)) + trueExpression: NSExpression(forConstantValue: self.routeLineCasingAlternativeColor), + falseExpression: NSExpression(forConstantValue: self.routeLineCasingColor)) lineCasing.lineOpacity = NSExpression( forConditional: NSPredicate(format: "isAlternateRoute == true"), @@ -1004,23 +991,18 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { to call this method on the value of `NavigationViewController.mapView`. */ @objc public func localizeLabels() { - - guard let style = style else { + guard let style else { return } let streetsSourceIdentifiers: [String] = style.sources.compactMap { $0 as? MLNVectorTileSource - }.filter { - $0.isMapboxStreets - }.map { - $0.identifier - } + }.filter(\.isMapboxStreets).map(\.identifier) for layer in style.layers where layer is MLNSymbolStyleLayer { let layer = layer as! MLNSymbolStyleLayer guard let sourceIdentifier = layer.sourceIdentifier, - streetsSourceIdentifiers.contains(sourceIdentifier) else { + streetsSourceIdentifiers.contains(sourceIdentifier) else { continue } guard let text = layer.text else { @@ -1038,7 +1020,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } @objc public func showVoiceInstructionsOnMap(route: Route) { - guard let style = style else { + guard let style else { return } @@ -1048,7 +1030,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { for instruction in step.instructionsSpokenAlongStep! { let feature = MLNPointFeature() feature.coordinate = Polyline(route.legs[legIndex].steps[stepIndex].coordinates!.reversed()).coordinateFromStart(distance: instruction.distanceAlongStep)! - feature.attributes = [ "instruction": instruction.text ] + feature.attributes = ["instruction": instruction.text] features.append(feature) } } @@ -1081,21 +1063,20 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { } } - /** Sets the camera directly over a series of coordinates. */ @objc public func setOverheadCameraView(from userLocation: CLLocationCoordinate2D, along coordinates: [CLLocationCoordinate2D], for bounds: UIEdgeInsets) { - isAnimatingToOverheadMode = true + self.isAnimatingToOverheadMode = true let slicedLine = Polyline(coordinates).sliced(from: userLocation).coordinates let line = MLNPolyline(coordinates: slicedLine, count: UInt(slicedLine.count)) - tracksUserCourse = false + self.tracksUserCourse = false // If the user has a short distance left on the route, prevent the camera from zooming all the way. // `MLNMapView.setVisibleCoordinateBounds(:edgePadding:animated:)` will go beyond what is convenient for the driver. guard line.overlayBounds.ne.distance(to: line.overlayBounds.sw) > NavigationMapViewMinimumDistanceForOverheadZooming else { - let camera = self.camera + let camera = camera camera.pitch = 0 camera.heading = 0 camera.centerCoordinate = userLocation @@ -1106,7 +1087,7 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { return } - let cam = self.camera + let cam = camera cam.pitch = 0 cam.heading = 0 @@ -1120,8 +1101,8 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate { Recenters the camera and begins tracking the user's location. */ @objc public func recenterMap() { - tracksUserCourse = true - enableFrameByFrameCourseViewTracking(for: 3) + self.tracksUserCourse = true + self.enableFrameByFrameCourseViewTracking(for: 3) } } @@ -1172,10 +1153,10 @@ public protocol NavigationMapViewDelegate: AnyObject { @objc optional func navigationMapView(_ mapView: NavigationMapView, routeCasingStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? /** - Tells the receiver that the user has selected a route by interacting with the map view. - - parameter mapView: The NavigationMapView. - - parameter route: The route that was selected. - */ + Tells the receiver that the user has selected a route by interacting with the map view. + - parameter mapView: The NavigationMapView. + - parameter route: The route that was selected. + */ @objc(navigationMapView:didSelectRoute:) optional func navigationMapView(_ mapView: NavigationMapView, didSelect route: Route) @@ -1235,16 +1216,17 @@ public protocol NavigationMapViewDelegate: AnyObject { optional func navigationMapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? /** - Asks the receiver to return a CGPoint to serve as the anchor for the user icon. - - important: The return value should be returned in the normal UIKit coordinate-space, NOT CoreAnimation's unit coordinate-space. - - parameter mapView: The NavigationMapView. - - returns: A CGPoint (in regular coordinate-space) that represents the point on-screen where the user location icon should be drawn. - */ + Asks the receiver to return a CGPoint to serve as the anchor for the user icon. + - important: The return value should be returned in the normal UIKit coordinate-space, NOT CoreAnimation's unit coordinate-space. + - parameter mapView: The NavigationMapView. + - returns: A CGPoint (in regular coordinate-space) that represents the point on-screen where the user location icon should be drawn. + */ @objc(navigationMapViewUserAnchorPoint:) optional func navigationMapViewUserAnchorPoint(_ mapView: NavigationMapView) -> CGPoint } // MARK: NavigationMapViewCourseTrackingDelegate + /** The `NavigationMapViewCourseTrackingDelegate` provides methods for responding to the `NavigationMapView` starting or stopping course tracking. */ diff --git a/MapboxNavigation/NavigationView.swift b/MapboxNavigation/NavigationView.swift index 38d3c6561..007839f98 100644 --- a/MapboxNavigation/NavigationView.swift +++ b/MapboxNavigation/NavigationView.swift @@ -1,6 +1,6 @@ -import UIKit -import MapLibre import MapboxDirections +import MapLibre +import UIKit /** A view that represents the root view of the MapboxNavigation drop-in UI. @@ -34,11 +34,10 @@ import MapboxDirections | 3 | +--------------------+ ``` -*/ + */ @IBDesignable @objc(MBNavigationView) open class NavigationView: UIView { - private enum Constants { static let endOfRouteHeight: CGFloat = 260.0 static let feedbackTopConstraintPadding: CGFloat = 10.0 @@ -48,7 +47,8 @@ open class NavigationView: UIView { lazy var bannerShowConstraints: [NSLayoutConstraint] = [ self.instructionsBannerView.topAnchor.constraint(equalTo: self.safeTopAnchor), - self.instructionsBannerContentView.topAnchor.constraint(equalTo: self.topAnchor)] + self.instructionsBannerContentView.topAnchor.constraint(equalTo: self.topAnchor) + ] lazy var bannerHideConstraints: [NSLayoutConstraint] = [ self.informationStackView.bottomAnchor.constraint(equalTo: self.topAnchor), @@ -64,7 +64,7 @@ open class NavigationView: UIView { private enum Images { static let overview = UIImage(named: "overview", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate) static let volumeUp = UIImage(named: "volume_up", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate) - static let volumeOff = UIImage(named: "volume_off", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate) + static let volumeOff = UIImage(named: "volume_off", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate) static let feedback = UIImage(named: "feedback", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate) } @@ -74,9 +74,9 @@ open class NavigationView: UIView { lazy var mapView: NavigationMapView = { let map: NavigationMapView = .forAutoLayout(frame: self.bounds) - map.delegate = delegate - map.navigationMapDelegate = delegate - map.courseTrackingDelegate = delegate + map.delegate = self.delegate + map.navigationMapDelegate = self.delegate + map.courseTrackingDelegate = self.delegate map.showsUserLocation = true return map }() @@ -85,7 +85,7 @@ open class NavigationView: UIView { lazy var instructionsBannerView: InstructionsBannerView = { let banner: InstructionsBannerView = .forAutoLayout() - banner.delegate = delegate + banner.delegate = self.delegate return banner }() @@ -106,7 +106,7 @@ open class NavigationView: UIView { lazy var nextBannerView: NextBannerView = .forAutoLayout(hidden: true) lazy var statusView: StatusView = { let view: StatusView = .forAutoLayout() - view.delegate = delegate + view.delegate = self.delegate view.isHidden = true return view }() @@ -125,12 +125,11 @@ open class NavigationView: UIView { let view: BottomBannerView = .forAutoLayout() view.cancelButton.addTarget(self, action: Actions.cancelButton, for: .touchUpInside) return view - }() + }() - weak var delegate: NavigationViewDelegate? { didSet { - updateDelegates() + self.updateDelegates() } } @@ -142,46 +141,45 @@ open class NavigationView: UIView { oldValue?.removeFromSuperview() if let eor = endOfRouteView { addSubview(eor) } - endOfRouteView?.translatesAutoresizingMaskIntoConstraints = false + self.endOfRouteView?.translatesAutoresizingMaskIntoConstraints = false } } - //MARK: - Initializers + // MARK: - Initializers convenience init(delegate: NavigationViewDelegate) { self.init(frame: .zero) self.delegate = delegate - updateDelegates() //this needs to be called because didSet's do not fire in init contexts. + self.updateDelegates() // this needs to be called because didSet's do not fire in init contexts. } override init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { - setupViews() + self.setupViews() setupConstraints() } func setupStackViews() { - - setupInformationStackView() - floatingStackView.addArrangedSubviews([overviewButton, muteButton, reportButton]) + self.setupInformationStackView() + self.floatingStackView.addArrangedSubviews([self.overviewButton, self.muteButton, self.reportButton]) } func setupInformationStackView() { let informationChildren: [UIView] = [instructionsBannerView, lanesView, nextBannerView, statusView] - informationStackView.addArrangedSubviews(informationChildren) + self.informationStackView.addArrangedSubviews(informationChildren) - informationChildren.forEach { - $0.leadingAnchor.constraint(equalTo: informationStackView.leadingAnchor).isActive = true - $0.trailingAnchor.constraint(equalTo: informationStackView.trailingAnchor).isActive = true + for informationChild in informationChildren { + informationChild.leadingAnchor.constraint(equalTo: self.informationStackView.leadingAnchor).isActive = true + informationChild.trailingAnchor.constraint(equalTo: self.informationStackView.trailingAnchor).isActive = true } } @@ -194,8 +192,8 @@ open class NavigationView: UIView { } func setupViews() { - setupStackViews() - setupContainers() + self.setupStackViews() + self.setupContainers() let subviews: [UIView] = [ mapView, @@ -210,25 +208,25 @@ open class NavigationView: UIView { subviews.forEach(addSubview(_:)) } - open override func prepareForInterfaceBuilder() { + override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() DayStyle().apply() - [mapView, instructionsBannerView, lanesView, bottomBannerView, nextBannerView].forEach { $0.prepareForInterfaceBuilder() } - wayNameView.text = "Street Label" + [self.mapView, self.instructionsBannerView, self.lanesView, self.bottomBannerView, self.nextBannerView].forEach { $0.prepareForInterfaceBuilder() } + self.wayNameView.text = "Street Label" } @objc func cancelButtonTapped(_ sender: CancelButton) { - delegate?.navigationView(self, didTapCancelButton: bottomBannerView.cancelButton) + self.delegate?.navigationView(self, didTapCancelButton: self.bottomBannerView.cancelButton) } private func updateDelegates() { - mapView.delegate = delegate - mapView.navigationMapDelegate = delegate - mapView.courseTrackingDelegate = delegate - instructionsBannerView.delegate = delegate - instructionsBannerView.instructionDelegate = delegate - nextBannerView.instructionDelegate = delegate - statusView.delegate = delegate + self.mapView.delegate = self.delegate + self.mapView.navigationMapDelegate = self.delegate + self.mapView.courseTrackingDelegate = self.delegate + self.instructionsBannerView.delegate = self.delegate + self.instructionsBannerView.instructionDelegate = self.delegate + self.nextBannerView.instructionDelegate = self.delegate + self.statusView.delegate = self.delegate } } diff --git a/MapboxNavigation/NavigationViewController.swift b/MapboxNavigation/NavigationViewController.swift index bdc445483..7dc065358 100644 --- a/MapboxNavigation/NavigationViewController.swift +++ b/MapboxNavigation/NavigationViewController.swift @@ -1,7 +1,7 @@ -import UIKit import MapboxCoreNavigation import MapboxDirections import MapLibre +import UIKit #if canImport(CarPlay) import CarPlay #endif @@ -33,14 +33,14 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { optional func navigationViewController(_ navigationViewController: NavigationViewController, didArriveAt waypoint: Waypoint) -> Bool /** - Returns whether the navigation view controller should be allowed to calculate a new route. + Returns whether the navigation view controller should be allowed to calculate a new route. - If implemented, this method is called as soon as the navigation view controller detects that the user is off the predetermined route. Implement this method to conditionally prevent rerouting. If this method returns `true`, `navigationViewController(_:willRerouteFrom:)` will be called immediately afterwards. + If implemented, this method is called as soon as the navigation view controller detects that the user is off the predetermined route. Implement this method to conditionally prevent rerouting. If this method returns `true`, `navigationViewController(_:willRerouteFrom:)` will be called immediately afterwards. - - parameter navigationViewController: The navigation view controller that has detected the need to calculate a new route. - - parameter location: The user’s current location. - - returns: True to allow the navigation view controller to calculate a new route; false to keep tracking the current route. - */ + - parameter navigationViewController: The navigation view controller that has detected the need to calculate a new route. + - parameter location: The user’s current location. + - returns: True to allow the navigation view controller to calculate a new route; false to keep tracking the current route. + */ @objc(navigationViewController:shouldRerouteFromLocation:) optional func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool @@ -192,22 +192,21 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate { */ @objc(MBNavigationViewController) open class NavigationViewController: UIViewController { - - /** + /** A `Route` object constructed by [MapboxDirections](https://mapbox.github.io/mapbox-navigation-ios/directions/). In cases where you need to update the route after navigation has started you can set a new `route` here and `NavigationViewController` will update its UI accordingly. */ @objc public var route: Route! { didSet { - if routeController == nil { - routeController = RouteController(along: route, directions: directions, locationManager: locationManager) - routeController.delegate = self + if self.routeController == nil { + self.routeController = RouteController(along: self.route, directions: self.directions, locationManager: self.locationManager) + self.routeController.delegate = self } else { - routeController.routeProgress = RouteProgress(route: route) + self.routeController.routeProgress = RouteProgress(route: self.route) } - NavigationSettings.shared.distanceUnit = route.routeOptions.locale.usesMetric ? .kilometer : .mile - mapViewController?.notifyDidReroute(route: route) + NavigationSettings.shared.distanceUnit = self.route.routeOptions.locale.usesMetric ? .kilometer : .mile + self.mapViewController?.notifyDidReroute(route: self.route) } } @@ -245,7 +244,7 @@ open class NavigationViewController: UIViewController { */ @objc public var routeController: RouteController! { didSet { - mapViewController?.routeController = routeController + self.mapViewController?.routeController = self.routeController } } @@ -255,9 +254,7 @@ open class NavigationViewController: UIViewController { - note: Do not change this map view’s delegate. */ @objc public var mapView: NavigationMapView? { - get { - return mapViewController?.mapView - } + self.mapViewController?.mapView } /** @@ -277,17 +274,17 @@ open class NavigationViewController: UIViewController { */ @objc public var showsReportFeedback: Bool = true { didSet { - mapViewController?.reportButton.isHidden = !showsReportFeedback - showsEndOfRouteFeedback = showsReportFeedback + self.mapViewController?.reportButton.isHidden = !self.showsReportFeedback + self.showsEndOfRouteFeedback = self.showsReportFeedback } } /** - Shows End of route Feedback UI when the route controller arrives at the final destination. Defaults to `true.` - */ + Shows End of route Feedback UI when the route controller arrives at the final destination. Defaults to `true.` + */ @objc public var showsEndOfRouteFeedback: Bool = true { didSet { - mapViewController?.showsEndOfRoute = showsEndOfRouteFeedback + self.mapViewController?.showsEndOfRoute = self.showsEndOfRouteFeedback } } @@ -296,7 +293,7 @@ open class NavigationViewController: UIViewController { */ @objc public var automaticallyAdjustsStyleForTimeOfDay = true { didSet { - styleManager.automaticallyAdjustsStyleForTimeOfDay = automaticallyAdjustsStyleForTimeOfDay + self.styleManager.automaticallyAdjustsStyleForTimeOfDay = self.automaticallyAdjustsStyleForTimeOfDay } } @@ -310,15 +307,15 @@ open class NavigationViewController: UIViewController { */ @objc public var isUsedInConjunctionWithCarPlayWindow = false { didSet { - mapViewController?.isUsedInConjunctionWithCarPlayWindow = isUsedInConjunctionWithCarPlayWindow + self.mapViewController?.isUsedInConjunctionWithCarPlayWindow = self.isUsedInConjunctionWithCarPlayWindow } } var isConnectedToCarPlay: Bool { if #available(iOS 12.0, *) { - return CarPlayManager.shared.isConnectedToCarPlay + CarPlayManager.shared.isConnectedToCarPlay } else { - return false + false } } @@ -335,18 +332,16 @@ open class NavigationViewController: UIViewController { var currentStatusBarStyle: UIStatusBarStyle = .default { didSet { - mapViewController?.instructionsBannerView.backgroundColor = InstructionsBannerView.appearance().backgroundColor - mapViewController?.instructionsBannerContentView.backgroundColor = InstructionsBannerContentView.appearance().backgroundColor + self.mapViewController?.instructionsBannerView.backgroundColor = InstructionsBannerView.appearance().backgroundColor + self.mapViewController?.instructionsBannerContentView.backgroundColor = InstructionsBannerContentView.appearance().backgroundColor } } - open override var preferredStatusBarStyle: UIStatusBarStyle { - get { - return currentStatusBarStyle - } + override open var preferredStatusBarStyle: UIStatusBarStyle { + self.currentStatusBarStyle } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @@ -356,13 +351,12 @@ open class NavigationViewController: UIViewController { See [Mapbox Directions](https://mapbox.github.io/mapbox-navigation-ios/directions/) for further information. */ @objc(initWithRoute:directions:styles:routeController:locationManager:voiceController:) - required public init(for route: Route, + public required init(for route: Route, directions: Directions = Directions.shared, styles: [Style]? = [DayStyle(), NightStyle()], routeController: RouteController? = nil, locationManager: NavigationLocationManager? = nil, voiceController: RouteVoiceController? = nil) { - super.init(nibName: nil, bundle: nil) self.locationManager = locationManager ?? NavigationLocationManager() @@ -390,7 +384,7 @@ open class NavigationViewController: UIViewController { view.addSubview(mapSubview) mapSubview.pinInSuperview() - mapViewController.reportButton.isHidden = !showsReportFeedback + mapViewController.reportButton.isHidden = !self.showsReportFeedback self.styleManager = StyleManager(self) self.styleManager.styles = styles ?? [DayStyle(), NightStyle()] @@ -408,44 +402,44 @@ open class NavigationViewController: UIViewController { super.viewDidLoad() // Initialize voice controller if it hasn't been overridden. // This is optional and lazy so it can be mutated by the developer after init. - _ = voiceController - resumeNotifications() + _ = self.voiceController + self.resumeNotifications() view.clipsToBounds = true } - open override func viewWillAppear(_ animated: Bool) { + override open func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - if shouldManageApplicationIdleTimer { + if self.shouldManageApplicationIdleTimer { UIApplication.shared.isIdleTimerDisabled = true } - if routeController.locationManager is SimulatedLocationManager { + if self.routeController.locationManager is SimulatedLocationManager { let localized = String.Localized.simulationStatus(speed: 1) - mapViewController?.statusView.show(localized, showSpinner: false, interactive: true) + self.mapViewController?.statusView.show(localized, showSpinner: false, interactive: true) } } - open override func viewWillDisappear(_ animated: Bool) { + override open func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - if shouldManageApplicationIdleTimer { + if self.shouldManageApplicationIdleTimer { UIApplication.shared.isIdleTimerDisabled = false } - routeController.suspendLocationUpdates() + self.routeController.suspendLocationUpdates() } // MARK: Route controller notifications func resumeNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(notification:)), name: .routeControllerProgressDidChange, object: routeController) - NotificationCenter.default.addObserver(self, selector: #selector(didPassInstructionPoint(notification:)), name: .routeControllerDidPassSpokenInstructionPoint, object: routeController) + NotificationCenter.default.addObserver(self, selector: #selector(self.progressDidChange(notification:)), name: .routeControllerProgressDidChange, object: self.routeController) + NotificationCenter.default.addObserver(self, selector: #selector(self.didPassInstructionPoint(notification:)), name: .routeControllerDidPassSpokenInstructionPoint, object: self.routeController) } func suspendNotifications() { - NotificationCenter.default.removeObserver(self, name: .routeControllerProgressDidChange, object: routeController) - NotificationCenter.default.removeObserver(self, name: .routeControllerDidPassSpokenInstructionPoint, object: routeController) + NotificationCenter.default.removeObserver(self, name: .routeControllerProgressDidChange, object: self.routeController) + NotificationCenter.default.removeObserver(self, name: .routeControllerDidPassSpokenInstructionPoint, object: self.routeController) } @objc func progressDidChange(notification: NSNotification) { @@ -453,34 +447,34 @@ open class NavigationViewController: UIViewController { let location = notification.userInfo![RouteControllerNotificationUserInfoKey.locationKey] as! CLLocation let secondsRemaining = routeProgress.currentLegProgress.currentStepProgress.durationRemaining - mapViewController?.notifyDidChange(routeProgress: routeProgress, location: location, secondsRemaining: secondsRemaining) + self.mapViewController?.notifyDidChange(routeProgress: routeProgress, location: location, secondsRemaining: secondsRemaining) // If the user has arrived, don't snap the user puck. // In the case the user drives beyond the waypoint, // we should accurately depict this. - let shouldPreventReroutesWhenArrivingAtWaypoint = routeController.delegate?.routeController?(routeController, shouldPreventReroutesWhenArrivingAt: routeController.routeProgress.currentLeg.destination) ?? true - let userHasArrivedAndShouldPreventRerouting = shouldPreventReroutesWhenArrivingAtWaypoint && !routeController.routeProgress.currentLegProgress.userHasArrivedAtWaypoint + let shouldPreventReroutesWhenArrivingAtWaypoint = self.routeController.delegate?.routeController?(self.routeController, shouldPreventReroutesWhenArrivingAt: self.routeController.routeProgress.currentLeg.destination) ?? true + let userHasArrivedAndShouldPreventRerouting = shouldPreventReroutesWhenArrivingAtWaypoint && !self.routeController.routeProgress.currentLegProgress.userHasArrivedAtWaypoint - if snapsUserLocationAnnotationToRoute, - userHasArrivedAndShouldPreventRerouting { - mapViewController?.mapView.updateCourseTracking(location: location, animated: true) + if self.snapsUserLocationAnnotationToRoute, + userHasArrivedAndShouldPreventRerouting { + self.mapViewController?.mapView.updateCourseTracking(location: location, animated: true) } } @objc func didPassInstructionPoint(notification: NSNotification) { let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as! RouteProgress - mapViewController?.updateCameraAltitude(for: routeProgress) + self.mapViewController?.updateCameraAltitude(for: routeProgress) - clearStaleNotifications() + self.clearStaleNotifications() if routeProgress.currentLegProgress.currentStepProgress.durationRemaining <= RouteControllerHighAlertInterval { - scheduleLocalNotification(about: routeProgress.currentLegProgress.currentStep, legIndex: routeProgress.legIndex, numberOfLegs: routeProgress.route.legs.count) + self.scheduleLocalNotification(about: routeProgress.currentLegProgress.currentStep, legIndex: routeProgress.legIndex, numberOfLegs: routeProgress.route.legs.count) } } func scheduleLocalNotification(about step: RouteStep, legIndex: Int?, numberOfLegs: Int?) { - guard sendsNotifications else { return } + guard self.sendsNotifications else { return } guard UIApplication.shared.applicationState == .background else { return } guard let text = step.instructionsSpokenAlongStep?.last?.text else { return } @@ -488,14 +482,14 @@ open class NavigationViewController: UIViewController { notification.alertBody = text notification.fireDate = Date() - clearStaleNotifications() + self.clearStaleNotifications() UIApplication.shared.cancelAllLocalNotifications() UIApplication.shared.scheduleLocalNotification(notification) } func clearStaleNotifications() { - guard sendsNotifications else { return } + guard self.sendsNotifications else { return } // Remove all outstanding notifications from notification center. // This will only work if it's set to 1 and then back to 0. // This way, there is always just one notification. @@ -510,17 +504,14 @@ open class NavigationViewController: UIViewController { */ @available(iOS 12.0, *) public class func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith routeController: RouteController, window: UIWindow) { - if let navigationViewController = window.viewControllerInStack(of: NavigationViewController.self) { // Open StepsViewController on iPhone if NavigationViewController is being presented navigationViewController.isUsedInConjunctionWithCarPlayWindow = true } else { - // Start NavigationViewController and open StepsViewController if navigation has not started on iPhone yet. let navigationViewControllerExistsInStack = window.viewControllerInStack(of: NavigationViewController.self) != nil if !navigationViewControllerExistsInStack { - let locationManager = routeController.locationManager.copy() as! NavigationLocationManager let directions = routeController.directions let route = routeController.routeProgress.route @@ -545,50 +536,51 @@ open class NavigationViewController: UIViewController { #endif } -//MARK: - RouteMapViewControllerDelegate +// MARK: - RouteMapViewControllerDelegate + extension NavigationViewController: RouteMapViewControllerDelegate { public func navigationMapView(_ mapView: NavigationMapView, routeCasingStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - return delegate?.navigationViewController?(self, routeCasingStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationViewController?(self, routeCasingStyleLayerWithIdentifier: identifier, source: source) } public func navigationMapView(_ mapView: NavigationMapView, routeStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - return delegate?.navigationViewController?(self, routeStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationViewController?(self, routeStyleLayerWithIdentifier: identifier, source: source) } public func navigationMapView(_ mapView: NavigationMapView, didSelect route: Route) { - delegate?.navigationViewController?(self, didSelect: route) + self.delegate?.navigationViewController?(self, didSelect: route) } @objc public func navigationMapView(_ mapView: NavigationMapView, shapeFor routes: [Route]) -> MLNShape? { - return delegate?.navigationViewController?(self, shapeFor: routes) + self.delegate?.navigationViewController?(self, shapeFor: routes) } @objc public func navigationMapView(_ mapView: NavigationMapView, simplifiedShapeFor route: Route) -> MLNShape? { - return delegate?.navigationViewController?(self, simplifiedShapeFor: route) + self.delegate?.navigationViewController?(self, simplifiedShapeFor: route) } public func navigationMapView(_ mapView: NavigationMapView, waypointStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - return delegate?.navigationViewController?(self, waypointStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationViewController?(self, waypointStyleLayerWithIdentifier: identifier, source: source) } public func navigationMapView(_ mapView: NavigationMapView, waypointSymbolStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - return delegate?.navigationViewController?(self, waypointSymbolStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationViewController?(self, waypointSymbolStyleLayerWithIdentifier: identifier, source: source) } @objc public func navigationMapView(_ mapView: NavigationMapView, shapeFor waypoints: [Waypoint], legIndex: Int) -> MLNShape? { - return delegate?.navigationViewController?(self, shapeFor: waypoints, legIndex: legIndex) + self.delegate?.navigationViewController?(self, shapeFor: waypoints, legIndex: legIndex) } @objc public func navigationMapView(_ mapView: MLNMapView, imageFor annotation: MLNAnnotation) -> MLNAnnotationImage? { - return delegate?.navigationViewController?(self, imageFor: annotation) + self.delegate?.navigationViewController?(self, imageFor: annotation) } @objc public func navigationMapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? { - return delegate?.navigationViewController?(self, viewFor: annotation) + self.delegate?.navigationViewController?(self, viewFor: annotation) } func mapViewControllerDidDismiss(_ mapViewController: RouteMapViewController, byCanceling canceled: Bool) { - if delegate?.navigationViewControllerDidDismiss?(self, byCanceling: canceled) != nil { + if self.delegate?.navigationViewControllerDidDismiss?(self, byCanceling: canceled) != nil { // The receiver should handle dismissal of the NavigationViewController } else { dismiss(animated: true, completion: nil) @@ -596,11 +588,11 @@ extension NavigationViewController: RouteMapViewControllerDelegate { } public func navigationMapViewUserAnchorPoint(_ mapView: NavigationMapView) -> CGPoint { - return delegate?.navigationViewController?(self, mapViewUserAnchorPoint: mapView) ?? .zero + self.delegate?.navigationViewController?(self, mapViewUserAnchorPoint: mapView) ?? .zero } func mapViewControllerShouldAnnotateSpokenInstructions(_ routeMapViewController: RouteMapViewController) -> Bool { - return annotatesSpokenInstructions + self.annotatesSpokenInstructions } @objc func mapViewController(_ mapViewController: RouteMapViewController, roadNameAt location: CLLocation) -> String? { @@ -611,56 +603,56 @@ extension NavigationViewController: RouteMapViewControllerDelegate { } @objc public func label(_ label: InstructionLabel, willPresent instruction: VisualInstruction, as presented: NSAttributedString) -> NSAttributedString? { - return delegate?.label?(label, willPresent: instruction, as: presented) + self.delegate?.label?(label, willPresent: instruction, as: presented) } } -//MARK: - RouteControllerDelegate +// MARK: - RouteControllerDelegate + extension NavigationViewController: RouteControllerDelegate { @objc public func routeController(_ routeController: RouteController, shouldRerouteFrom location: CLLocation) -> Bool { - return delegate?.navigationViewController?(self, shouldRerouteFrom: location) ?? true + self.delegate?.navigationViewController?(self, shouldRerouteFrom: location) ?? true } @objc public func routeController(_ routeController: RouteController, willRerouteFrom location: CLLocation) { - delegate?.navigationViewController?(self, willRerouteFrom: location) + self.delegate?.navigationViewController?(self, willRerouteFrom: location) } @objc public func routeController(_ routeController: RouteController, didRerouteAlong route: Route) { - mapViewController?.notifyDidReroute(route: route) - delegate?.navigationViewController?(self, didRerouteAlong: route) + self.mapViewController?.notifyDidReroute(route: route) + self.delegate?.navigationViewController?(self, didRerouteAlong: route) } @objc public func routeController(_ routeController: RouteController, didFailToRerouteWith error: Error) { - delegate?.navigationViewController?(self, didFailToRerouteWith: error) + self.delegate?.navigationViewController?(self, didFailToRerouteWith: error) } - @objc public func routeController(_ routeController: RouteController, shouldDiscard location: CLLocation) -> Bool { - return delegate?.navigationViewController?(self, shouldDiscard: location) ?? true + @objc public func routeController(_ routeController: RouteController, shouldDiscard location: CLLocation) -> Bool { + self.delegate?.navigationViewController?(self, shouldDiscard: location) ?? true } @objc public func routeController(_ routeController: RouteController, didUpdate locations: [CLLocation]) { - // If the user has arrived, don't snap the user puck. // In the case the user drives beyond the waypoint, // we should accurately depict this. let shouldPreventReroutesWhenArrivingAtWaypoint = routeController.delegate?.routeController?(routeController, shouldPreventReroutesWhenArrivingAt: routeController.routeProgress.currentLeg.destination) ?? true let userHasArrivedAndShouldPreventRerouting = shouldPreventReroutesWhenArrivingAtWaypoint && !routeController.routeProgress.currentLegProgress.userHasArrivedAtWaypoint - if snapsUserLocationAnnotationToRoute, - let snappedLocation = routeController.location ?? locations.last, - let rawLocation = locations.last, - userHasArrivedAndShouldPreventRerouting { - mapViewController?.labelCurrentRoad(at: rawLocation, for: snappedLocation) + if self.snapsUserLocationAnnotationToRoute, + let snappedLocation = routeController.location ?? locations.last, + let rawLocation = locations.last, + userHasArrivedAndShouldPreventRerouting { + self.mapViewController?.labelCurrentRoad(at: rawLocation, for: snappedLocation) } else if let rawlocation = locations.last { - mapViewController?.labelCurrentRoad(at: rawlocation) + self.mapViewController?.labelCurrentRoad(at: rawlocation) } } @objc public func routeController(_ routeController: RouteController, didArriveAt waypoint: Waypoint) -> Bool { - let advancesToNextLeg = delegate?.navigationViewController?(self, didArriveAt: waypoint) ?? true + let advancesToNextLeg = self.delegate?.navigationViewController?(self, didArriveAt: waypoint) ?? true - if !isConnectedToCarPlay, // CarPlayManager shows rating on CarPlay if it's connected - routeController.routeProgress.isFinalLeg && advancesToNextLeg && showsEndOfRouteFeedback { + if !self.isConnectedToCarPlay, // CarPlayManager shows rating on CarPlay if it's connected + routeController.routeProgress.isFinalLeg, advancesToNextLeg, self.showsEndOfRouteFeedback { self.mapViewController?.showEndOfRoute { _ in } } return advancesToNextLeg @@ -669,40 +661,38 @@ extension NavigationViewController: RouteControllerDelegate { extension NavigationViewController: TunnelIntersectionManagerDelegate { public func tunnelIntersectionManager(_ manager: TunnelIntersectionManager, willEnableAnimationAt location: CLLocation) { - routeController.tunnelIntersectionManager(manager, willEnableAnimationAt: location) - styleManager.applyStyle(type: .night) + self.routeController.tunnelIntersectionManager(manager, willEnableAnimationAt: location) + self.styleManager.applyStyle(type: .night) } public func tunnelIntersectionManager(_ manager: TunnelIntersectionManager, willDisableAnimationAt location: CLLocation) { - routeController.tunnelIntersectionManager(manager, willDisableAnimationAt: location) - styleManager.timeOfDayChanged() + self.routeController.tunnelIntersectionManager(manager, willDisableAnimationAt: location) + self.styleManager.timeOfDayChanged() } } - extension NavigationViewController: StyleManagerDelegate { - public func locationFor(styleManager: StyleManager) -> CLLocation? { if let location = routeController.location { - return location + location } else if let firstCoord = routeController.routeProgress.route.coordinates?.first { - return CLLocation(latitude: firstCoord.latitude, longitude: firstCoord.longitude) + CLLocation(latitude: firstCoord.latitude, longitude: firstCoord.longitude) } else { - return nil + nil } } public func styleManager(_ styleManager: StyleManager, didApply style: Style) { - if mapView?.styleURL != style.mapStyleURL { - mapView?.style?.transition = MLNTransition(duration: 0.5, delay: 0) - mapView?.styleURL = style.mapStyleURL + if self.mapView?.styleURL != style.mapStyleURL { + self.mapView?.style?.transition = MLNTransition(duration: 0.5, delay: 0) + self.mapView?.styleURL = style.mapStyleURL } - currentStatusBarStyle = style.statusBarStyle ?? .default + self.currentStatusBarStyle = style.statusBarStyle ?? .default setNeedsStatusBarAppearanceUpdate() } public func styleManagerDidRefreshAppearance(_ styleManager: StyleManager) { - mapView?.reloadStyle(self) + self.mapView?.reloadStyle(self) } } diff --git a/MapboxNavigation/NavigationViewLayout.swift b/MapboxNavigation/NavigationViewLayout.swift index 9d96f84f7..e180fcf5b 100644 --- a/MapboxNavigation/NavigationViewLayout.swift +++ b/MapboxNavigation/NavigationViewLayout.swift @@ -41,12 +41,11 @@ extension NavigationView { } func constrainEndOfRoute() { - self.endOfRouteHideConstraint?.isActive = true + endOfRouteHideConstraint?.isActive = true endOfRouteView?.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true endOfRouteView?.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - self.endOfRouteHeightConstraint?.isActive = true - + endOfRouteHeightConstraint?.isActive = true } } diff --git a/MapboxNavigation/NextBannerView.swift b/MapboxNavigation/NextBannerView.swift index 377b49fb4..add95e1fa 100644 --- a/MapboxNavigation/NextBannerView.swift +++ b/MapboxNavigation/NextBannerView.swift @@ -1,38 +1,37 @@ -import UIKit -import MapboxDirections import MapboxCoreNavigation +import MapboxDirections +import UIKit /// :nodoc: @objc(MBNextInstructionLabel) -open class NextInstructionLabel: InstructionLabel { } +open class NextInstructionLabel: InstructionLabel {} /// :nodoc: @IBDesignable @objc(MBNextBannerView) open class NextBannerView: UIView { - weak var maneuverView: ManeuverView! weak var instructionLabel: NextInstructionLabel! weak var bottomSeparatorView: SeparatorView! weak var instructionDelegate: VisualInstructionDelegate? { didSet { - instructionLabel.instructionDelegate = instructionDelegate + self.instructionLabel.instructionDelegate = self.instructionDelegate } } override init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { - setupViews() - setupLayout() + self.setupViews() + self.setupLayout() } func setupViews() { @@ -44,7 +43,7 @@ open class NextBannerView: UIView { self.maneuverView = maneuverView let instructionLabel = NextInstructionLabel() - instructionLabel.instructionDelegate = instructionDelegate + instructionLabel.instructionDelegate = self.instructionDelegate instructionLabel.shieldHeight = instructionLabel.font.pointSize instructionLabel.translatesAutoresizingMaskIntoConstraints = false addSubview(instructionLabel) @@ -52,7 +51,7 @@ open class NextBannerView: UIView { instructionLabel.availableBounds = { [unowned self] in // Available width H:|-padding-maneuverView-padding-availableWidth-padding-| - let availableWidth = self.bounds.width - BaseInstructionsBannerView.maneuverViewSize.width - BaseInstructionsBannerView.padding * 3 + let availableWidth = bounds.width - BaseInstructionsBannerView.maneuverViewSize.width - BaseInstructionsBannerView.padding * 3 return CGRect(x: 0, y: 0, width: availableWidth, height: self.instructionLabel.font.lineHeight) } @@ -64,10 +63,10 @@ open class NextBannerView: UIView { override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() - maneuverView.isEnd = true + self.maneuverView.isEnd = true let component = VisualInstructionComponent(type: .text, text: "Next step", imageURL: nil, abbreviation: nil, abbreviationPriority: NSNotFound) let instruction = VisualInstruction(text: nil, maneuverType: .none, maneuverDirection: .none, components: [component]) - instructionLabel.instruction = instruction + self.instructionLabel.instruction = instruction } func setupLayout() { @@ -76,19 +75,19 @@ open class NextBannerView: UIView { heightConstraint.isActive = true let midX = BaseInstructionsBannerView.padding + BaseInstructionsBannerView.maneuverViewSize.width / 2 - maneuverView.centerXAnchor.constraint(equalTo: leadingAnchor, constant: midX).isActive = true - maneuverView.heightAnchor.constraint(equalToConstant: 22).isActive = true - maneuverView.widthAnchor.constraint(equalToConstant: 22).isActive = true - maneuverView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + self.maneuverView.centerXAnchor.constraint(equalTo: leadingAnchor, constant: midX).isActive = true + self.maneuverView.heightAnchor.constraint(equalToConstant: 22).isActive = true + self.maneuverView.widthAnchor.constraint(equalToConstant: 22).isActive = true + self.maneuverView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - instructionLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 70).isActive = true - instructionLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - instructionLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true + self.instructionLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 70).isActive = true + self.instructionLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + self.instructionLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true - bottomSeparatorView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - bottomSeparatorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - bottomSeparatorView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - bottomSeparatorView.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale).isActive = true + self.bottomSeparatorView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + self.bottomSeparatorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + self.bottomSeparatorView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + self.bottomSeparatorView.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale).isActive = true } /** @@ -97,14 +96,14 @@ open class NextBannerView: UIView { @objc(updateForVisualInstructionBanner:) public func update(for visualInstruction: VisualInstructionBanner?) { guard let tertiaryInstruction = visualInstruction?.tertiaryInstruction, !tertiaryInstruction.containsLaneIndications else { - hide() + self.hide() return } - maneuverView.visualInstruction = tertiaryInstruction - maneuverView.drivingSide = visualInstruction?.drivingSide ?? .right - instructionLabel.instruction = tertiaryInstruction - show() + self.maneuverView.visualInstruction = tertiaryInstruction + self.maneuverView.drivingSide = visualInstruction?.drivingSide ?? .right + self.instructionLabel.instruction = tertiaryInstruction + self.show() } public func show() { @@ -120,5 +119,4 @@ open class NextBannerView: UIView { self.isHidden = true }, completion: nil) } - } diff --git a/MapboxNavigation/RatingControl.swift b/MapboxNavigation/RatingControl.swift index fd9f2c127..12560faf9 100644 --- a/MapboxNavigation/RatingControl.swift +++ b/MapboxNavigation/RatingControl.swift @@ -1,74 +1,78 @@ -import UIKit import CoreGraphics +import UIKit -typealias RatingClosure = (Int) -> Void //rating +typealias RatingClosure = (Int) -> Void // rating /*@IBDesignable*/ class RatingControl: UIStackView { - // MARK: Constants + static let defaultSize = CGSize(width: 32.0, height: 32.0) private let starTemplate = UIImage(named: "star", in: .mapboxNavigation, compatibleWith: nil) // MARK: Properties + private var stars = [UIButton]() var didChangeRating: RatingClosure? var rating: Int = 0 { didSet { - updateSelectionStates() - didChangeRating?(rating) + self.updateSelectionStates() + self.didChangeRating?(self.rating) } } - @objc dynamic public var selectedColor: UIColor = #colorLiteral(red: 0.1205472574, green: 0.2422055006, blue: 0.3489340544, alpha: 1) { + @objc public dynamic var selectedColor: UIColor = #colorLiteral(red: 0.1205472574, green: 0.2422055006, blue: 0.3489340544, alpha: 1) { didSet { updateSelectionStates() } } - @objc dynamic public var normalColor: UIColor = #colorLiteral(red: 0.8508961797, green: 0.8510394692, blue: 0.850877285, alpha: 1) { + + @objc public dynamic var normalColor: UIColor = #colorLiteral(red: 0.8508961797, green: 0.8510394692, blue: 0.850877285, alpha: 1) { didSet { updateSelectionStates() } } - @objc dynamic public var starSize: CGSize = defaultSize { + @objc public dynamic var starSize: CGSize = defaultSize { didSet { - configureStars() + self.configureStars() } } - @objc dynamic public var starCount: Int = 5 { + @objc public dynamic var starCount: Int = 5 { didSet { - configureStars() + self.configureStars() } } // MARK: Initializers - public override init(frame: CGRect) { + + override public init(frame: CGRect) { super.init(frame: frame) - configureStars() + self.configureStars() } - required public init(coder: NSCoder) { + public required init(coder: NSCoder) { super.init(coder: coder) - configureStars() + self.configureStars() } // MARK: Private Functions + private func configureStars() { - removeStars() - addStars() - updateSelectionStates() + self.removeStars() + self.addStars() + self.updateSelectionStates() } private func addStars() { - for index in 0.. Void - var labelRoadNameCompletionHandler: (LabelRoadNameCompletionHandler)? + var labelRoadNameCompletionHandler: LabelRoadNameCompletionHandler? convenience init(routeController: RouteController, delegate: RouteMapViewControllerDelegate? = nil) { self.init() @@ -113,7 +112,6 @@ class RouteMapViewController: UIViewController { automaticallyAdjustsScrollViewInsets = false } - override func loadView() { view = NavigationView(delegate: self) view.frame = parent?.view.bounds ?? UIScreen.main.bounds @@ -122,19 +120,18 @@ class RouteMapViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - mapView.contentInset = contentInsets + self.mapView.contentInset = self.contentInsets view.layoutIfNeeded() - mapView.tracksUserCourse = true - + self.mapView.tracksUserCourse = true - distanceFormatter.numberFormatter.locale = .nationalizedCurrent + self.distanceFormatter.numberFormatter.locale = .nationalizedCurrent - navigationView.overviewButton.addTarget(self, action: Actions.overview, for: .touchUpInside) - navigationView.muteButton.addTarget(self, action: Actions.mute, for: .touchUpInside) - navigationView.resumeButton.addTarget(self, action: Actions.recenter, for: .touchUpInside) - resumeNotifications() - notifyUserAboutLowVolume() + self.navigationView.overviewButton.addTarget(self, action: Actions.overview, for: .touchUpInside) + self.navigationView.muteButton.addTarget(self, action: Actions.mute, for: .touchUpInside) + self.navigationView.resumeButton.addTarget(self, action: Actions.recenter, for: .touchUpInside) + self.resumeNotifications() + self.notifyUserAboutLowVolume() } deinit { @@ -147,45 +144,45 @@ class RouteMapViewController: UIViewController { resetETATimer() - navigationView.muteButton.isSelected = NavigationSettings.shared.voiceMuted - mapView.compassView.isHidden = true + self.navigationView.muteButton.isSelected = NavigationSettings.shared.voiceMuted + self.mapView.compassView.isHidden = true - mapView.tracksUserCourse = true + self.mapView.tracksUserCourse = true if let camera = pendingCamera { - mapView.camera = camera + self.mapView.camera = camera } else if let location = routeController.location, location.course > 0 { - mapView.updateCourseTracking(location: location, animated: false) + self.mapView.updateCourseTracking(location: location, animated: false) } else if let coordinates = routeController.routeProgress.currentLegProgress.currentStep.coordinates, let firstCoordinate = coordinates.first, coordinates.count > 1 { let secondCoordinate = coordinates[1] let course = firstCoordinate.direction(to: secondCoordinate) let newLocation = CLLocation(coordinate: routeController.location?.coordinate ?? firstCoordinate, altitude: 0, horizontalAccuracy: 0, verticalAccuracy: 0, course: course, speed: 0, timestamp: Date()) - mapView.updateCourseTracking(location: newLocation, animated: false) + self.mapView.updateCourseTracking(location: newLocation, animated: false) } else { - mapView.setCamera(tiltedCamera, animated: false) + self.mapView.setCamera(self.tiltedCamera, animated: false) } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - annotatesSpokenInstructions = delegate?.mapViewControllerShouldAnnotateSpokenInstructions(self) ?? false + self.annotatesSpokenInstructions = self.delegate?.mapViewControllerShouldAnnotateSpokenInstructions(self) ?? false showRouteIfNeeded() - currentLegIndexMapped = routeController.routeProgress.legIndex - currentStepIndexMapped = routeController.routeProgress.currentLegProgress.stepIndex + self.currentLegIndexMapped = self.routeController.routeProgress.legIndex + self.currentStepIndexMapped = self.routeController.routeProgress.currentLegProgress.stepIndex } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - removeTimer() + self.removeTimer() } func resumeNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(willReroute(notification:)), name: .routeControllerWillReroute, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(didReroute(notification:)), name: .routeControllerDidReroute, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(rerouteDidFail(notification:)), name: .routeControllerDidFailToReroute, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(removeTimer), name: UIApplication.didEnterBackgroundNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(updateInstructionsBanner(notification:)), name: .routeControllerDidPassVisualInstructionPoint, object: routeController) + NotificationCenter.default.addObserver(self, selector: #selector(self.willReroute(notification:)), name: .routeControllerWillReroute, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didReroute(notification:)), name: .routeControllerDidReroute, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.rerouteDidFail(notification:)), name: .routeControllerDidFailToReroute, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.applicationWillEnterForeground(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.removeTimer), name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.updateInstructionsBanner(notification:)), name: .routeControllerDidPassVisualInstructionPoint, object: self.routeController) subscribeToKeyboardNotifications() } @@ -200,38 +197,38 @@ class RouteMapViewController: UIViewController { } @objc func recenter(_ sender: AnyObject) { - mapView.tracksUserCourse = true - mapView.enableFrameByFrameCourseViewTracking(for: 3) - isInOverviewMode = false - updateCameraAltitude(for: routeController.routeProgress) + self.mapView.tracksUserCourse = true + self.mapView.enableFrameByFrameCourseViewTracking(for: 3) + self.isInOverviewMode = false + self.updateCameraAltitude(for: self.routeController.routeProgress) - mapView.addArrow(route: routeController.routeProgress.route, - legIndex: routeController.routeProgress.legIndex, - stepIndex: routeController.routeProgress.currentLegProgress.stepIndex + 1) + self.mapView.addArrow(route: self.routeController.routeProgress.route, + legIndex: self.routeController.routeProgress.legIndex, + stepIndex: self.routeController.routeProgress.currentLegProgress.stepIndex + 1) - removePreviewInstructions() + self.removePreviewInstructions() } @objc func removeTimer() { - updateETATimer?.invalidate() - updateETATimer = nil + self.updateETATimer?.invalidate() + self.updateETATimer = nil } func removePreviewInstructions() { if let view = previewInstructionsView { view.removeFromSuperview() - navigationView.instructionsBannerContentView.backgroundColor = InstructionsBannerView.appearance().backgroundColor - navigationView.instructionsBannerView.delegate = self - previewInstructionsView = nil + self.navigationView.instructionsBannerContentView.backgroundColor = InstructionsBannerView.appearance().backgroundColor + self.navigationView.instructionsBannerView.delegate = self + self.previewInstructionsView = nil } } @objc func toggleOverview(_ sender: Any) { - mapView.enableFrameByFrameCourseViewTracking(for: 3) + self.mapView.enableFrameByFrameCourseViewTracking(for: 3) if let coordinates = routeController.routeProgress.route.coordinates, let userLocation = routeController.locationManager.location?.coordinate { - mapView.setOverheadCameraView(from: userLocation, along: coordinates, for: overheadInsets) + self.mapView.setOverheadCameraView(from: userLocation, along: coordinates, for: self.overheadInsets) } - isInOverviewMode = true + self.isInOverviewMode = true } @objc func toggleMute(_ sender: UIButton) { @@ -243,39 +240,39 @@ class RouteMapViewController: UIViewController { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - mapView.enableFrameByFrameCourseViewTracking(for: 3) + self.mapView.enableFrameByFrameCourseViewTracking(for: 3) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - mapView.setContentInset(contentInsets, animated: true, completionHandler: nil) - mapView.setNeedsUpdateConstraints() + self.mapView.setContentInset(self.contentInsets, animated: true, completionHandler: nil) + self.mapView.setNeedsUpdateConstraints() } func notifyDidReroute(route: Route) { updateETA() - currentStepIndexMapped = 0 + self.currentStepIndexMapped = 0 - instructionsBannerView.updateDistance(for: routeController.routeProgress.currentLegProgress.currentStepProgress) + self.instructionsBannerView.updateDistance(for: self.routeController.routeProgress.currentLegProgress.currentStepProgress) - mapView.addArrow(route: routeController.routeProgress.route, legIndex: routeController.routeProgress.legIndex, stepIndex: routeController.routeProgress.currentLegProgress.stepIndex + 1) - mapView.showRoutes([routeController.routeProgress.route], legIndex: routeController.routeProgress.legIndex) - mapView.showWaypoints(routeController.routeProgress.route) + self.mapView.addArrow(route: self.routeController.routeProgress.route, legIndex: self.routeController.routeProgress.legIndex, stepIndex: self.routeController.routeProgress.currentLegProgress.stepIndex + 1) + self.mapView.showRoutes([self.routeController.routeProgress.route], legIndex: self.routeController.routeProgress.legIndex) + self.mapView.showWaypoints(self.routeController.routeProgress.route) - if annotatesSpokenInstructions { - mapView.showVoiceInstructionsOnMap(route: routeController.routeProgress.route) + if self.annotatesSpokenInstructions { + self.mapView.showVoiceInstructionsOnMap(route: self.routeController.routeProgress.route) } - if isInOverviewMode { + if self.isInOverviewMode { if let coordinates = routeController.routeProgress.route.coordinates, let userLocation = routeController.locationManager.location?.coordinate { - mapView.setOverheadCameraView(from: userLocation, along: coordinates, for: overheadInsets) + self.mapView.setOverheadCameraView(from: userLocation, along: coordinates, for: self.overheadInsets) } } else { - mapView.tracksUserCourse = true - navigationView.wayNameView.isHidden = true + self.mapView.tracksUserCourse = true + self.navigationView.wayNameView.isHidden = true } - stepsViewController?.dismiss { + self.stepsViewController?.dismiss { self.removePreviewInstructions() self.stepsViewController = nil self.navigationView.instructionsBannerView.stepListIndicatorView.isHidden = false @@ -283,167 +280,166 @@ class RouteMapViewController: UIViewController { } @objc func applicationWillEnterForeground(notification: NSNotification) { - mapView.updateCourseTracking(location: routeController.location, animated: false) + self.mapView.updateCourseTracking(location: self.routeController.location, animated: false) resetETATimer() } @objc func willReroute(notification: NSNotification) { let title = NSLocalizedString("REROUTING", bundle: .mapboxNavigation, value: "Rerouting…", comment: "Indicates that rerouting is in progress") - lanesView.hide() - statusView.show(title, showSpinner: true) + self.lanesView.hide() + self.statusView.show(title, showSpinner: true) } @objc func rerouteDidFail(notification: NSNotification) { - statusView.hide() + self.statusView.hide() } func notifyUserAboutLowVolume() { - guard !(routeController.locationManager is SimulatedLocationManager) else { return } + guard !(self.routeController.locationManager is SimulatedLocationManager) else { return } guard !NavigationSettings.shared.voiceMuted else { return } guard AVAudioSession.sharedInstance().outputVolume <= NavigationViewMinimumVolumeForWarning else { return } let title = String.localizedStringWithFormat(NSLocalizedString("DEVICE_VOLUME_LOW", bundle: .mapboxNavigation, value: "%@ Volume Low", comment: "Format string for indicating the device volume is low; 1 = device model"), UIDevice.current.model) - statusView.show(title, showSpinner: false) - statusView.hide(delay: 3, animated: true) + self.statusView.show(title, showSpinner: false) + self.statusView.hide(delay: 3, animated: true) } @objc func didReroute(notification: NSNotification) { - guard self.isViewLoaded else { return } + guard isViewLoaded else { return } if let locationManager = routeController.locationManager as? SimulatedLocationManager { let localized = String.Localized.simulationStatus(speed: Int(locationManager.speedMultiplier)) - showStatus(title: localized, for: .infinity, interactive: true) + self.showStatus(title: localized, for: .infinity, interactive: true) } else { - statusView.hide(delay: 2, animated: true) + self.statusView.hide(delay: 2, animated: true) } if notification.userInfo![RouteControllerNotificationUserInfoKey.isProactiveKey] as! Bool { let title = NSLocalizedString("FASTER_ROUTE_FOUND", bundle: .mapboxNavigation, value: "Faster Route Found", comment: "Indicates a faster route was found") - showStatus(title: title, withSpinner: true, for: 3) + self.showStatus(title: title, withSpinner: true, for: 3) } } @objc func updateInstructionsBanner(notification: NSNotification) { guard let routeProgress = notification.userInfo?[RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress else { return } - instructionsBannerView.update(for: routeProgress.currentLegProgress.currentStepProgress.currentVisualInstruction) - lanesView.update(for: routeProgress.currentLegProgress.currentStepProgress.currentVisualInstruction) - nextBannerView.update(for: routeProgress.currentLegProgress.currentStepProgress.currentVisualInstruction) - + self.instructionsBannerView.update(for: routeProgress.currentLegProgress.currentStepProgress.currentVisualInstruction) + self.lanesView.update(for: routeProgress.currentLegProgress.currentStepProgress.currentVisualInstruction) + self.nextBannerView.update(for: routeProgress.currentLegProgress.currentStepProgress.currentVisualInstruction) } func updateMapOverlays(for routeProgress: RouteProgress) { if routeProgress.currentLegProgress.followOnStep != nil { - mapView.addArrow(route: routeController.routeProgress.route, legIndex: routeController.routeProgress.legIndex, stepIndex: routeController.routeProgress.currentLegProgress.stepIndex + 1) + self.mapView.addArrow(route: self.routeController.routeProgress.route, legIndex: self.routeController.routeProgress.legIndex, stepIndex: self.routeController.routeProgress.currentLegProgress.stepIndex + 1) } else { - mapView.removeArrow() + self.mapView.removeArrow() } } func updateCameraAltitude(for routeProgress: RouteProgress) { - guard mapView.tracksUserCourse else { return } //only adjust when we are actively tracking user course + guard self.mapView.tracksUserCourse else { return } // only adjust when we are actively tracking user course - let zoomOutAltitude = mapView.zoomedOutMotorwayAltitude - let defaultAltitude = mapView.defaultAltitude - let isLongRoad = routeProgress.distanceRemaining >= mapView.longManeuverDistance + let zoomOutAltitude = self.mapView.zoomedOutMotorwayAltitude + let defaultAltitude = self.mapView.defaultAltitude + let isLongRoad = routeProgress.distanceRemaining >= self.mapView.longManeuverDistance let currentStep = routeProgress.currentLegProgress.currentStep let upComingStep = routeProgress.currentLegProgress.upComingStep - //If the user is at the last turn maneuver, the map should zoom in to the default altitude. + // If the user is at the last turn maneuver, the map should zoom in to the default altitude. let currentInstruction = routeProgress.currentLegProgress.currentStepProgress.currentSpokenInstruction - //If the user is on a motorway, not exiting, and their segment is sufficently long, the map should zoom out to the motorway altitude. - //otherwise, zoom in if it's the last instruction on the step. + // If the user is on a motorway, not exiting, and their segment is sufficently long, the map should zoom out to the motorway altitude. + // otherwise, zoom in if it's the last instruction on the step. let currentStepIsMotorway = currentStep.isMotorway let nextStepIsMotorway = upComingStep?.isMotorway ?? false if currentStepIsMotorway, nextStepIsMotorway, isLongRoad { - setCamera(altitude: zoomOutAltitude) + self.setCamera(altitude: zoomOutAltitude) } else if currentInstruction == currentStep.lastInstruction { - setCamera(altitude: defaultAltitude) + self.setCamera(altitude: defaultAltitude) } } private func showStatus(title: String, withSpinner spin: Bool = false, for time: TimeInterval, animated: Bool = true, interactive: Bool = false) { - statusView.show(title, showSpinner: spin, interactive: interactive) + self.statusView.show(title, showSpinner: spin, interactive: interactive) guard time < .infinity else { return } - statusView.hide(delay: time, animated: animated) + self.statusView.hide(delay: time, animated: animated) } private func setCamera(altitude: Double) { - guard mapView.altitude != altitude else { return } - mapView.altitude = altitude + guard self.mapView.altitude != altitude else { return } + self.mapView.altitude = altitude } func mapView(_ mapView: MLNMapView, imageFor annotation: MLNAnnotation) -> MLNAnnotationImage? { - return navigationMapView(mapView, imageFor: annotation) + navigationMapView(mapView, imageFor: annotation) } func mapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? { - return navigationMapView(mapView, viewFor: annotation) + navigationMapView(mapView, viewFor: annotation) } func notifyDidChange(routeProgress: RouteProgress, location: CLLocation, secondsRemaining: TimeInterval) { resetETATimer() updateETA() - instructionsBannerView.updateDistance(for: routeProgress.currentLegProgress.currentStepProgress) + self.instructionsBannerView.updateDistance(for: routeProgress.currentLegProgress.currentStepProgress) - if currentLegIndexMapped != routeProgress.legIndex { - mapView.showWaypoints(routeProgress.route, legIndex: routeProgress.legIndex) - mapView.showRoutes([routeProgress.route], legIndex: routeProgress.legIndex) + if self.currentLegIndexMapped != routeProgress.legIndex { + self.mapView.showWaypoints(routeProgress.route, legIndex: routeProgress.legIndex) + self.mapView.showRoutes([routeProgress.route], legIndex: routeProgress.legIndex) - currentLegIndexMapped = routeProgress.legIndex + self.currentLegIndexMapped = routeProgress.legIndex } - if currentStepIndexMapped != routeProgress.currentLegProgress.stepIndex { - updateMapOverlays(for: routeProgress) - currentStepIndexMapped = routeProgress.currentLegProgress.stepIndex + if self.currentStepIndexMapped != routeProgress.currentLegProgress.stepIndex { + self.updateMapOverlays(for: routeProgress) + self.currentStepIndexMapped = routeProgress.currentLegProgress.stepIndex } - if annotatesSpokenInstructions { - mapView.showVoiceInstructionsOnMap(route: routeController.routeProgress.route) + if self.annotatesSpokenInstructions { + self.mapView.showVoiceInstructionsOnMap(route: self.routeController.routeProgress.route) } } var contentInsets: UIEdgeInsets { - let top = navigationView.instructionsBannerContentView.bounds.height - let bottom = navigationView.bottomBannerView.bounds.height + let top = self.navigationView.instructionsBannerContentView.bounds.height + let bottom = self.navigationView.bottomBannerView.bounds.height return UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0) } // MARK: End Of Route func embedEndOfRoute() { - let endOfRoute = endOfRouteViewController + let endOfRoute = self.endOfRouteViewController addChild(endOfRoute) - navigationView.endOfRouteView = endOfRoute.view - navigationView.constrainEndOfRoute() + self.navigationView.endOfRouteView = endOfRoute.view + self.navigationView.constrainEndOfRoute() endOfRoute.didMove(toParent: self) - endOfRoute.dismissHandler = { [weak self] (stars, comment) in + endOfRoute.dismissHandler = { [weak self] _, _ in self?.routeController.endNavigation() self?.delegate?.mapViewControllerDidDismiss(self!, byCanceling: false) } } func unembedEndOfRoute() { - let endOfRoute = endOfRouteViewController + let endOfRoute = self.endOfRouteViewController endOfRoute.willMove(toParent: nil) endOfRoute.removeFromParent() } func showEndOfRoute(duration: TimeInterval = 1.0, completion: ((Bool) -> Void)? = nil) { - embedEndOfRoute() - endOfRouteViewController.destination = destination - navigationView.endOfRouteView?.isHidden = false + self.embedEndOfRoute() + self.endOfRouteViewController.destination = self.destination + self.navigationView.endOfRouteView?.isHidden = false - view.layoutIfNeeded() //flush layout queue - NSLayoutConstraint.deactivate(navigationView.bannerShowConstraints) - NSLayoutConstraint.activate(navigationView.bannerHideConstraints) - navigationView.endOfRouteHideConstraint?.isActive = false - navigationView.endOfRouteShowConstraint?.isActive = true + view.layoutIfNeeded() // flush layout queue + NSLayoutConstraint.deactivate(self.navigationView.bannerShowConstraints) + NSLayoutConstraint.activate(self.navigationView.bannerHideConstraints) + self.navigationView.endOfRouteHideConstraint?.isActive = false + self.navigationView.endOfRouteShowConstraint?.isActive = true - mapView.enableFrameByFrameCourseViewTracking(for: duration) - mapView.setNeedsUpdateConstraints() + self.mapView.enableFrameByFrameCourseViewTracking(for: duration) + self.mapView.setNeedsUpdateConstraints() let animate = { self.view.layoutIfNeeded() @@ -454,7 +450,7 @@ class RouteMapViewController: UIViewController { guard duration > 0.0 else { return noAnimation() } - navigationView.mapView.tracksUserCourse = false + self.navigationView.mapView.tracksUserCourse = false UIView.animate(withDuration: duration, delay: 0.0, options: [.curveLinear], animations: animate, completion: completion) guard let height = navigationView.endOfRouteHeightConstraint?.constant else { return } @@ -464,21 +460,21 @@ class RouteMapViewController: UIViewController { let slicedLine = Polyline(coordinates).sliced(from: userLocation).coordinates let line = MLNPolyline(coordinates: slicedLine, count: UInt(slicedLine.count)) - let camera = navigationView.mapView.cameraThatFitsShape(line, direction: navigationView.mapView.camera.heading, edgePadding: insets) + let camera = self.navigationView.mapView.cameraThatFitsShape(line, direction: self.navigationView.mapView.camera.heading, edgePadding: insets) camera.pitch = 0 - camera.altitude = navigationView.mapView.camera.altitude - navigationView.mapView.setCamera(camera, animated: true) + camera.altitude = self.navigationView.mapView.camera.altitude + self.navigationView.mapView.setCamera(camera, animated: true) } } func hideEndOfRoute(duration: TimeInterval = 0.3, completion: ((Bool) -> Void)? = nil) { - view.layoutIfNeeded() //flush layout queue - navigationView.endOfRouteHideConstraint?.isActive = true - navigationView.endOfRouteShowConstraint?.isActive = false + view.layoutIfNeeded() // flush layout queue + self.navigationView.endOfRouteHideConstraint?.isActive = true + self.navigationView.endOfRouteShowConstraint?.isActive = false view.clipsToBounds = true - mapView.enableFrameByFrameCourseViewTracking(for: duration) - mapView.setNeedsUpdateConstraints() + self.mapView.enableFrameByFrameCourseViewTracking(for: duration) + self.mapView.setNeedsUpdateConstraints() let animate = { self.view.layoutIfNeeded() @@ -502,8 +498,8 @@ class RouteMapViewController: UIViewController { fileprivate func populateName(for waypoint: Waypoint, populated: @escaping (Waypoint) -> Void) { guard waypoint.name == nil else { return populated(waypoint) } - CLGeocoder().reverseGeocodeLocation(waypoint.location) { (places, error) in - guard let place = places?.first, let placeName = place.name, error == nil else { return } + CLGeocoder().reverseGeocodeLocation(waypoint.location) { places, error in + guard let place = places?.first, let placeName = place.name, error == nil else { return } let named = Waypoint(coordinate: waypoint.coordinate, name: placeName) return populated(named) } @@ -514,7 +510,7 @@ class RouteMapViewController: UIViewController { extension RouteMapViewController { override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) { - navigationView.endOfRouteHeightConstraint?.constant = container.preferredContentSize.height + self.navigationView.endOfRouteHeightConstraint?.constant = container.preferredContentSize.height UIView.animate(withDuration: 0.3, animations: view.layoutIfNeeded) } @@ -524,71 +520,75 @@ extension RouteMapViewController { extension RouteMapViewController: NavigationViewDelegate { // MARK: NavigationViewDelegate + func navigationView(_ view: NavigationView, didTapCancelButton: CancelButton) { - delegate?.mapViewControllerDidDismiss(self, byCanceling: true) + self.delegate?.mapViewControllerDidDismiss(self, byCanceling: true) } // MARK: MLNMapViewDelegate + func mapView(_ mapView: MLNMapView, regionDidChangeAnimated animated: Bool) { var userTrackingMode = mapView.userTrackingMode if let mapView = mapView as? NavigationMapView, mapView.tracksUserCourse { userTrackingMode = .followWithCourse } - if userTrackingMode == .none && !isInOverviewMode { - navigationView.wayNameView.isHidden = true + if userTrackingMode == .none, !self.isInOverviewMode { + self.navigationView.wayNameView.isHidden = true } } func mapView(_ mapView: MLNMapView, didFinishLoading style: MLNStyle) { // This method is called before the view is added to a window // (if the style is cached) preventing UIAppearance to apply the style. - showRouteIfNeeded() + self.showRouteIfNeeded() self.mapView.localizeLabels() - delegate?.mapView?(mapView, didFinishLoading: style) + self.delegate?.mapView?(mapView, didFinishLoading: style) } func mapViewDidFinishLoadingMap(_ mapView: MLNMapView) { - delegate?.mapViewDidFinishLoadingMap?(mapView) + self.delegate?.mapViewDidFinishLoadingMap?(mapView) } func label(_ label: InstructionLabel, willPresent instruction: VisualInstruction, as presented: NSAttributedString) -> NSAttributedString? { - return delegate?.label?(label, willPresent: instruction, as: presented) + self.delegate?.label?(label, willPresent: instruction, as: presented) } // MARK: NavigationMapViewCourseTrackingDelegate + func navigationMapViewDidStartTrackingCourse(_ mapView: NavigationMapView) { - navigationView.resumeButton.isHidden = true + self.navigationView.resumeButton.isHidden = true mapView.logoView.isHidden = false } func navigationMapViewDidStopTrackingCourse(_ mapView: NavigationMapView) { - navigationView.resumeButton.isHidden = false - navigationView.wayNameView.isHidden = true + self.navigationView.resumeButton.isHidden = false + self.navigationView.wayNameView.isHidden = true mapView.logoView.isHidden = true } - //MARK: InstructionsBannerViewDelegate + // MARK: InstructionsBannerViewDelegate + func didDragInstructionsBanner(_ sender: BaseInstructionsBannerView) { - displayPreviewInstructions() + self.displayPreviewInstructions() } func didTapInstructionsBanner(_ sender: BaseInstructionsBannerView) { - displayPreviewInstructions() + self.displayPreviewInstructions() } private func displayPreviewInstructions() { - removePreviewInstructions() + self.removePreviewInstructions() if let controller = stepsViewController { - stepsViewController = nil + self.stepsViewController = nil controller.dismiss() } else { let controller = StepsViewController(routeProgress: routeController.routeProgress) controller.delegate = self addChild(controller) - view.insertSubview(controller.view, belowSubview: navigationView.instructionsBannerContentView) + view.insertSubview(controller.view, belowSubview: self.navigationView.instructionsBannerContentView) - controller.view.topAnchor.constraint(equalTo: navigationView.instructionsBannerContentView.bottomAnchor).isActive = true + controller.view.topAnchor.constraint(equalTo: self.navigationView.instructionsBannerContentView.bottomAnchor).isActive = true controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true @@ -596,60 +596,61 @@ extension RouteMapViewController: NavigationViewDelegate { controller.didMove(toParent: self) controller.dropDownAnimation() - stepsViewController = controller + self.stepsViewController = controller return } } - //MARK: NavigationMapViewDelegate + // MARK: NavigationMapViewDelegate + func navigationMapView(_ mapView: NavigationMapView, routeStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - return delegate?.navigationMapView?(mapView, routeStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationMapView?(mapView, routeStyleLayerWithIdentifier: identifier, source: source) } func navigationMapView(_ mapView: NavigationMapView, routeCasingStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - return delegate?.navigationMapView?(mapView, routeCasingStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationMapView?(mapView, routeCasingStyleLayerWithIdentifier: identifier, source: source) } func navigationMapView(_ mapView: NavigationMapView, waypointStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - return delegate?.navigationMapView?(mapView, waypointStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationMapView?(mapView, waypointStyleLayerWithIdentifier: identifier, source: source) } func navigationMapView(_ mapView: NavigationMapView, waypointSymbolStyleLayerWithIdentifier identifier: String, source: MLNSource) -> MLNStyleLayer? { - return delegate?.navigationMapView?(mapView, waypointSymbolStyleLayerWithIdentifier: identifier, source: source) + self.delegate?.navigationMapView?(mapView, waypointSymbolStyleLayerWithIdentifier: identifier, source: source) } func navigationMapView(_ mapView: NavigationMapView, shapeFor waypoints: [Waypoint], legIndex: Int) -> MLNShape? { - return delegate?.navigationMapView?(mapView, shapeFor: waypoints, legIndex: legIndex) + self.delegate?.navigationMapView?(mapView, shapeFor: waypoints, legIndex: legIndex) } func navigationMapView(_ mapView: NavigationMapView, shapeFor routes: [Route]) -> MLNShape? { - return delegate?.navigationMapView?(mapView, shapeFor: routes) + self.delegate?.navigationMapView?(mapView, shapeFor: routes) } func navigationMapView(_ mapView: NavigationMapView, didSelect route: Route) { - delegate?.navigationMapView?(mapView, didSelect: route) + self.delegate?.navigationMapView?(mapView, didSelect: route) } func navigationMapView(_ mapView: NavigationMapView, simplifiedShapeFor route: Route) -> MLNShape? { - return delegate?.navigationMapView?(mapView, simplifiedShapeFor: route) + self.delegate?.navigationMapView?(mapView, simplifiedShapeFor: route) } func navigationMapView(_ mapView: MLNMapView, imageFor annotation: MLNAnnotation) -> MLNAnnotationImage? { - return delegate?.navigationMapView?(mapView, imageFor: annotation) + self.delegate?.navigationMapView?(mapView, imageFor: annotation) } func navigationMapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? { - return delegate?.navigationMapView?(mapView, viewFor: annotation) + self.delegate?.navigationMapView?(mapView, viewFor: annotation) } func navigationMapViewUserAnchorPoint(_ mapView: NavigationMapView) -> CGPoint { - //If the end of route component is showing, then put the anchor point slightly above the middle of the map - if navigationView.endOfRouteView != nil, let show = navigationView.endOfRouteShowConstraint, show.isActive { - return CGPoint(x: mapView.bounds.midX, y: (mapView.bounds.height * 0.4)) + // If the end of route component is showing, then put the anchor point slightly above the middle of the map + if self.navigationView.endOfRouteView != nil, let show = navigationView.endOfRouteShowConstraint, show.isActive { + return CGPoint(x: mapView.bounds.midX, y: mapView.bounds.height * 0.4) } - //otherwise, ask the delegate or return .zero - return delegate?.navigationMapViewUserAnchorPoint?(mapView) ?? .zero + // otherwise, ask the delegate or return .zero + return self.delegate?.navigationMapViewUserAnchorPoint?(mapView) ?? .zero } /** @@ -658,41 +659,38 @@ extension RouteMapViewController: NavigationViewDelegate { - parameter location: The user’s current location. */ func labelCurrentRoad(at rawLocation: CLLocation, for snappedLoction: CLLocation? = nil) { - - guard navigationView.resumeButton.isHidden else { - return + guard self.navigationView.resumeButton.isHidden else { + return } - let roadName = delegate?.mapViewController(self, roadNameAt: rawLocation) + let roadName = self.delegate?.mapViewController(self, roadNameAt: rawLocation) guard roadName == nil else { - if let roadName = roadName { - navigationView.wayNameView.text = roadName - navigationView.wayNameView.isHidden = roadName.isEmpty + if let roadName { + self.navigationView.wayNameView.text = roadName + self.navigationView.wayNameView.isHidden = roadName.isEmpty } return } let location = snappedLoction ?? rawLocation - labelCurrentRoadFeature(at: location) + self.labelCurrentRoadFeature(at: location) - if let labelRoadNameCompletionHandler = labelRoadNameCompletionHandler { + if let labelRoadNameCompletionHandler { labelRoadNameCompletionHandler(true) } } func labelCurrentRoadFeature(at location: CLLocation) { guard let style = mapView.style, let stepCoordinates = routeController.routeProgress.currentLegProgress.currentStep.coordinates else { - return + return } let closestCoordinate = location.coordinate let roadLabelLayerIdentifier = "roadLabelLayer" var streetsSources: [MLNVectorTileSource] = style.sources.compactMap { $0 as? MLNVectorTileSource - }.filter { - $0.isMapboxStreets - } + }.filter(\.isMapboxStreets) // Add Mapbox Streets if the map does not already have it if streetsSources.isEmpty { @@ -710,8 +708,8 @@ extension RouteMapViewController: NavigationViewDelegate { style.insertLayer(streetLabelLayer, at: 0) } - let userPuck = mapView.convert(closestCoordinate, toPointTo: mapView) - let features = mapView.visibleFeatures(at: userPuck, styleLayerIdentifiers: Set([roadLabelLayerIdentifier])) + let userPuck = self.mapView.convert(closestCoordinate, toPointTo: self.mapView) + let features = self.mapView.visibleFeatures(at: userPuck, styleLayerIdentifiers: Set([roadLabelLayerIdentifier])) var smallestLabelDistance = Double.infinity var currentName: String? var currentShieldName: NSAttributedString? @@ -726,7 +724,7 @@ extension RouteMapViewController: NavigationViewDelegate { } for line in allLines { - let featureCoordinates = Array(UnsafeBufferPointer(start: line.coordinates, count: Int(line.pointCount))) + let featureCoordinates = Array(UnsafeBufferPointer(start: line.coordinates, count: Int(line.pointCount))) let featurePolyline = Polyline(featureCoordinates) let slicedLine = Polyline(stepCoordinates).sliced(from: closestCoordinate) @@ -743,11 +741,11 @@ extension RouteMapViewController: NavigationViewDelegate { smallestLabelDistance = minDistanceBetweenPoints if let line = feature as? MLNPolylineFeature { - let roadNameRecord = roadFeature(for: line) + let roadNameRecord = self.roadFeature(for: line) currentShieldName = roadNameRecord.shieldName currentName = roadNameRecord.roadName } else if let line = feature as? MLNMultiPolylineFeature { - let roadNameRecord = roadFeature(for: line) + let roadNameRecord = self.roadFeature(for: line) currentShieldName = roadNameRecord.shieldName currentName = roadNameRecord.roadName } @@ -756,32 +754,32 @@ extension RouteMapViewController: NavigationViewDelegate { } let hasWayName = currentName != nil || currentShieldName != nil - if smallestLabelDistance < 5 && hasWayName { - if let currentShieldName = currentShieldName { - navigationView.wayNameView.attributedText = currentShieldName - } else if let currentName = currentName { - navigationView.wayNameView.text = currentName + if smallestLabelDistance < 5, hasWayName { + if let currentShieldName { + self.navigationView.wayNameView.attributedText = currentShieldName + } else if let currentName { + self.navigationView.wayNameView.text = currentName } - navigationView.wayNameView.isHidden = false + self.navigationView.wayNameView.isHidden = false } else { - navigationView.wayNameView.isHidden = true + self.navigationView.wayNameView.isHidden = true } } private func roadFeature(for line: MLNPolylineFeature) -> (roadName: String?, shieldName: NSAttributedString?) { - let roadNameRecord = roadFeatureHelper(ref: line.attribute(forKey: "ref"), - shield: line.attribute(forKey: "shield"), - reflen: line.attribute(forKey: "reflen"), - name: line.attribute(forKey: "name")) + let roadNameRecord = self.roadFeatureHelper(ref: line.attribute(forKey: "ref"), + shield: line.attribute(forKey: "shield"), + reflen: line.attribute(forKey: "reflen"), + name: line.attribute(forKey: "name")) return (roadName: roadNameRecord.roadName, shieldName: roadNameRecord.shieldName) } private func roadFeature(for line: MLNMultiPolylineFeature) -> (roadName: String?, shieldName: NSAttributedString?) { - let roadNameRecord = roadFeatureHelper(ref: line.attribute(forKey: "ref"), - shield: line.attribute(forKey: "shield"), - reflen: line.attribute(forKey: "reflen"), - name: line.attribute(forKey: "name")) + let roadNameRecord = self.roadFeatureHelper(ref: line.attribute(forKey: "ref"), + shield: line.attribute(forKey: "shield"), + reflen: line.attribute(forKey: "reflen"), + name: line.attribute(forKey: "name")) return (roadName: roadNameRecord.roadName, shieldName: roadNameRecord.shieldName) } @@ -790,7 +788,7 @@ extension RouteMapViewController: NavigationViewDelegate { var currentShieldName: NSAttributedString?, currentRoadName: String? if let text = ref as? String, let shieldID = shield as? String, let reflenDigit = reflen as? Int { - currentShieldName = roadShieldName(for: text, shield: shieldID, reflen: reflenDigit) + currentShieldName = self.roadShieldName(for: text, shield: shieldID, reflen: reflenDigit) } if let roadName = name as? String { @@ -807,7 +805,7 @@ extension RouteMapViewController: NavigationViewDelegate { } private func roadShieldName(for text: String?, shield: String?, reflen: Int?) -> NSAttributedString? { - guard let text = text, let shield = shield, let reflen = reflen else { return nil } + guard let text, let shield, let reflen else { return nil } let currentShield = HighwayShield.RoadType(rawValue: shield) let textColor = currentShield?.textColor ?? .black @@ -822,27 +820,27 @@ extension RouteMapViewController: NavigationViewDelegate { } @objc func updateETA() { - guard isViewLoaded, routeController != nil else { return } - navigationView.bottomBannerView.updateETA(routeProgress: routeController.routeProgress) + guard isViewLoaded, self.routeController != nil else { return } + self.navigationView.bottomBannerView.updateETA(routeProgress: self.routeController.routeProgress) } func resetETATimer() { - removeTimer() - updateETATimer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(updateETA), userInfo: nil, repeats: true) + self.removeTimer() + self.updateETATimer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(self.updateETA), userInfo: nil, repeats: true) } func showRouteIfNeeded() { - guard isViewLoaded && view.window != nil else { return } - guard !mapView.showsRoute else { return } - mapView.showRoutes([routeController.routeProgress.route], legIndex: routeController.routeProgress.legIndex) - mapView.showWaypoints(routeController.routeProgress.route, legIndex: routeController.routeProgress.legIndex) + guard isViewLoaded, view.window != nil else { return } + guard !self.mapView.showsRoute else { return } + self.mapView.showRoutes([self.routeController.routeProgress.route], legIndex: self.routeController.routeProgress.legIndex) + self.mapView.showWaypoints(self.routeController.routeProgress.route, legIndex: self.routeController.routeProgress.legIndex) - if routeController.routeProgress.currentLegProgress.stepIndex + 1 <= routeController.routeProgress.currentLegProgress.leg.steps.count { - mapView.addArrow(route: routeController.routeProgress.route, legIndex: routeController.routeProgress.legIndex, stepIndex: routeController.routeProgress.currentLegProgress.stepIndex + 1) + if self.routeController.routeProgress.currentLegProgress.stepIndex + 1 <= self.routeController.routeProgress.currentLegProgress.leg.steps.count { + self.mapView.addArrow(route: self.routeController.routeProgress.route, legIndex: self.routeController.routeProgress.legIndex, stepIndex: self.routeController.routeProgress.currentLegProgress.stepIndex + 1) } - if annotatesSpokenInstructions { - mapView.showVoiceInstructionsOnMap(route: routeController.routeProgress.route) + if self.annotatesSpokenInstructions { + self.mapView.showVoiceInstructionsOnMap(route: self.routeController.routeProgress.route) } } } @@ -850,9 +848,7 @@ extension RouteMapViewController: NavigationViewDelegate { // MARK: StepsViewControllerDelegate extension RouteMapViewController: StepsViewControllerDelegate { - func stepsViewController(_ viewController: StepsViewController, didSelect legIndex: Int, stepIndex: Int, cell: StepTableViewCell) { - let legProgress = RouteLegProgress(leg: routeController.routeProgress.route.legs[legIndex], stepIndex: stepIndex) let step = legProgress.currentStep guard let upcomingStep = legProgress.upComingStep else { return } @@ -862,16 +858,16 @@ extension RouteMapViewController: StepsViewControllerDelegate { self.stepsViewController = nil } - mapView.enableFrameByFrameCourseViewTracking(for: 1) - mapView.tracksUserCourse = false - mapView.setCenter(upcomingStep.maneuverLocation, zoomLevel: mapView.zoomLevel, direction: upcomingStep.initialHeading!, animated: true, completionHandler: nil) + self.mapView.enableFrameByFrameCourseViewTracking(for: 1) + self.mapView.tracksUserCourse = false + self.mapView.setCenter(upcomingStep.maneuverLocation, zoomLevel: self.mapView.zoomLevel, direction: upcomingStep.initialHeading!, animated: true, completionHandler: nil) - guard isViewLoaded && view.window != nil else { return } - mapView.addArrow(route: routeController.routeProgress.route, legIndex: legIndex, stepIndex: stepIndex + 1) + guard isViewLoaded, view.window != nil else { return } + self.mapView.addArrow(route: self.routeController.routeProgress.route, legIndex: legIndex, stepIndex: stepIndex + 1) } func addPreviewInstructions(step: RouteStep, maneuverStep: RouteStep, distance: CLLocationDistance?) { - removePreviewInstructions() + self.removePreviewInstructions() guard let instructions = step.instructionsDisplayedAlongStep?.last else { return } @@ -880,11 +876,11 @@ extension RouteMapViewController: StepsViewControllerDelegate { instructionsView.delegate = self instructionsView.distance = distance - navigationView.instructionsBannerContentView.backgroundColor = instructionsView.backgroundColor + self.navigationView.instructionsBannerContentView.backgroundColor = instructionsView.backgroundColor view.addSubview(instructionsView) instructionsView.update(for: instructions) - previewInstructionsView = instructionsView + self.previewInstructionsView = instructionsView } func didDismissStepsViewController(_ viewController: StepsViewController) { @@ -895,9 +891,9 @@ extension RouteMapViewController: StepsViewControllerDelegate { } func statusView(_ statusView: StatusView, valueChangedTo value: Double) { - let displayValue = 1+min(Int(9 * value), 8) + let displayValue = 1 + min(Int(9 * value), 8) let title = String.Localized.simulationStatus(speed: displayValue) - showStatus(title: title, for: .infinity, interactive: true) + self.showStatus(title: title, for: .infinity, interactive: true) if let locationManager = routeController.locationManager as? SimulatedLocationManager { locationManager.speedMultiplier = Double(displayValue) @@ -907,18 +903,19 @@ extension RouteMapViewController: StepsViewControllerDelegate { // MARK: - Keyboard Handling -extension RouteMapViewController { - fileprivate func subscribeToKeyboardNotifications() { +private extension RouteMapViewController { + func subscribeToKeyboardNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(RouteMapViewController.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(RouteMapViewController.keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) - } - fileprivate func unsubscribeFromKeyboardNotifications() { + + func unsubscribeFromKeyboardNotifications() { NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) } - @objc fileprivate func keyboardWillShow(notification: NSNotification) { - guard navigationView.endOfRouteView != nil else { return } + + @objc func keyboardWillShow(notification: NSNotification) { + guard self.navigationView.endOfRouteView != nil else { return } guard let userInfo = notification.userInfo else { return } guard let curveValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int else { return } guard let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } @@ -929,29 +926,29 @@ extension RouteMapViewController { let keyboardHeight = keyBoardRect.size.height if #available(iOS 11.0, *) { - navigationView.endOfRouteShowConstraint?.constant = -1 * (keyboardHeight - view.safeAreaInsets.bottom) //subtract the safe area, which is part of the keyboard's frame + navigationView.endOfRouteShowConstraint?.constant = -1 * (keyboardHeight - view.safeAreaInsets.bottom) // subtract the safe area, which is part of the keyboard's frame } else { - navigationView.endOfRouteShowConstraint?.constant = -1 * keyboardHeight + self.navigationView.endOfRouteShowConstraint?.constant = -1 * keyboardHeight } let opts = UIView.AnimationOptions(curve: options.curve) UIView.animate(withDuration: options.duration, delay: 0, options: opts, animations: view.layoutIfNeeded, completion: nil) } - @objc fileprivate func keyboardWillHide(notification: NSNotification) { - guard navigationView.endOfRouteView != nil else { return } + @objc func keyboardWillHide(notification: NSNotification) { + guard self.navigationView.endOfRouteView != nil else { return } guard let userInfo = notification.userInfo else { return } let curve = UIView.AnimationCurve(rawValue: userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! Int) let options = (duration: userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double, curve: UIView.AnimationOptions(curve: curve!)) - navigationView.endOfRouteShowConstraint?.constant = 0 + self.navigationView.endOfRouteShowConstraint?.constant = 0 UIView.animate(withDuration: options.duration, delay: 0, options: options.curve, animations: view.layoutIfNeeded, completion: nil) } } -fileprivate extension UIView.AnimationOptions { +private extension UIView.AnimationOptions { init(curve: UIView.AnimationCurve) { switch curve { case .easeIn: @@ -967,6 +964,7 @@ fileprivate extension UIView.AnimationOptions { } } } + @objc protocol RouteMapViewControllerDelegate: NavigationMapViewDelegate, MLNMapViewDelegate, VisualInstructionDelegate { func mapViewControllerDidDismiss(_ mapViewController: RouteMapViewController, byCanceling canceled: Bool) func mapViewControllerShouldAnnotateSpokenInstructions(_ routeMapViewController: RouteMapViewController) -> Bool diff --git a/MapboxNavigation/RouteVoiceController.swift b/MapboxNavigation/RouteVoiceController.swift index 67dac1e64..121be56d8 100644 --- a/MapboxNavigation/RouteVoiceController.swift +++ b/MapboxNavigation/RouteVoiceController.swift @@ -1,16 +1,15 @@ - import AVFoundation -import MapboxDirections import MapboxCoreNavigation +import MapboxDirections import MapboxNavigationObjC extension ErrorUserInfoKey { static let spokenInstructionErrorCode = MBSpokenInstructionErrorCodeKey } -extension NSAttributedString { +public extension NSAttributedString { @available(iOS 10.0, *) - public func pronounced(_ pronunciation: String) -> NSAttributedString { + func pronounced(_ pronunciation: String) -> NSAttributedString { let phoneticWords = pronunciation.components(separatedBy: " ") let phoneticString = NSMutableAttributedString() for (word, phoneticWord) in zip(string.components(separatedBy: " "), phoneticWords) { @@ -32,18 +31,18 @@ extension SpokenInstruction { func attributedText(for legProgress: RouteLegProgress) -> NSAttributedString { let attributedText = NSMutableAttributedString(string: text) if let step = legProgress.upComingStep, - let name = step.names?.first, - let phoneticName = step.phoneticNames?.first { + let name = step.names?.first, + let phoneticName = step.phoneticNames?.first { let nameRange = attributedText.mutableString.range(of: name) - if (nameRange.location != NSNotFound) { + if nameRange.location != NSNotFound { attributedText.replaceCharacters(in: nameRange, with: NSAttributedString(string: name).pronounced(phoneticName)) } } if let step = legProgress.followOnStep, - let name = step.names?.first, - let phoneticName = step.phoneticNames?.first { + let name = step.names?.first, + let phoneticName = step.phoneticNames?.first { let nameRange = attributedText.mutableString.range(of: name) - if (nameRange.location != NSNotFound) { + if nameRange.location != NSNotFound { attributedText.replaceCharacters(in: nameRange, with: NSAttributedString(string: name).pronounced(phoneticName)) } } @@ -56,7 +55,6 @@ extension SpokenInstruction { */ @objc(MBRouteVoiceController) open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { - lazy var speechSynth = AVSpeechSynthesizer() let audioQueue = DispatchQueue(label: Bundle.mapboxNavigation.bundleIdentifier! + ".audio") @@ -88,11 +86,11 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { override public init() { super.init() - verifyBackgroundAudio() + self.verifyBackgroundAudio() - speechSynth.delegate = self + self.speechSynth.delegate = self - resumeNotifications() + self.resumeNotifications() } private func verifyBackgroundAudio() { @@ -111,11 +109,11 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { } func resumeNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(didPassSpokenInstructionPoint(notification:)), name: .routeControllerDidPassSpokenInstructionPoint, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(pauseSpeechAndPlayReroutingDing(notification:)), name: .routeControllerWillReroute, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(didReroute(notification:)), name: .routeControllerDidReroute, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didPassSpokenInstructionPoint(notification:)), name: .routeControllerDidPassSpokenInstructionPoint, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.pauseSpeechAndPlayReroutingDing(notification:)), name: .routeControllerWillReroute, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didReroute(notification:)), name: .routeControllerDidReroute, object: nil) - muteToken = NavigationSettings.shared.observe(\.voiceMuted) { [weak self] settings, _ in + self.muteToken = NavigationSettings.shared.observe(\.voiceMuted) { [weak self] settings, _ in if settings.voiceMuted { self?.speechSynth.stopSpeaking(at: .immediate) } @@ -131,30 +129,30 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { @objc func didReroute(notification: NSNotification) { // Play reroute sound when a faster route is found if notification.userInfo?[RouteControllerNotificationUserInfoKey.isProactiveKey] as! Bool { - pauseSpeechAndPlayReroutingDing(notification: notification) + self.pauseSpeechAndPlayReroutingDing(notification: notification) } } @objc func pauseSpeechAndPlayReroutingDing(notification: NSNotification) { - speechSynth.stopSpeaking(at: .word) + self.speechSynth.stopSpeaking(at: .word) - guard playRerouteSound && !NavigationSettings.shared.voiceMuted else { + guard self.playRerouteSound, !NavigationSettings.shared.voiceMuted else { return } do { - try mixAudio() + try self.mixAudio() } catch { - voiceControllerDelegate?.voiceController?(self, spokenInstructionsDidFailWith: error) + self.voiceControllerDelegate?.voiceController?(self, spokenInstructionsDidFailWith: error) } - rerouteSoundPlayer.play() + self.rerouteSoundPlayer.play() } @objc public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) { do { - try unDuckAudio() + try self.unDuckAudio() } catch { - voiceControllerDelegate?.voiceController?(self, spokenInstructionsDidFailWith: error) + self.voiceControllerDelegate?.voiceController?(self, spokenInstructionsDidFailWith: error) } } @@ -177,14 +175,14 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { @objc open func didPassSpokenInstructionPoint(notification: NSNotification) { guard !NavigationSettings.shared.voiceMuted else { return } - routeProgress = notification.userInfo?[RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress - assert(routeProgress != nil, "routeProgress should not be nil.") + self.routeProgress = notification.userInfo?[RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress + assert(self.routeProgress != nil, "routeProgress should not be nil.") guard let instruction = routeProgress?.currentLegProgress.currentStepProgress.currentSpokenInstruction else { return } - let speechLocale = routeProgress?.route.routeOptions.locale + let speechLocale = self.routeProgress?.route.routeOptions.locale - lastSpokenInstruction = instruction - speak(instruction, with: speechLocale) + self.lastSpokenInstruction = instruction + self.speak(instruction, with: speechLocale) } /** @@ -195,17 +193,17 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { - parameter ignoreProgress: A `Bool` that indicates if the routeProgress is added to the instruction. */ open func speak(_ instruction: SpokenInstruction, with locale: Locale?, ignoreProgress: Bool = false) { - if speechSynth.isSpeaking, let lastSpokenInstruction = lastSpokenInstruction { - voiceControllerDelegate?.voiceController?(self, didInterrupt: lastSpokenInstruction, with: instruction) + if self.speechSynth.isSpeaking, let lastSpokenInstruction { + self.voiceControllerDelegate?.voiceController?(self, didInterrupt: lastSpokenInstruction, with: instruction) } do { - try duckAudio() + try self.duckAudio() } catch { - voiceControllerDelegate?.voiceController?(self, spokenInstructionsDidFailWith: error) + self.voiceControllerDelegate?.voiceController?(self, spokenInstructionsDidFailWith: error) } - let modifiedInstruction = voiceControllerDelegate?.voiceController?(self, willSpeak: instruction, routeProgress: routeProgress) ?? instruction + let modifiedInstruction = self.voiceControllerDelegate?.voiceController?(self, willSpeak: instruction, routeProgress: self.routeProgress) ?? instruction let utterance: AVSpeechUtterance @@ -224,7 +222,7 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { utterance.voice = AVSpeechSynthesisVoice(language: locale?.identifier ?? Locale.preferredLocalLanguageCountryCode) } - speechSynth.speak(utterance) + self.speechSynth.speak(utterance) } } @@ -233,7 +231,6 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate { */ @objc(MBVoiceControllerDelegate) public protocol VoiceControllerDelegate { - /** Called when the voice controller failed to speak an instruction. diff --git a/MapboxNavigation/StatusView.swift b/MapboxNavigation/StatusView.swift index 3d2f506bb..2f01501dc 100644 --- a/MapboxNavigation/StatusView.swift +++ b/MapboxNavigation/StatusView.swift @@ -14,7 +14,6 @@ import UIKit @IBDesignable @objc(MBStatusView) public class StatusView: UIView { - weak var activityIndicatorView: UIActivityIndicatorView! weak var textLabel: UILabel! @objc public weak var delegate: StatusViewDelegate? @@ -24,22 +23,26 @@ public class StatusView: UIView { @objc public var canChangeValue = false var value: Double = 0 { didSet { - delegate?.statusView?(self, valueChangedTo: value) + self.delegate?.statusView?(self, valueChangedTo: self.value) } } - @objc public override init(frame: CGRect) { + @objc override public init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } @objc public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { - let activityIndicatorView = UIActivityIndicatorView(style: .white) + let activityIndicatorView = if #available(iOS 13.0, *) { + UIActivityIndicatorView(style: .medium) + } else { + UIActivityIndicatorView(style: .white) + } activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false addSubview(activityIndicatorView) self.activityIndicatorView = activityIndicatorView @@ -70,22 +73,22 @@ public class StatusView: UIView { } @objc func pan(_ sender: UIPanGestureRecognizer) { - guard canChangeValue else { return } + guard self.canChangeValue else { return } let location = sender.location(in: self) if sender.state == .began { - panStartPoint = location + self.panStartPoint = location } else if sender.state == .changed { guard let startPoint = panStartPoint else { return } let offsetX = location.x - startPoint.x let coefficient = (offsetX / bounds.width) / 20.0 - value = Double(min(max(CGFloat(value) + coefficient, 0), 1)) + self.value = Double(min(max(CGFloat(self.value) + coefficient, 0), 1)) } } @objc func tap(_ sender: UITapGestureRecognizer) { - guard canChangeValue else { return } + guard self.canChangeValue else { return } let location = sender.location(in: self) @@ -99,7 +102,7 @@ public class StatusView: UIView { @unknown default: fatalError("Unknown userInterfaceLayoutDirection") } - value = min(max(value + incrementer, 0), 1) + self.value = min(max(self.value + incrementer, 0), 1) } } @@ -107,21 +110,21 @@ public class StatusView: UIView { Shows the status view with an optional spinner. */ public func show(_ title: String, showSpinner: Bool, interactive: Bool = false) { - canChangeValue = interactive - textLabel.text = title - activityIndicatorView.hidesWhenStopped = true - if (!showSpinner) { activityIndicatorView.stopAnimating() } + self.canChangeValue = interactive + self.textLabel.text = title + self.activityIndicatorView.hidesWhenStopped = true + if !showSpinner { self.activityIndicatorView.stopAnimating() } - guard isCurrentlyVisible == false, isHidden == true else { return } + guard self.isCurrentlyVisible == false, isHidden == true else { return } let show = { self.isHidden = false self.textLabel.alpha = 1 - if (showSpinner) { self.activityIndicatorView.isHidden = false } + if showSpinner { self.activityIndicatorView.isHidden = false } self.superview?.layoutIfNeeded() } - UIView.defaultAnimation(0.3, animations:show, completion:{ _ in + UIView.defaultAnimation(0.3, animations: show, completion: { _ in self.isCurrentlyVisible = true guard showSpinner else { return } self.activityIndicatorView.startAnimating() @@ -132,7 +135,6 @@ public class StatusView: UIView { Hides the status view. */ public func hide(delay: TimeInterval = 0, animated: Bool = true) { - let hide = { self.isHidden = true self.textLabel.alpha = 0 @@ -143,14 +145,14 @@ public class StatusView: UIView { guard self.isHidden == false else { return } let fireTime = DispatchTime.now() + delay - DispatchQueue.main.asyncAfter(deadline: fireTime, execute: { + DispatchQueue.main.asyncAfter(deadline: fireTime) { self.activityIndicatorView.stopAnimating() UIView.defaultAnimation(0.3, delay: 0, animations: hide, completion: { _ in self.isCurrentlyVisible = false }) - }) + } } - animated ? animate() : hide() + if animated { animate() } else { hide() } } } diff --git a/MapboxNavigation/StepsViewController.swift b/MapboxNavigation/StepsViewController.swift index 69fa5f2b0..53977b65c 100644 --- a/MapboxNavigation/StepsViewController.swift +++ b/MapboxNavigation/StepsViewController.swift @@ -1,18 +1,16 @@ -import UIKit -import MapboxDirections import MapboxCoreNavigation +import MapboxDirections import Turf +import UIKit /// :nodoc: @objc(MBStepsBackgroundView) -open class StepsBackgroundView: UIView { } - +open class StepsBackgroundView: UIView {} /** `StepsViewControllerDelegate` provides methods for user interactions in a `StepsViewController`. */ @objc public protocol StepsViewControllerDelegate: AnyObject { - /** Called when the user selects a step in a `StepsViewController`. */ @@ -27,7 +25,6 @@ open class StepsBackgroundView: UIView { } /// :nodoc: @objc(MBStepsViewController) public class StepsViewController: UIViewController { - weak var tableView: UITableView! weak var backgroundView: UIView! weak var bottomView: UIView! @@ -57,16 +54,15 @@ public class StepsViewController: UIViewController { @discardableResult func rebuildDataSourceIfNecessary() -> Bool { - - let legIndex = routeProgress.legIndex - let stepIndex = routeProgress.currentLegProgress.stepIndex - let didProcessCurrentStep = previousLegIndex == legIndex && previousStepIndex == stepIndex + let legIndex = self.routeProgress.legIndex + let stepIndex = self.routeProgress.currentLegProgress.stepIndex + let didProcessCurrentStep = self.previousLegIndex == legIndex && self.previousStepIndex == stepIndex guard !didProcessCurrentStep else { return false } - sections.removeAll() + self.sections.removeAll() - let currentLeg = routeProgress.currentLeg + let currentLeg = self.routeProgress.currentLeg // Add remaining steps for current leg var section = [RouteStep]() @@ -78,29 +74,29 @@ public class StepsViewController: UIViewController { } if !section.isEmpty { - sections.append(section) + self.sections.append(section) } // Include all steps on any future legs - if !routeProgress.isFinalLeg { - routeProgress.route.legs.suffix(from: routeProgress.legIndex + 1).forEach { - var steps = $0.steps + if !self.routeProgress.isFinalLeg { + for item in self.routeProgress.route.legs.suffix(from: self.routeProgress.legIndex + 1) { + var steps = item.steps // Don't include the last step, it includes nothing _ = steps.popLast() - sections.append(steps) + self.sections.append(steps) } } - previousStepIndex = stepIndex - previousLegIndex = legIndex + self.previousStepIndex = stepIndex + self.previousLegIndex = legIndex return true } override open func viewDidLoad() { super.viewDidLoad() - setupViews() - rebuildDataSourceIfNecessary() + self.setupViews() + self.rebuildDataSourceIfNecessary() NotificationCenter.default.addObserver(self, selector: #selector(StepsViewController.progressDidChange(_:)), name: .routeControllerProgressDidChange, object: nil) } @@ -110,9 +106,8 @@ public class StepsViewController: UIViewController { } @objc func progressDidChange(_ notification: Notification) { - - if rebuildDataSourceIfNecessary() { - tableView.reloadData() + if self.rebuildDataSourceIfNecessary() { + self.tableView.reloadData() } } @@ -178,10 +173,9 @@ public class StepsViewController: UIViewController { tableView.bottomAnchor.constraint(equalTo: dismissButton.topAnchor).isActive = true tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true - tableView.register(StepTableViewCell.self, forCellReuseIdentifier: cellId) + tableView.register(StepTableViewCell.self, forCellReuseIdentifier: self.cellId) } - /** Shows and animates the `StepsViewController` down. */ @@ -197,7 +191,6 @@ public class StepsViewController: UIViewController { }, completion: nil) } - /** Dismisses and animates the `StepsViewController` up. */ @@ -206,20 +199,20 @@ public class StepsViewController: UIViewController { var frame = self.view.frame frame.origin.y -= frame.height self.view.frame = frame - }) { (completed) in + }, completion: { _ in completion?() - } + }) } @IBAction func tappedDismiss(_ sender: Any) { - delegate?.didDismissStepsViewController(self) + self.delegate?.didDismissStepsViewController(self) } /** Dismisses the `StepsViewController`. */ public func dismiss(completion: CompletionHandler? = nil) { - slideUpAnimation { + self.slideUpAnimation { self.willMove(toParent: nil) self.view.removeFromSuperview() self.removeFromParent() @@ -238,42 +231,42 @@ extension StepsViewController: UITableViewDelegate { if indexPath.section > 0 { stepIndex = indexPath.row } else { - stepIndex = indexPath.row + routeProgress.currentLegProgress.stepIndex + stepIndex = indexPath.row + self.routeProgress.currentLegProgress.stepIndex // For the current leg, we need to know the upcoming step. - stepIndex += indexPath.row + 1 > sections[indexPath.section].count ? 0 : 1 + stepIndex += indexPath.row + 1 > self.sections[indexPath.section].count ? 0 : 1 } - delegate?.stepsViewController?(self, didSelect: indexPath.section, stepIndex: stepIndex, cell: cell) + self.delegate?.stepsViewController?(self, didSelect: indexPath.section, stepIndex: stepIndex, cell: cell) } } extension StepsViewController: UITableViewDataSource { public func numberOfSections(in tableView: UITableView) -> Int { - return sections.count + self.sections.count } public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let steps = sections[section] + let steps = self.sections[section] return steps.count } public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 96 + 96 } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! StepTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: self.cellId, for: indexPath) as! StepTableViewCell return cell } public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - updateCell(cell as! StepTableViewCell, at: indexPath) + self.updateCell(cell as! StepTableViewCell, at: indexPath) } func updateCell(_ cell: StepTableViewCell, at indexPath: IndexPath) { cell.instructionsView.primaryLabel.viewForAvailableBoundsCalculation = cell cell.instructionsView.secondaryLabel.viewForAvailableBoundsCalculation = cell - let step = sections[indexPath.section][indexPath.row] + let step = self.sections[indexPath.section][indexPath.row] if let instructions = step.instructionsDisplayedAlongStep?.last { cell.instructionsView.update(for: instructions) @@ -284,7 +277,7 @@ extension StepsViewController: UITableViewDataSource { cell.instructionsView.stepListIndicatorView.isHidden = true // Hide cell separator if it’s the last row in a section - let isLastRowInSection = indexPath.row == sections[indexPath.section].count - 1 + let isLastRowInSection = indexPath.row == self.sections[indexPath.section].count - 1 cell.separatorView.isHidden = isLastRowInSection } @@ -293,7 +286,7 @@ extension StepsViewController: UITableViewDataSource { return nil } - let leg = routeProgress.route.legs[section] + let leg = self.routeProgress.route.legs[section] let sourceName = leg.source.name let destinationName = leg.destination.name let majorWays = leg.name.components(separatedBy: ", ") @@ -311,23 +304,22 @@ extension StepsViewController: UITableViewDataSource { /// :nodoc: @objc(MBStepInstructionsView) -open class StepInstructionsView: BaseInstructionsBannerView { } +open class StepInstructionsView: BaseInstructionsBannerView {} /// :nodoc: @objc(MBStepTableViewCell) open class StepTableViewCell: UITableViewCell { - weak var instructionsView: StepInstructionsView! weak var separatorView: SeparatorView! override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - commonInit() + self.commonInit() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { @@ -356,21 +348,20 @@ open class StepTableViewCell: UITableViewCell { separatorView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -18).isActive = true } - open override func prepareForReuse() { + override open func prepareForReuse() { super.prepareForReuse() - instructionsView.update(for:nil) + self.instructionsView.update(for: nil) } } -extension Array where Element == RouteStep { - - fileprivate func stepBefore(_ step: RouteStep) -> RouteStep? { - guard let index = self.firstIndex(of: step) else { +private extension [RouteStep] { + func stepBefore(_ step: RouteStep) -> RouteStep? { + guard let index = firstIndex(of: step) else { return nil } if index > 0 { - return self[index-1] + return self[index - 1] } return nil diff --git a/MapboxNavigation/Style.swift b/MapboxNavigation/Style.swift index c4e6a6df1..5d816a23e 100644 --- a/MapboxNavigation/Style.swift +++ b/MapboxNavigation/Style.swift @@ -1,5 +1,5 @@ -import UIKit import MapLibre +import UIKit /** `Style` is a convenient wrapper for styling the appearance of various interface components throughout the Navigation UI. @@ -8,7 +8,6 @@ import MapLibre */ @objc(MBStyle) open class Style: NSObject { - /// General styling /** @@ -54,9 +53,9 @@ open class Style: NSObject { /** Applies the style for all changed properties. */ - @objc open func apply() { } + @objc open func apply() {} - @objc public required override init() { } + @objc override public required init() {} } /** @@ -64,28 +63,28 @@ open class Style: NSObject { `MBButton` sets the tintColor according to the style. */ @objc(MBButton) -open class Button: StylableButton { } +open class Button: StylableButton {} /// :nodoc: @objc(MBCancelButton) -open class CancelButton: Button { } +open class CancelButton: Button {} /// :nodoc: @objc(MBDismissButton) -open class DismissButton: Button { } +open class DismissButton: Button {} /// :nodoc: @objc(MBFloatingButton) open class FloatingButton: Button { - static let buttonSize = CGSize(width: 50, height: 50) - static let sizeConstraintPriority = UILayoutPriority(999.0) //Don't fight with the stack view (superview) when it tries to hide buttons. + static let sizeConstraintPriority = UILayoutPriority(999.0) // Don't fight with the stack view (superview) when it tries to hide buttons. lazy var widthConstraint: NSLayoutConstraint = { let constraint = self.widthAnchor.constraint(equalToConstant: FloatingButton.buttonSize.width) constraint.priority = FloatingButton.sizeConstraintPriority return constraint }() + lazy var heightConstraint: NSLayoutConstraint = { let constraint = self.heightAnchor.constraint(equalToConstant: FloatingButton.buttonSize.height) constraint.priority = FloatingButton.sizeConstraintPriority @@ -95,17 +94,17 @@ open class FloatingButton: Button { var constrainedSize: CGSize? { didSet { guard let size = constrainedSize else { - NSLayoutConstraint.deactivate([widthConstraint, heightConstraint]) + NSLayoutConstraint.deactivate([self.widthConstraint, self.heightConstraint]) return } - widthConstraint.constant = size.width - heightConstraint.constant = size.height - NSLayoutConstraint.activate([widthConstraint, heightConstraint]) + self.widthConstraint.constant = size.width + self.heightConstraint.constant = size.height + NSLayoutConstraint.activate([self.widthConstraint, self.heightConstraint]) } } class func rounded(image: UIImage, selectedImage: UIImage? = nil, size: CGSize = FloatingButton.buttonSize) -> T { - let button = T.init(type: .custom) + let button = T(type: .custom) button.translatesAutoresizingMaskIntoConstraints = false button.constrainedSize = size button.setImage(image, for: .normal) @@ -118,7 +117,6 @@ open class FloatingButton: Button { /// :nodoc: @objc(MBReportButton) public class ReportButton: Button { - static let padding: CGFloat = 10 static let downConstant: CGFloat = 10 static let defaultInsets: UIEdgeInsets = 10.0 @@ -126,11 +124,12 @@ public class ReportButton: Button { public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } + override public init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } private func commonInit() { @@ -139,7 +138,7 @@ public class ReportButton: Button { } var upConstant: CGFloat { - return -bounds.height-(ReportButton.padding * 2) + -bounds.height - (ReportButton.padding * 2) } func slideDown(constraint: NSLayoutConstraint, interval: TimeInterval) { @@ -150,20 +149,20 @@ public class ReportButton: Button { setNeedsUpdateConstraints() UIView.defaultAnimation(0.5, animations: { self.superview?.layoutIfNeeded() - }) { (completed) in + }, completion: { _ in NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(ReportButton.slideUp(constraint:)), object: nil) self.perform(#selector(ReportButton.slideUp(constraint:)), with: constraint, afterDelay: interval) - } + }) } @objc func slideUp(constraint: NSLayoutConstraint) { - constraint.constant = upConstant + constraint.constant = self.upConstant setNeedsUpdateConstraints() UIView.defaultSpringAnimation(0.5, animations: { self.superview?.layoutIfNeeded() - }) { (completed) in + }, completion: { _ in self.isHidden = true - } + }) } } @@ -173,45 +172,45 @@ public class ReportButton: Button { `Button`. */ @objc(MBHighlightedButton) -public class HighlightedButton: Button { } +public class HighlightedButton: Button {} /// :nodoc: @IBDesignable @objc(MBResumeButton) public class ResumeButton: UIControl { - public override dynamic var tintColor: UIColor! { + override public dynamic var tintColor: UIColor! { didSet { - imageView.tintColor = tintColor - titleLabel.textColor = tintColor + self.imageView.tintColor = self.tintColor + self.titleLabel.textColor = self.tintColor } } let imageView = UIImageView(image: UIImage(named: "location", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)) let titleLabel = UILabel() - public override init(frame: CGRect) { + override public init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } - public override func prepareForInterfaceBuilder() { + override public func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() - commonInit() + self.commonInit() } func commonInit() { - titleLabel.text = NSLocalizedString("RESUME", bundle: .mapboxNavigation, value: "Resume", comment: "Button title for resume tracking") - titleLabel.sizeToFit() - addSubview(imageView) - addSubview(titleLabel) + self.titleLabel.text = NSLocalizedString("RESUME", bundle: .mapboxNavigation, value: "Resume", comment: "Button title for resume tracking") + self.titleLabel.sizeToFit() + addSubview(self.imageView) + addSubview(self.titleLabel) - titleLabel.translatesAutoresizingMaskIntoConstraints = false - imageView.translatesAutoresizingMaskIntoConstraints = false + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.imageView.translatesAutoresizingMaskIntoConstraints = false translatesAutoresizingMaskIntoConstraints = false let views = ["label": titleLabel, "imageView": imageView] @@ -227,48 +226,44 @@ public class ResumeButton: UIControl { /// :nodoc: @objc(MBDraggableView) open class StepListIndicatorView: UIView { - // Workaround the fact that UIView properties are not marked with UI_APPEARANCE_SELECTOR - @objc dynamic open var gradientColors: [UIColor] = [.gray, .lightGray, .gray] { + @objc open dynamic var gradientColors: [UIColor] = [.gray, .lightGray, .gray] { didSet { setNeedsLayout() } } - fileprivate lazy var blurredEffectView: UIVisualEffectView = { - return UIVisualEffectView(effect: UIBlurEffect(style: .extraLight)) - }() + fileprivate lazy var blurredEffectView: UIVisualEffectView = .init(effect: UIBlurEffect(style: .extraLight)) override open func layoutSubviews() { super.layoutSubviews() layer.cornerRadius = bounds.midY layer.masksToBounds = true layer.opacity = 0.25 - applyGradient(colors: gradientColors) - addBlurredEffect(view: blurredEffectView, to: self) + applyGradient(colors: self.gradientColors) + self.addBlurredEffect(view: self.blurredEffectView, to: self) } - fileprivate func addBlurredEffect(view: UIView, to parentView: UIView) { + fileprivate func addBlurredEffect(view: UIView, to parentView: UIView) { guard !view.isDescendant(of: parentView) else { return } view.frame = parentView.bounds parentView.addSubview(view) } - } /// :nodoc: @objc(MBStylableLabel) open class StylableLabel: UILabel { // Workaround the fact that UILabel properties are not marked with UI_APPEARANCE_SELECTOR - @objc dynamic open var normalTextColor: UIColor = .black { + @objc open dynamic var normalTextColor: UIColor = .black { didSet { - textColor = normalTextColor + textColor = self.normalTextColor } } - @objc dynamic open var normalFont: UIFont = .systemFont(ofSize: 16) { + @objc open dynamic var normalFont: UIFont = .systemFont(ofSize: 16) { didSet { - font = normalFont + font = self.normalFont } } } @@ -278,12 +273,13 @@ open class StylableLabel: UILabel { open class StylableView: UIView { @objc dynamic var borderWidth: CGFloat = 0.0 { didSet { - layer.borderWidth = borderWidth + layer.borderWidth = self.borderWidth } } + @objc dynamic var cornerRadius: CGFloat = 0.0 { didSet { - layer.cornerRadius = cornerRadius + layer.cornerRadius = self.cornerRadius } } } @@ -292,9 +288,9 @@ open class StylableView: UIView { @objc(MBStylableTextView) open class StylableTextView: UITextView { // Workaround the fact that UITextView properties are not marked with UI_APPEARANCE_SELECTOR - @objc dynamic open var normalTextColor: UIColor = .black { + @objc open dynamic var normalTextColor: UIColor = .black { didSet { - textColor = normalTextColor + textColor = self.normalTextColor } } } @@ -302,17 +298,20 @@ open class StylableTextView: UITextView { /// :nodoc: @objc(MBDistanceLabel) open class DistanceLabel: StylableLabel { - @objc dynamic public var valueTextColor: UIColor = #colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.431372549, alpha: 1) { + @objc public dynamic var valueTextColor: UIColor = #colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.431372549, alpha: 1) { didSet { update() } } - @objc dynamic public var unitTextColor: UIColor = #colorLiteral(red: 0.6274509804, green: 0.6274509804, blue: 0.6274509804, alpha: 1) { + + @objc public dynamic var unitTextColor: UIColor = #colorLiteral(red: 0.6274509804, green: 0.6274509804, blue: 0.6274509804, alpha: 1) { didSet { update() } } - @objc dynamic public var valueFont: UIFont = UIFont.systemFont(ofSize: 16, weight: .medium) { - didSet { update() } + + @objc public dynamic var valueFont: UIFont = .systemFont(ofSize: 16, weight: .medium) { + didSet { self.update() } } - @objc dynamic public var unitFont: UIFont = UIFont.systemFont(ofSize: 11, weight: .medium) { - didSet { update() } + + @objc public dynamic var unitFont: UIFont = .systemFont(ofSize: 11, weight: .medium) { + didSet { self.update() } } /** @@ -323,12 +322,12 @@ open class DistanceLabel: StylableLabel { */ var attributedDistanceString: NSAttributedString? { didSet { - update() + self.update() } } fileprivate func update() { - guard let attributedDistanceString = attributedDistanceString else { + guard let attributedDistanceString else { return } @@ -336,23 +335,23 @@ open class DistanceLabel: StylableLabel { let emphasizedDistanceString = NSMutableAttributedString(attributedString: attributedDistanceString) let wholeRange = NSRange(location: 0, length: emphasizedDistanceString.length) var hasQuantity = false - emphasizedDistanceString.enumerateAttribute(.quantity, in: wholeRange, options: .longestEffectiveRangeNotRequired) { (value, range, stop) in + emphasizedDistanceString.enumerateAttribute(.quantity, in: wholeRange, options: .longestEffectiveRangeNotRequired) { _, range, _ in let foregroundColor: UIColor let font: UIFont - if let _ = emphasizedDistanceString.attribute(NSAttributedString.Key.quantity, at: range.location, effectiveRange: nil) { - foregroundColor = valueTextColor - font = valueFont + if emphasizedDistanceString.attribute(NSAttributedString.Key.quantity, at: range.location, effectiveRange: nil) != nil { + foregroundColor = self.valueTextColor + font = self.valueFont hasQuantity = true } else { - foregroundColor = unitTextColor - font = unitFont + foregroundColor = self.unitTextColor + font = self.unitFont } emphasizedDistanceString.addAttributes([.foregroundColor: foregroundColor, .font: font], range: range) } // As a failsafe, if no quantity was found, emphasize the entire string. if !hasQuantity { - emphasizedDistanceString.addAttributes([.foregroundColor: valueTextColor, .font: valueFont], range: wholeRange) + emphasizedDistanceString.addAttributes([.foregroundColor: self.valueTextColor, .font: self.valueFont], range: wholeRange) } // Replace spaces with hair spaces to economize on horizontal screen @@ -367,75 +366,74 @@ open class DistanceLabel: StylableLabel { /// :nodoc: @objc(MBPrimaryLabel) -open class PrimaryLabel: InstructionLabel { } +open class PrimaryLabel: InstructionLabel {} /// :nodoc: @objc(MBSecondaryLabel) -open class SecondaryLabel: InstructionLabel { } +open class SecondaryLabel: InstructionLabel {} /// :nodoc: @objc(MBTimeRemainingLabel) open class TimeRemainingLabel: StylableLabel { - // Sets the text color for no or unknown traffic - @objc dynamic public var trafficUnknownColor: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) { + @objc public dynamic var trafficUnknownColor: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) { didSet { textColor = trafficUnknownColor } } + // Sets the text color for low traffic - @objc dynamic public var trafficLowColor: UIColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1) + @objc public dynamic var trafficLowColor: UIColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1) // Sets the text color for moderate traffic - @objc dynamic public var trafficModerateColor: UIColor = #colorLiteral(red:0.95, green:0.65, blue:0.31, alpha:1.0) + @objc public dynamic var trafficModerateColor: UIColor = #colorLiteral(red: 0.95, green: 0.65, blue: 0.31, alpha: 1.0) // Sets the text color for heavy traffic - @objc dynamic public var trafficHeavyColor: UIColor = #colorLiteral(red:0.91, green:0.20, blue:0.25, alpha:1.0) + @objc public dynamic var trafficHeavyColor: UIColor = #colorLiteral(red: 0.91, green: 0.20, blue: 0.25, alpha: 1.0) // Sets the text color for severe traffic - @objc dynamic public var trafficSevereColor: UIColor = #colorLiteral(red:0.54, green:0.06, blue:0.22, alpha:1.0) + @objc public dynamic var trafficSevereColor: UIColor = #colorLiteral(red: 0.54, green: 0.06, blue: 0.22, alpha: 1.0) } /// :nodoc: @objc(MBDistanceRemainingLabel) -open class DistanceRemainingLabel: StylableLabel { } +open class DistanceRemainingLabel: StylableLabel {} /// :nodoc: @objc(MBArrivalTimeLabel) -open class ArrivalTimeLabel: StylableLabel { } +open class ArrivalTimeLabel: StylableLabel {} /// :nodoc: @objc(MBTitleLabel) -open class TitleLabel: StylableLabel { } +open class TitleLabel: StylableLabel {} /// :nodoc: @objc(MBSubtitleLabel) -open class SubtitleLabel: StylableLabel { } +open class SubtitleLabel: StylableLabel {} /// :nodoc: @objc(MBWayNameView) open class WayNameView: UIView { - private static let textInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14) lazy var label: WayNameLabel = .forAutoLayout() var text: String? { get { - return label.text + self.label.text } set { - label.text = newValue + self.label.text = newValue } } var attributedText: NSAttributedString? { get { - return label.attributedText + self.label.attributedText } set { - label.attributedText = newValue + self.label.attributedText = newValue } } - @objc dynamic public var borderColor: UIColor? { + @objc public dynamic var borderColor: UIColor? { get { guard let color = layer.borderColor else { return nil } return UIColor(cgColor: color) @@ -445,24 +443,23 @@ open class WayNameView: UIView { } } - public override init(frame: CGRect) { + override public init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } - func commonInit() { - addSubview(label) + addSubview(self.label) layoutMargins = WayNameView.textInsets - label.pinInSuperview(respectingMargins: true) + self.label.pinInSuperview(respectingMargins: true) } - open override func layoutSubviews() { + override open func layoutSubviews() { super.layoutSubviews() layer.cornerRadius = bounds.midY } @@ -475,13 +472,12 @@ open class WayNameLabel: StylableLabel {} /// :nodoc: @objc(MBProgressBar) public class ProgressBar: UIView { - let bar = UIView() var barHeight: CGFloat = 3 // Sets the color of the progress bar. - @objc dynamic public var barColor: UIColor = #colorLiteral(red: 0.1411764771, green: 0.3960784376, blue: 0.5647059083, alpha: 1) { + @objc public dynamic var barColor: UIColor = #colorLiteral(red: 0.1411764771, green: 0.3960784376, blue: 0.5647059083, alpha: 1) { didSet { bar.backgroundColor = barColor } @@ -491,12 +487,12 @@ public class ProgressBar: UIView { var progress: CGFloat = 0 { didSet { self.updateProgressBar() - self.layoutIfNeeded() + layoutIfNeeded() } } override open var description: String { - return super.description + "; progress = \(progress)" + super.description + "; progress = \(self.progress)" } func setProgress(_ progress: CGFloat, animated: Bool) { @@ -505,28 +501,28 @@ public class ProgressBar: UIView { }, completion: nil) } - public override func layoutSubviews() { + override public func layoutSubviews() { super.layoutSubviews() - if bar.superview == nil { - addSubview(bar) + if self.bar.superview == nil { + addSubview(self.bar) } - updateProgressBar() + self.updateProgressBar() } func updateProgressBar() { - if let superview = superview { + if let superview { let origin: CGPoint switch UIApplication.shared.userInterfaceLayoutDirection { case .leftToRight: origin = .zero case .rightToLeft: - origin = CGPoint(x: superview.bounds.width * (1 - progress), y: 0) + origin = CGPoint(x: superview.bounds.width * (1 - self.progress), y: 0) @unknown default: fatalError("Unknown userInterfaceLayoutDirection") } - bar.frame = CGRect(origin: origin, size: CGSize(width: superview.bounds.width * progress, height: bounds.height)) + self.bar.frame = CGRect(origin: origin, size: CGSize(width: superview.bounds.width * self.progress, height: bounds.height)) } } } @@ -534,9 +530,8 @@ public class ProgressBar: UIView { /// :nodoc: @objc(MBLineView) public class LineView: UIView { - // Set the line color on all line views. - @objc dynamic public var lineColor: UIColor = .black { + @objc public dynamic var lineColor: UIColor = .black { didSet { setNeedsDisplay() setNeedsLayout() @@ -546,45 +541,43 @@ public class LineView: UIView { /// :nodoc: @objc(MBSeparatorView) -public class SeparatorView: UIView { } +public class SeparatorView: UIView {} /// :nodoc: @objc(MBStylableButton) open class StylableButton: UIButton { - - // Sets the font on the button’s titleLabel - @objc dynamic open var textFont: UIFont = UIFont.systemFont(ofSize: 20, weight: .medium) { + @objc open dynamic var textFont: UIFont = .systemFont(ofSize: 20, weight: .medium) { didSet { - titleLabel?.font = textFont + titleLabel?.font = self.textFont } } // Sets the text color for normal state - @objc dynamic open var textColor: UIColor = .black { + @objc open dynamic var textColor: UIColor = .black { didSet { - setTitleColor(textColor, for: .normal) + setTitleColor(self.textColor, for: .normal) } } // Sets the border color - @objc dynamic open var borderColor: UIColor = .clear { + @objc open dynamic var borderColor: UIColor = .clear { didSet { - layer.borderColor = borderColor.cgColor + layer.borderColor = self.borderColor.cgColor } } // Sets the border width - @objc dynamic open var borderWidth: CGFloat = 0 { + @objc open dynamic var borderWidth: CGFloat = 0 { didSet { - layer.borderWidth = borderWidth + layer.borderWidth = self.borderWidth } } // Sets the corner radius - @objc dynamic open var cornerRadius: CGFloat = 0 { + @objc open dynamic var cornerRadius: CGFloat = 0 { didSet { - layer.cornerRadius = cornerRadius + layer.cornerRadius = self.cornerRadius } } } @@ -592,11 +585,11 @@ open class StylableButton: UIButton { /// :nodoc: @objc(MBManeuverContainerView) open class ManeuverContainerView: UIView { - @IBOutlet weak var heightConstraint: NSLayoutConstraint! + @IBOutlet var heightConstraint: NSLayoutConstraint! @objc dynamic var height: CGFloat = 100 { didSet { - heightConstraint.constant = height + self.heightConstraint.constant = self.height setNeedsUpdateConstraints() } } @@ -604,16 +597,15 @@ open class ManeuverContainerView: UIView { /// :nodoc: @objc(MBInstructionsBannerContentView) -open class InstructionsBannerContentView: UIView { } +open class InstructionsBannerContentView: UIView {} /// :nodoc: @objc(MBBottomBannerContentView) -open class BottomBannerContentView: UIView { } +open class BottomBannerContentView: UIView {} /// :nodoc: @objc(MBMarkerView) public class MarkerView: UIView { - // Sets the inner color on the pin. @objc public dynamic var innerColor: UIColor = .white { didSet { @@ -643,16 +635,16 @@ public class MarkerView: UIView { } override public var intrinsicContentSize: CGSize { - return CGSize(width: 39, height: 51) + CGSize(width: 39, height: 51) } - public override func layoutSubviews() { + override public func layoutSubviews() { super.layoutSubviews() backgroundColor = .clear } override public func draw(_ rect: CGRect) { super.draw(rect) - StyleKitMarker.drawMarker(innerColor: innerColor, shadowColor: shadowColor, pinColor: pinColor, strokeColor: strokeColor) + StyleKitMarker.drawMarker(innerColor: self.innerColor, shadowColor: self.shadowColor, pinColor: self.pinColor, strokeColor: self.strokeColor) } } diff --git a/MapboxNavigation/StyleKitMarker.swift b/MapboxNavigation/StyleKitMarker.swift index d6410d653..ece696e7b 100644 --- a/MapboxNavigation/StyleKitMarker.swift +++ b/MapboxNavigation/StyleKitMarker.swift @@ -2,10 +2,9 @@ import UIKit @objc(MBStyleKitMarker) public class StyleKitMarker: NSObject { - //// Drawing Methods - @objc dynamic public class func drawMarker(frame: CGRect = CGRect(x: 57, y: 27, width: 50, height: 50), innerColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), shadowColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), pinColor: UIColor = UIColor(red: 0.290, green: 0.565, blue: 0.886, alpha: 1.000), strokeColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) { + @objc public dynamic class func drawMarker(frame: CGRect = CGRect(x: 57, y: 27, width: 50, height: 50), innerColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), shadowColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), pinColor: UIColor = UIColor(red: 0.290, green: 0.565, blue: 0.886, alpha: 1.000), strokeColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) { //// General Declarations let context = UIGraphicsGetCurrentContext()! @@ -52,5 +51,4 @@ public class StyleKitMarker: NSObject { innerColor.setFill() ovalPath.fill() } - } diff --git a/MapboxNavigation/StyleManager.swift b/MapboxNavigation/StyleManager.swift index 705013d7d..5e83c62c1 100644 --- a/MapboxNavigation/StyleManager.swift +++ b/MapboxNavigation/StyleManager.swift @@ -1,6 +1,6 @@ import CoreLocation -import UIKit import Solar +import UIKit /** The `StyleManagerDelegate` protocol defines a set of methods used for controlling the style. @@ -29,7 +29,6 @@ public protocol StyleManagerDelegate: NSObjectProtocol { */ @objc(MBStyleManager) open class StyleManager: NSObject { - /** The receiver of the delegate. See `StyleManagerDelegate` for more information. */ @@ -42,7 +41,7 @@ open class StyleManager: NSObject { */ @objc public var automaticallyAdjustsStyleForTimeOfDay = true { didSet { - resetTimeOfDayTimer() + self.resetTimeOfDayTimer() } } @@ -56,12 +55,12 @@ open class StyleManager: NSObject { */ @objc public var styles = [Style]() { didSet { - applyStyle() - resetTimeOfDayTimer() + self.applyStyle() + self.resetTimeOfDayTimer() } } - internal var date: Date? + var date: Date? var currentStyleType: StyleType? @@ -70,11 +69,11 @@ open class StyleManager: NSObject { - parameter delegate: The receiver’s delegate */ - required public init(_ delegate: StyleManagerDelegate) { + public required init(_ delegate: StyleManagerDelegate) { self.delegate = delegate super.init() - resumeNotifications() - resetTimeOfDayTimer() + self.resumeNotifications() + self.resetTimeOfDayTimer() } deinit { @@ -83,8 +82,8 @@ open class StyleManager: NSObject { } func resumeNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(timeOfDayChanged), name: UIApplication.significantTimeChangeNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(preferredContentSizeChanged(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.timeOfDayChanged), name: UIApplication.significantTimeChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.preferredContentSizeChanged(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) } func suspendNotifications() { @@ -93,15 +92,15 @@ open class StyleManager: NSObject { } func resetTimeOfDayTimer() { - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(timeOfDayChanged), object: nil) + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.timeOfDayChanged), object: nil) - guard automaticallyAdjustsStyleForTimeOfDay && styles.count > 1 else { return } + guard self.automaticallyAdjustsStyleForTimeOfDay, self.styles.count > 1 else { return } guard let location = delegate?.locationFor(styleManager: self) else { return } guard let solar = Solar(date: date, coordinate: location.coordinate), - let sunrise = solar.sunrise, - let sunset = solar.sunset else { - return + let sunrise = solar.sunrise, + let sunset = solar.sunset else { + return } guard let interval = solar.date.intervalUntilTimeOfDayChanges(sunrise: sunrise, sunset: sunset) else { @@ -109,64 +108,62 @@ open class StyleManager: NSObject { return } - perform(#selector(timeOfDayChanged), with: nil, afterDelay: interval+1) + perform(#selector(self.timeOfDayChanged), with: nil, afterDelay: interval + 1) } @objc func preferredContentSizeChanged(_ notification: Notification) { - applyStyle() + self.applyStyle() } @objc func timeOfDayChanged() { - forceRefreshAppearanceIfNeeded() - resetTimeOfDayTimer() + self.forceRefreshAppearanceIfNeeded() + self.resetTimeOfDayTimer() } func applyStyle(type styleType: StyleType) { - guard currentStyleType != styleType else { return } + guard self.currentStyleType != styleType else { return } - NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(timeOfDayChanged), object: nil) + NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.timeOfDayChanged), object: nil) - for style in styles { - if style.styleType == styleType { - style.apply() - currentStyleType = styleType - delegate?.styleManager?(self, didApply: style) - } + for style in self.styles where style.styleType == styleType { + style.apply() + currentStyleType = styleType + delegate?.styleManager?(self, didApply: style) } - forceRefreshAppearance() + self.forceRefreshAppearance() } func applyStyle() { guard let location = delegate?.locationFor(styleManager: self) else { // We can't calculate sunset or sunrise w/o a location so just apply the first style if let style = styles.first, currentStyleType != style.styleType { - currentStyleType = style.styleType + self.currentStyleType = style.styleType style.apply() - delegate?.styleManager?(self, didApply: style) + self.delegate?.styleManager?(self, didApply: style) } return } // Single style usage - guard styles.count > 1 else { + guard self.styles.count > 1 else { if let style = styles.first, currentStyleType != style.styleType { - currentStyleType = style.styleType + self.currentStyleType = style.styleType style.apply() - delegate?.styleManager?(self, didApply: style) + self.delegate?.styleManager?(self, didApply: style) } return } - let styleTypeForTimeOfDay = styleType(for: location) - applyStyle(type: styleTypeForTimeOfDay) + let styleTypeForTimeOfDay = self.styleType(for: location) + self.applyStyle(type: styleTypeForTimeOfDay) } func styleType(for location: CLLocation) -> StyleType { guard let solar = Solar(date: date, coordinate: location.coordinate), - let sunrise = solar.sunrise, - let sunset = solar.sunset else { - return .day + let sunrise = solar.sunrise, + let sunset = solar.sunset else { + return .day } return solar.date.isNighttime(sunrise: sunrise, sunset: sunset) ? .night : .day @@ -175,18 +172,18 @@ open class StyleManager: NSObject { func forceRefreshAppearanceIfNeeded() { guard let location = delegate?.locationFor(styleManager: self) else { return } - let styleTypeForLocation = styleType(for: location) + let styleTypeForLocation = self.styleType(for: location) // If `styles` does not contain at least one style for the selected location, don't try and apply it. - let availableStyleTypesForLocation = styles.filter { $0.styleType == styleTypeForLocation } + let availableStyleTypesForLocation = self.styles.filter { $0.styleType == styleTypeForLocation } guard availableStyleTypesForLocation.count > 0 else { return } - guard currentStyleType != styleTypeForLocation else { + guard self.currentStyleType != styleTypeForLocation else { return } - applyStyle() - forceRefreshAppearance() + self.applyStyle() + self.forceRefreshAppearance() } func forceRefreshAppearance() { @@ -197,7 +194,7 @@ open class StyleManager: NSObject { } } - delegate?.styleManagerDidRefreshAppearance?(self) + self.delegate?.styleManagerDidRefreshAppearance?(self) } } @@ -209,7 +206,7 @@ extension Date { return nil } - if isNighttime(sunrise: sunrise, sunset: sunset) { + if self.isNighttime(sunrise: sunrise, sunset: sunset) { let sunriseComponents = calendar.dateComponents([.hour, .minute, .second], from: sunrise) guard let sunriseDate = calendar.date(from: sunriseComponents) else { return nil @@ -236,7 +233,7 @@ extension Date { extension Solar { init?(date: Date?, coordinate: CLLocationCoordinate2D) { - if let date = date { + if let date { self.init(for: date, coordinate: coordinate) } else { self.init(coordinate: coordinate) diff --git a/MapboxNavigation/StyleType.swift b/MapboxNavigation/StyleType.swift index 8153aa055..e72d4d304 100644 --- a/MapboxNavigation/StyleType.swift +++ b/MapboxNavigation/StyleType.swift @@ -2,7 +2,6 @@ import Foundation @objc(MBStyleType) public enum StyleType: Int, CustomStringConvertible { - case day case night @@ -22,9 +21,9 @@ public enum StyleType: Int, CustomStringConvertible { public var description: String { switch self { case .day: - return "day" + "day" case .night: - return "night" + "night" } } } diff --git a/MapboxNavigation/Transitioning.swift b/MapboxNavigation/Transitioning.swift index 28ef832c9..53d42b2ab 100644 --- a/MapboxNavigation/Transitioning.swift +++ b/MapboxNavigation/Transitioning.swift @@ -5,11 +5,10 @@ class Interactor: UIPercentDrivenInteractiveTransition { var shouldFinish = false } -class DismissAnimator: NSObject { } +class DismissAnimator: NSObject {} extension DismissAnimator: UIViewControllerAnimatedTransitioning { - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.5 + 0.5 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { @@ -18,23 +17,23 @@ extension DismissAnimator: UIViewControllerAnimatedTransitioning { let containerView = transitionContext.containerView let point = CGPoint(x: 0, y: toVC.view.bounds.maxY) - let height = fromVC.view.bounds.height-toVC.view.frame.minY + let height = fromVC.view.bounds.height - toVC.view.frame.minY let finalFrame = CGRect(origin: point, size: CGSize(width: fromVC.view.bounds.width, height: height)) - UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [.curveEaseInOut], animations: { + UIView.animate(withDuration: self.transitionDuration(using: transitionContext), delay: 0, options: [.curveEaseInOut], animations: { fromVC.view.frame = finalFrame containerView.backgroundColor = .clear - }) { _ in + }, completion: { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) - } + }) } } -class PresentAnimator: NSObject { } +class PresentAnimator: NSObject {} extension PresentAnimator: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.5 + 0.5 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { @@ -57,12 +56,12 @@ extension PresentAnimator: UIViewControllerAnimatedTransitioning { let finalFrame = CGRect(origin: CGPoint(x: 0, y: fromVC.view.bounds.height - height), size: CGSize(width: fromVC.view.bounds.width, height: height)) - UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [.curveEaseInOut], animations: { + UIView.animate(withDuration: self.transitionDuration(using: transitionContext), delay: 0, options: [.curveEaseInOut], animations: { toView.frame = finalFrame containerView.backgroundColor = UIColor.black.withAlphaComponent(0.5) - }) { _ in + }, completion: { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) - } + }) } } @@ -72,7 +71,7 @@ extension PresentAnimator: UIViewControllerAnimatedTransitioning { @objc optional func handleDismissPan(_ sender: UIPanGestureRecognizer) } -fileprivate extension Selector { +private extension Selector { static let handleDismissDrag = #selector(UIViewController.handleDismissPan(_:)) } @@ -83,8 +82,7 @@ extension DismissDraggable where Self: UIViewController { } } -fileprivate extension UIViewController { - +private extension UIViewController { @objc func handleDismissPan(_ sender: UIPanGestureRecognizer) { self.handlePan(sender) } @@ -108,7 +106,11 @@ fileprivate extension UIViewController { vc.interactor.cancel() case .ended: vc.interactor.hasStarted = false - vc.interactor.shouldFinish ? vc.interactor.finish() : vc.interactor.cancel() + if vc.interactor.shouldFinish { + vc.interactor.finish() + } else { + vc.interactor.cancel() + } default: break } diff --git a/MapboxNavigation/UICollectionView.swift b/MapboxNavigation/UICollectionView.swift index 5a938040c..f8f72296d 100644 --- a/MapboxNavigation/UICollectionView.swift +++ b/MapboxNavigation/UICollectionView.swift @@ -1,12 +1,11 @@ import UIKit extension UICollectionView { - func numberOfRows(using delegate: UIViewController & UICollectionViewDelegateFlowLayout) -> Int { let layout = (collectionViewLayout as? UICollectionViewFlowLayout)! var totalNumberOfItems = 0 - for section in 0...numberOfSections - 1 { + for section in 0 ... numberOfSections - 1 { totalNumberOfItems += numberOfItems(inSection: section) } diff --git a/MapboxNavigation/UIEdgeInsets.swift b/MapboxNavigation/UIEdgeInsets.swift index b53f8c5d0..12a96e4b6 100644 --- a/MapboxNavigation/UIEdgeInsets.swift +++ b/MapboxNavigation/UIEdgeInsets.swift @@ -1,17 +1,17 @@ import Foundation -import UIKit import MapboxDirections +import UIKit public extension UIEdgeInsets { - static func +(left: UIEdgeInsets, right: UIEdgeInsets) -> UIEdgeInsets { - return UIEdgeInsets(top: left.top + right.top, - left: left.left + right.left, - bottom: left.bottom + right.bottom, - right: left.right + right.right ) + static func + (left: UIEdgeInsets, right: UIEdgeInsets) -> UIEdgeInsets { + UIEdgeInsets(top: left.top + right.top, + left: left.left + right.left, + bottom: left.bottom + right.bottom, + right: left.right + right.right) } - static func >(lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> Bool { - return (lhs.top + lhs.left + lhs.bottom + lhs.right) + static func > (lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> Bool { + (lhs.top + lhs.left + lhs.bottom + lhs.right) > (rhs.top + rhs.left + rhs.bottom + rhs.right) } } diff --git a/MapboxNavigation/UIFont.swift b/MapboxNavigation/UIFont.swift index 7f402da69..60937158d 100644 --- a/MapboxNavigation/UIFont.swift +++ b/MapboxNavigation/UIFont.swift @@ -1,24 +1,21 @@ import UIKit extension UIFont { - var fontSizeMultiplier: CGFloat { - get { - switch UIApplication.shared.preferredContentSizeCategory { - case UIContentSizeCategory.accessibilityExtraExtraExtraLarge: return 23 / 16 - case UIContentSizeCategory.accessibilityExtraExtraLarge: return 22 / 16 - case UIContentSizeCategory.accessibilityExtraLarge: return 21 / 16 - case UIContentSizeCategory.accessibilityLarge: return 20 / 16 - case UIContentSizeCategory.accessibilityMedium: return 19 / 16 - case UIContentSizeCategory.extraExtraExtraLarge: return 19 / 16 - case UIContentSizeCategory.extraExtraLarge: return 18 / 16 - case UIContentSizeCategory.extraLarge: return 17 / 16 - case UIContentSizeCategory.large: return 1 - case UIContentSizeCategory.medium: return 15 / 16 - case UIContentSizeCategory.small: return 14 / 16 - case UIContentSizeCategory.extraSmall: return 13 / 16 - default: return 1 - } + switch UIApplication.shared.preferredContentSizeCategory { + case UIContentSizeCategory.accessibilityExtraExtraExtraLarge: 23 / 16 + case UIContentSizeCategory.accessibilityExtraExtraLarge: 22 / 16 + case UIContentSizeCategory.accessibilityExtraLarge: 21 / 16 + case UIContentSizeCategory.accessibilityLarge: 20 / 16 + case UIContentSizeCategory.accessibilityMedium: 19 / 16 + case UIContentSizeCategory.extraExtraExtraLarge: 19 / 16 + case UIContentSizeCategory.extraExtraLarge: 18 / 16 + case UIContentSizeCategory.extraLarge: 17 / 16 + case UIContentSizeCategory.large: 1 + case UIContentSizeCategory.medium: 15 / 16 + case UIContentSizeCategory.small: 14 / 16 + case UIContentSizeCategory.extraSmall: 13 / 16 + default: 1 } } @@ -26,17 +23,17 @@ extension UIFont { Returns an adjusted font for the `preferredContentSizeCategory`. */ @objc public var adjustedFont: UIFont { - let font = with(multiplier: fontSizeMultiplier) + let font = self.with(multiplier: self.fontSizeMultiplier) return font } func with(multiplier: CGFloat) -> UIFont { - let font = UIFont(descriptor: fontDescriptor, size: pointSize * fontSizeMultiplier) + let font = UIFont(descriptor: fontDescriptor, size: pointSize * self.fontSizeMultiplier) return font } func with(fontFamily: String?) -> UIFont { - guard let fontFamily = fontFamily else { return self } + guard let fontFamily else { return self } let weight = (fontDescriptor.object(forKey: .traits) as! [String: Any])[UIFontDescriptor.TraitKey.weight.rawValue] let descriptor = UIFontDescriptor(name: fontName, size: pointSize).withFamily(fontFamily).addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: weight]]) return UIFont(descriptor: descriptor, size: pointSize) diff --git a/MapboxNavigation/UIGestureRecognizer.swift b/MapboxNavigation/UIGestureRecognizer.swift index e2ff4f2a9..705a9ae84 100644 --- a/MapboxNavigation/UIGestureRecognizer.swift +++ b/MapboxNavigation/UIGestureRecognizer.swift @@ -3,12 +3,12 @@ import UIKit extension UIGestureRecognizer { var point: CGPoint? { - guard let view = view else { return nil } + guard let view else { return nil } return location(in: view) } func requireFailure(of gestures: [UIGestureRecognizer]?) { - guard let gestures = gestures else { return } - gestures.forEach(self.require(toFail:)) + guard let gestures else { return } + gestures.forEach(require(toFail:)) } } diff --git a/MapboxNavigation/UIImage.swift b/MapboxNavigation/UIImage.swift index db927c2fb..b3f1b2126 100644 --- a/MapboxNavigation/UIImage.swift +++ b/MapboxNavigation/UIImage.swift @@ -6,7 +6,7 @@ extension UIImage { let imageView = UIImageView(frame: CGRect(origin: .zero, size: square)) imageView.contentMode = .center imageView.image = self - imageView.layer.cornerRadius = square.width/2 + imageView.layer.cornerRadius = square.width / 2 imageView.layer.masksToBounds = true imageView.layer.borderWidth = width imageView.layer.backgroundColor = color.cgColor diff --git a/MapboxNavigation/UIStackView.swift b/MapboxNavigation/UIStackView.swift index e8352e2ee..a60026446 100644 --- a/MapboxNavigation/UIStackView.swift +++ b/MapboxNavigation/UIStackView.swift @@ -4,10 +4,10 @@ extension UIStackView { convenience init(orientation: NSLayoutConstraint.Axis, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil, spacing: CGFloat? = nil, autoLayout: Bool = false) { self.init(frame: .zero) axis = orientation - if let alignment = alignment { self.alignment = alignment } - if let distribution = distribution { self.distribution = distribution } - if let spacing = spacing { self.spacing = spacing } - if (autoLayout) { translatesAutoresizingMaskIntoConstraints = false } + if let alignment { self.alignment = alignment } + if let distribution { self.distribution = distribution } + if let spacing { self.spacing = spacing } + if autoLayout { translatesAutoresizingMaskIntoConstraints = false } } func addArrangedSubviews(_ views: [UIView]) { diff --git a/MapboxNavigation/UIView.swift b/MapboxNavigation/UIView.swift index 17eeae2e5..920016154 100644 --- a/MapboxNavigation/UIView.swift +++ b/MapboxNavigation/UIView.swift @@ -30,9 +30,9 @@ extension UIView { } func applyGradient(colors: [UIColor], locations: [NSNumber]? = nil) { - let gradient: CAGradientLayer = CAGradientLayer() - gradient.frame = self.bounds - gradient.colors = colors.map { $0.cgColor } + let gradient = CAGradientLayer() + gradient.frame = bounds + gradient.colors = colors.map(\.cgColor) gradient.locations = locations if let sublayers = layer.sublayers, !sublayers.isEmpty, let sublayer = sublayers.first { @@ -51,14 +51,14 @@ extension UIView { rippleLayer.startAnimation() } - class func fromNib() -> ViewType? { + class func fromNib() -> ViewType? { let nibName = String(describing: ViewType.self) return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)?[0] as? ViewType } func constraints(affecting view: UIView?) -> [NSLayoutConstraint]? { - guard let view = view else { return nil } - return constraints.filter { constraint in + guard let view else { return nil } + return self.constraints.filter { constraint in if let first = constraint.firstItem as? UIView, first == view { return true } @@ -70,8 +70,8 @@ extension UIView { } func pinInSuperview(respectingMargins margins: Bool = false) { - guard let superview = superview else { return } - let guide: Anchorable = (margins) ? superview.layoutMarginsGuide : superview + guard let superview else { return } + let guide: Anchorable = margins ? superview.layoutMarginsGuide : superview let constraints = [ topAnchor.constraint(equalTo: guide.topAnchor), @@ -82,9 +82,8 @@ extension UIView { NSLayoutConstraint.activate(constraints) } - class func forAutoLayout(frame: CGRect = .zero, hidden: Bool = false) -> ViewType { - let view = ViewType.init(frame: frame) + let view = ViewType(frame: frame) view.isHidden = hidden view.translatesAutoresizingMaskIntoConstraints = false return view @@ -141,7 +140,7 @@ extension UIView { let size = CGSize(width: frame.size.width, height: frame.size.height) UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) guard let currentContext = UIGraphicsGetCurrentContext() else { return nil } - layer.render(in:currentContext) + layer.render(in: currentContext) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image @@ -151,9 +150,10 @@ extension UIView { class RippleLayer: CAReplicatorLayer { var animationGroup: CAAnimationGroup? { didSet { - animationGroup?.delegate = self + self.animationGroup?.delegate = self } } + var rippleRadius: CGFloat = 100 var rippleColor: UIColor = .red var rippleRepeatCount: Float = .greatestFiniteMagnitude @@ -163,44 +163,44 @@ class RippleLayer: CAReplicatorLayer { override init() { super.init() - commonInit() + self.commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { - setupRippleEffect() - repeatCount = Float(rippleRepeatCount) + self.setupRippleEffect() + repeatCount = Float(self.rippleRepeatCount) } override func layoutSublayers() { super.layoutSublayers() - rippleEffect?.bounds = CGRect(x: 0, y: 0, width: rippleRadius*2, height: rippleRadius*2) - rippleEffect?.cornerRadius = rippleRadius + self.rippleEffect?.bounds = CGRect(x: 0, y: 0, width: self.rippleRadius * 2, height: self.rippleRadius * 2) + self.rippleEffect?.cornerRadius = self.rippleRadius instanceCount = 3 instanceDelay = 0.4 } func setupRippleEffect() { - rippleEffect = CALayer() - rippleEffect?.borderWidth = CGFloat(rippleWidth) - rippleEffect?.borderColor = rippleColor.cgColor - rippleEffect?.opacity = 0 + self.rippleEffect = CALayer() + self.rippleEffect?.borderWidth = CGFloat(self.rippleWidth) + self.rippleEffect?.borderColor = self.rippleColor.cgColor + self.rippleEffect?.opacity = 0 - addSublayer(rippleEffect!) + addSublayer(self.rippleEffect!) } func startAnimation() { - animationGroup = rippleAnimationGroup() - rippleEffect?.add(animationGroup!, forKey: "ripple") + self.animationGroup = self.rippleAnimationGroup() + self.rippleEffect?.add(self.animationGroup!, forKey: "ripple") } func stopAnimation() { - rippleEffect?.removeAnimation(forKey: "ripple") + self.rippleEffect?.removeAnimation(forKey: "ripple") } func rippleAnimationGroup() -> CAAnimationGroup { @@ -208,7 +208,7 @@ class RippleLayer: CAReplicatorLayer { let group = CAAnimationGroup() group.duration = duration - group.repeatCount = self.repeatCount + group.repeatCount = repeatCount group.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default) let scaleAnimation = CABasicAnimation(keyPath: "transform.scale.xy") @@ -219,7 +219,7 @@ class RippleLayer: CAReplicatorLayer { let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity") opacityAnimation.duration = duration let fromAlpha = 1.0 - opacityAnimation.values = [fromAlpha, (fromAlpha * 0.5), 0] + opacityAnimation.values = [fromAlpha, fromAlpha * 0.5, 0] opacityAnimation.keyTimes = [0, 0.2, 1] group.animations = [scaleAnimation, opacityAnimation] @@ -231,7 +231,7 @@ class RippleLayer: CAReplicatorLayer { extension RippleLayer: CAAnimationDelegate { func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { if let count = rippleEffect?.animationKeys()?.count, count > 0 { - rippleEffect?.removeAllAnimations() + self.rippleEffect?.removeAllAnimations() } } } @@ -247,4 +247,3 @@ protocol Anchorable { extension UIView: Anchorable {} extension UILayoutGuide: Anchorable {} - diff --git a/MapboxNavigation/UIViewController.swift b/MapboxNavigation/UIViewController.swift index 175b00002..72ce08126 100644 --- a/MapboxNavigation/UIViewController.swift +++ b/MapboxNavigation/UIViewController.swift @@ -1,23 +1,21 @@ import UIKit - extension UIViewController { - func topMostViewController() -> UIViewController? { - return topViewController(controller: self) + self.topViewController(controller: self) } func topViewController(controller: UIViewController? = nil) -> UIViewController? { if let navigationController = controller as? UINavigationController { - return topViewController(controller: navigationController.visibleViewController) + return self.topViewController(controller: navigationController.visibleViewController) } if let tabController = controller as? UITabBarController { if let selected = tabController.selectedViewController { - return topViewController(controller: selected) + return self.topViewController(controller: selected) } } if let presented = controller?.presentedViewController { - return topViewController(controller: presented) + return self.topViewController(controller: presented) } return controller } @@ -25,7 +23,6 @@ extension UIViewController { extension UIWindow { func viewControllerInStack(of type: T.Type? = nil) -> T? { - if let vc = rootViewController as? T { return vc } else if let vc = rootViewController?.presentedViewController as? T { diff --git a/MapboxNavigation/UserCourseView.swift b/MapboxNavigation/UserCourseView.swift index ba175c5b1..2fc6c4917 100644 --- a/MapboxNavigation/UserCourseView.swift +++ b/MapboxNavigation/UserCourseView.swift @@ -1,6 +1,6 @@ -import UIKit -import Turf import MapLibre +import Turf +import UIKit let PuckSize: CGFloat = 45 let ArrowSize = PuckSize * 0.6 @@ -39,14 +39,12 @@ extension UIView { */ @objc(MBUserPuckCourseView) public class UserPuckCourseView: UIView, UserCourseView { - /** Transforms the location of the user puck. */ public func update(location: CLLocation, pitch: CGFloat, direction: CLLocationDegrees, animated: Bool, tracksUserCourse: Bool) { let duration: TimeInterval = animated ? 1 : 0 UIView.animate(withDuration: duration, delay: 0, options: [.beginFromCurrentState, .curveLinear], animations: { - let angle = tracksUserCourse ? 0 : CLLocationDegrees(direction - location.course) self.puckView.layer.setAffineTransform(CGAffineTransform.identity.rotated(by: -CGFloat(angle.toRadians()))) @@ -83,37 +81,36 @@ public class UserPuckCourseView: UIView, UserCourseView { override init(frame: CGRect) { super.init(frame: frame) - commonInit() + self.commonInit() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - commonInit() + self.commonInit() } func commonInit() { - puckView = UserPuckStyleKitView(frame: bounds) + self.puckView = UserPuckStyleKitView(frame: bounds) backgroundColor = .clear - puckView.backgroundColor = .clear - addSubview(puckView) + self.puckView.backgroundColor = .clear + addSubview(self.puckView) } } class UserPuckStyleKitView: UIView { - - var fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000) { + var fillColor: UIColor = .init(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000) { didSet { setNeedsDisplay() } } - var puckColor: UIColor = UIColor(red: 0.149, green: 0.239, blue: 0.341, alpha: 1.000) { + var puckColor: UIColor = .init(red: 0.149, green: 0.239, blue: 0.341, alpha: 1.000) { didSet { setNeedsDisplay() } } - var shadowColor: UIColor = UIColor(red: 0.149, green: 0.239, blue: 0.341, alpha: 0.160) { + var shadowColor: UIColor = .init(red: 0.149, green: 0.239, blue: 0.341, alpha: 0.160) { didSet { setNeedsDisplay() } @@ -121,11 +118,10 @@ class UserPuckStyleKitView: UIView { override func draw(_ rect: CGRect) { super.draw(rect) - drawNavigation_puck(fillColor: fillColor, puckColor: puckColor, shadowColor: shadowColor, circleColor: fillColor) + self.drawNavigation_puck(fillColor: self.fillColor, puckColor: self.puckColor, shadowColor: self.shadowColor, circleColor: self.fillColor) } func drawNavigation_puck(fillColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), puckColor: UIColor = UIColor(red: 0.149, green: 0.239, blue: 0.341, alpha: 1.000), shadowColor: UIColor = UIColor(red: 0.149, green: 0.239, blue: 0.341, alpha: 0.160), circleColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) { - //// Canvas 2 //// navigation_pluck //// Oval 7 diff --git a/MapboxNavigation/VisualInstruction.swift b/MapboxNavigation/VisualInstruction.swift index 93fd2e016..831b8b68d 100644 --- a/MapboxNavigation/VisualInstruction.swift +++ b/MapboxNavigation/VisualInstruction.swift @@ -4,16 +4,15 @@ import CarPlay #endif extension VisualInstruction { - var containsLaneIndications: Bool { - return components.contains(where: { $0 is LaneIndicationComponent }) + components.contains(where: { $0 is LaneIndicationComponent }) } -#if canImport(CarPlay) + #if canImport(CarPlay) @available(iOS 12.0, *) func maneuverImageSet(side: DrivingSide) -> CPImageSet? { let colors: [UIColor] = [.black, .white] - let blackAndWhiteManeuverIcons: [UIImage] = colors.compactMap { (color) in + let blackAndWhiteManeuverIcons: [UIImage] = colors.compactMap { color in let mv = ManeuverView() mv.frame = CGRect(x: 0, y: 0, width: 30, height: 30) mv.primaryColor = color @@ -21,7 +20,7 @@ extension VisualInstruction { mv.scale = UIScreen.main.scale mv.visualInstruction = self let image = mv.imageRepresentation - return shouldFlipImage(side: side) ? image?.withHorizontallyFlippedOrientation() : image + return self.shouldFlipImage(side: side) ? image?.withHorizontallyFlippedOrientation() : image } guard blackAndWhiteManeuverIcons.count == 2 else { return nil } return CPImageSet(lightContentImage: blackAndWhiteManeuverIcons[1], darkContentImage: blackAndWhiteManeuverIcons[0]) @@ -58,5 +57,5 @@ extension VisualInstruction { return instructionLabel.attributedText } -#endif + #endif } diff --git a/MapboxNavigation/VisualInstructionComponent.swift b/MapboxNavigation/VisualInstructionComponent.swift index 3af9ac2c5..be7e625c4 100644 --- a/MapboxNavigation/VisualInstructionComponent.swift +++ b/MapboxNavigation/VisualInstructionComponent.swift @@ -1,17 +1,16 @@ -import UIKit import MapboxDirections +import UIKit extension VisualInstructionComponent { - static let scale = UIScreen.main.scale var cacheKey: String? { switch type { case .exit, .exitCode: - guard let exitCode = self.text else { return nil } + guard let exitCode = text else { return nil } return "exit-" + exitCode + "-\(VisualInstructionComponent.scale)" case .image: - guard let imageURL = imageURL else { return genericCacheKey } + guard let imageURL else { return self.genericCacheKey } return "\(imageURL.absoluteString)-\(VisualInstructionComponent.scale)" case .text, .delimiter: return nil @@ -19,6 +18,6 @@ extension VisualInstructionComponent { } var genericCacheKey: String { - return "generic-" + (text ?? "nil") + "generic-" + (text ?? "nil") } } diff --git a/MapboxNavigation/Waypoint.swift b/MapboxNavigation/Waypoint.swift index 863e20777..ef7e087ff 100644 --- a/MapboxNavigation/Waypoint.swift +++ b/MapboxNavigation/Waypoint.swift @@ -3,15 +3,15 @@ import MapboxDirections extension Waypoint { var location: CLLocation { - return CLLocation.init(latitude: coordinate.latitude, longitude: coordinate.longitude) + CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) } var instructionComponent: VisualInstructionComponent? { - guard let name = name else { return nil } + guard let name else { return nil } return VisualInstructionComponent(type: .text, text: name, imageURL: nil, abbreviation: nil, abbreviationPriority: NSNotFound) } var instructionComponents: [VisualInstructionComponent]? { - return (instructionComponent != nil) ? [instructionComponent!] : nil + (self.instructionComponent != nil) ? [self.instructionComponent!] : nil } } diff --git a/MapboxNavigationTests/Sources/Support/Constants.swift b/MapboxNavigationTests/Sources/Support/Constants.swift index 66959459d..b0794a6ef 100644 --- a/MapboxNavigationTests/Sources/Support/Constants.swift +++ b/MapboxNavigationTests/Sources/Support/Constants.swift @@ -1,9 +1,9 @@ import XCTest extension CGSize { - static let iPhone5 : CGSize = CGSize(width: 320, height: 568) - static let iPhone6Plus : CGSize = CGSize(width: 414, height: 736) - static let iPhoneX : CGSize = CGSize(width: 375, height: 812) + static let iPhone5: CGSize = .init(width: 320, height: 568) + static let iPhone6Plus: CGSize = .init(width: 414, height: 736) + static let iPhoneX: CGSize = .init(width: 375, height: 812) } struct ShieldImage { diff --git a/MapboxNavigationTests/Sources/Support/Fixture.swift b/MapboxNavigationTests/Sources/Support/Fixture.swift index a2b9c0d8d..32eb1c630 100644 --- a/MapboxNavigationTests/Sources/Support/Fixture.swift +++ b/MapboxNavigationTests/Sources/Support/Fixture.swift @@ -1,41 +1,26 @@ -import XCTest -import MapboxCoreNavigation +import CoreLocation import Foundation +import MapboxCoreNavigation import MapboxDirections -import CoreLocation +import XCTest -internal extension Fixture { - - class func downloadRouteFixture(coordinates: [CLLocationCoordinate2D], fileName: String, completion: @escaping () -> Void) { - let accessToken = "<# Mapbox Access Token #>" - let directions = Directions(accessToken: accessToken) - - let options = RouteOptions(coordinates: coordinates, profileIdentifier: .automobileAvoidingTraffic) - options.includesSteps = true - options.routeShapeResolution = .full - let filePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(fileName) - - _ = directions.calculate(options, completionHandler: { (waypoints, routes, error) in - guard let route = routes?.first else { return } - - NSKeyedArchiver.archiveRootObject(route, toFile: filePath) - print("Route downloaded to \(filePath)") - completion() - }) - } - +extension Fixture { class var blankStyle: URL { let path = Bundle.module.path(forResource: "EmptyStyle", ofType: "json") return URL(fileURLWithPath: path!) } - + + class func routeWithBannerInstructions() -> Route { + route(from: "route-with-banner-instructions", bundle: .module, waypoints: [Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.795042, longitude: -122.413165)), Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.7727, longitude: -122.433378))]) + } +} + +// MARK: - Private + +private extension Fixture { class func route(from jsonFile: String, bundle: Bundle, waypoints: [Waypoint]) -> Route { let response = JSONFromFileNamed(name: jsonFile, bundle: bundle) - let jsonRoute = (response["routes"] as! [AnyObject]).first as! [String : Any] + let jsonRoute = (response["routes"] as! [AnyObject]).first as! [String: Any] return Route(json: jsonRoute, waypoints: waypoints, options: RouteOptions(waypoints: waypoints)) } - - class func routeWithBannerInstructions() -> Route { - return route(from: "route-with-banner-instructions", bundle: .module, waypoints: [Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.795042, longitude: -122.413165)), Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.7727, longitude: -122.433378))]) - } } diff --git a/MapboxNavigationTests/Sources/Support/ImageDownloadOperationSpy.swift b/MapboxNavigationTests/Sources/Support/ImageDownloadOperationSpy.swift index 21fb059d8..3af5a8b0f 100644 --- a/MapboxNavigationTests/Sources/Support/ImageDownloadOperationSpy.swift +++ b/MapboxNavigationTests/Sources/Support/ImageDownloadOperationSpy.swift @@ -1,18 +1,17 @@ import Foundation -import UIKit @testable import MapboxNavigation +import UIKit /** * This class can be used as a replacement for the `ImageDownloader`'s default download operation class, for spying on url download requests as well as returning canned responses ad hoc. */ class ImageDownloadOperationSpy: Operation, ImageDownload { - private static var operations: [URL: ImageDownloadOperationSpy] = [:] private(set) var request: URLRequest? - weak private var session: URLSession? + private weak var session: URLSession? - private var completionBlocks: Array = [] + private var completionBlocks: [ImageDownloadCompletionBlock] = [] required init(request: URLRequest, in session: URLSession) { self.request = request @@ -24,14 +23,14 @@ class ImageDownloadOperationSpy: Operation, ImageDownload { } static func reset() { - operations.removeAll() + self.operations.removeAll() } /** * Retrieve an operation spy instance for the given URL, which can then be used to inspect and/or execute completion handlers */ static func operationForURL(_ URL: URL) -> ImageDownloadOperationSpy? { - return operations[URL] + self.operations[URL] } func addCompletion(_ completion: @escaping ImageDownloadCompletionBlock) { @@ -44,21 +43,19 @@ class ImageDownloadOperationSpy: Operation, ImageDownload { } func shouldDecompressImages() -> Bool { - return false + false } - func setShouldDecompressImages(_ value: Bool) { - } + func setShouldDecompressImages(_ value: Bool) {} func credential() -> URLCredential? { fatalError("credential() has not been implemented") } - func setCredential(_ value: URLCredential?) { - } + func setCredential(_ value: URLCredential?) {} func fireAllCompletions(_ image: UIImage, data: Data?, error: Error?) { - completionBlocks.forEach { completion in + for completion in self.completionBlocks { completion(image, data, error) } } diff --git a/MapboxNavigationTests/Sources/Support/ImageLoadingURLProtocolSpy.swift b/MapboxNavigationTests/Sources/Support/ImageLoadingURLProtocolSpy.swift index 423855675..607e0d96c 100644 --- a/MapboxNavigationTests/Sources/Support/ImageLoadingURLProtocolSpy.swift +++ b/MapboxNavigationTests/Sources/Support/ImageLoadingURLProtocolSpy.swift @@ -5,7 +5,6 @@ import XCTest * This class stubs out the URL loading for any request url registered in `registerData(_, forURL:)` and records requests for a given URL for inspection. Note that unstubbed URLs will continue to load as normal. */ class ImageLoadingURLProtocolSpy: URLProtocol { - private static var responseData: [URL: Data] = [:] private static var activeRequests: [URL: URLRequest] = [:] private static var pastRequests: [URL: URLRequest] = [:] @@ -14,31 +13,31 @@ class ImageLoadingURLProtocolSpy: URLProtocol { private var loadingStopped: Bool = false override class func canInit(with request: URLRequest) -> Bool { - return responseData.keys.contains(request.url!) + self.responseData.keys.contains(request.url!) } override class func canInit(with task: URLSessionTask) -> Bool { - let keys = responseData.keys + let keys = self.responseData.keys return keys.contains(task.currentRequest!.url!) || keys.contains(task.originalRequest!.url!) } override class func canonicalRequest(for request: URLRequest) -> URLRequest { - return request + request } override class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool { - return a.url == b.url + a.url == b.url } override func startLoading() { - let request = self.request + let request = request guard let url = request.url else { XCTFail("Somehow the request doesn't have a URL") return } - guard let data = ImageLoadingURLProtocolSpy.responseData[url], let image: UIImage = UIImage(data: data), let client = client else { + guard let data = ImageLoadingURLProtocolSpy.responseData[url], let image = UIImage(data: data), let client else { XCTFail("No valid image data found for url: \(url)") return } @@ -56,7 +55,7 @@ class ImageLoadingURLProtocolSpy: URLProtocol { ImageLoadingURLProtocolSpy.activeRequests[url] = request // send an NSHTTPURLResponse to the client - let response = HTTPURLResponse.init(url: url, statusCode: 200, httpVersion: "1.1", headerFields: nil) + let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "1.1", headerFields: nil) client.urlProtocol(self, didReceive: response!, cacheStoragePolicy: .notAllowed) ImageLoadingURLProtocolSpy.imageLoadingSemaphore.wait() @@ -72,37 +71,37 @@ class ImageLoadingURLProtocolSpy: URLProtocol { } override func stopLoading() { - loadingStopped = true + self.loadingStopped = true } /** * Registers data for a given URL */ class func registerData(_ data: Data, forURL url: URL) { - responseData[url] = data + self.responseData[url] = data } /** * Reset stubbed data, active and past requests */ class func reset() { - responseData = [:] - activeRequests = [:] - pastRequests = [:] + self.responseData = [:] + self.activeRequests = [:] + self.pastRequests = [:] } /** * Indicates whether a request for the given URL is in progress */ class func hasActiveRequestForURL(_ url: URL) -> Bool { - return activeRequests.keys.contains(url) + self.activeRequests.keys.contains(url) } /** * Returns the most recently completed request for the given URL */ class func pastRequestForURL(_ url: URL) -> URLRequest? { - return pastRequests[url] + self.pastRequests[url] } /** @@ -118,5 +117,4 @@ class ImageLoadingURLProtocolSpy: URLProtocol { class func resumeImageLoading() { ImageLoadingURLProtocolSpy.imageLoadingSemaphore.signal() } - } diff --git a/MapboxNavigationTests/Sources/Tests/CPMapTemplateTests.swift b/MapboxNavigationTests/Sources/Tests/CPMapTemplateTests.swift index 86c026a17..4d0bf4a6b 100644 --- a/MapboxNavigationTests/Sources/Tests/CPMapTemplateTests.swift +++ b/MapboxNavigationTests/Sources/Tests/CPMapTemplateTests.swift @@ -1,5 +1,5 @@ -import XCTest @testable import MapboxNavigation +import XCTest #if canImport(CarPlay) import CarPlay diff --git a/MapboxNavigationTests/Sources/Tests/DataCacheTests.swift b/MapboxNavigationTests/Sources/Tests/DataCacheTests.swift index 65f245dde..802b4b99d 100644 --- a/MapboxNavigationTests/Sources/Tests/DataCacheTests.swift +++ b/MapboxNavigationTests/Sources/Tests/DataCacheTests.swift @@ -1,14 +1,12 @@ -import XCTest @testable import MapboxNavigation - +import XCTest class DataCacheTests: XCTestCase { - - let cache: DataCache = DataCache() + let cache: DataCache = .init() private func clearDisk() { let semaphore = DispatchSemaphore(value: 0) - cache.clearDisk { + self.cache.clearDisk { semaphore.signal() } let semaphoreResult = semaphore.wait(timeout: XCTestCase.NavigationTests.timeout) @@ -17,29 +15,27 @@ class DataCacheTests: XCTestCase { override func setUp() { super.setUp() - self.continueAfterFailure = false + continueAfterFailure = false - cache.clearMemory() - clearDisk() + self.cache.clearMemory() + self.clearDisk() } let dataKey = "dataKey" var exampleData: Data? { - get { - let bundle = Bundle.module - do { - return try NSData.init(contentsOf: bundle.url(forResource: "route", withExtension: "json")!) as Data - } catch { - XCTFail("Failed to create data") - return nil - } + let bundle = Bundle.module + do { + return try NSData(contentsOf: bundle.url(forResource: "route", withExtension: "json")!) as Data + } catch { + XCTFail("Failed to create data") + return nil } } private func storeDataInMemory() { let semaphore = DispatchSemaphore(value: 0) - cache.store(exampleData!, forKey: dataKey, toDisk: false) { + self.cache.store(self.exampleData!, forKey: self.dataKey, toDisk: false) { semaphore.signal() } let semaphoreResult = semaphore.wait(timeout: XCTestCase.NavigationTests.timeout) @@ -48,7 +44,7 @@ class DataCacheTests: XCTestCase { private func storeDataOnDisk() { let semaphore = DispatchSemaphore(value: 0) - cache.store(exampleData!, forKey: dataKey, toDisk: true) { + self.cache.store(self.exampleData!, forKey: self.dataKey, toDisk: true) { semaphore.signal() } let semaphoreResult = semaphore.wait(timeout: XCTestCase.NavigationTests.timeout) @@ -58,45 +54,45 @@ class DataCacheTests: XCTestCase { // MARK: Tests func testStoringDataInMemoryOnly() { - storeDataInMemory() + self.storeDataInMemory() - let returnedData = cache.data(forKey: dataKey) + let returnedData = self.cache.data(forKey: self.dataKey) XCTAssertNotNil(returnedData) } func testStoringDataOnDisk() { - storeDataOnDisk() + self.storeDataOnDisk() - var returnedData = cache.data(forKey: dataKey) + var returnedData = self.cache.data(forKey: self.dataKey) XCTAssertNotNil(returnedData) - cache.clearMemory() + self.cache.clearMemory() - returnedData = cache.data(forKey: dataKey) + returnedData = self.cache.data(forKey: self.dataKey) XCTAssertNotNil(returnedData) } func testResettingCache() { - storeDataInMemory() + self.storeDataInMemory() - cache.clearMemory() + self.cache.clearMemory() - XCTAssertNil(cache.data(forKey: dataKey)) + XCTAssertNil(self.cache.data(forKey: self.dataKey)) - storeDataOnDisk() + self.storeDataOnDisk() - cache.clearMemory() - clearDisk() + self.cache.clearMemory() + self.clearDisk() - XCTAssertNil(cache.data(forKey: dataKey)) + XCTAssertNil(self.cache.data(forKey: self.dataKey)) } func testClearingMemoryCacheOnMemoryWarning() { - storeDataInMemory() + self.storeDataInMemory() NotificationCenter.default.post(name: UIApplication.didReceiveMemoryWarningNotification, object: nil) - XCTAssertNil(cache.data(forKey: dataKey)) + XCTAssertNil(self.cache.data(forKey: self.dataKey)) } func testNotificationObserverDoesNotCrash() { @@ -110,13 +106,13 @@ class DataCacheTests: XCTestCase { func testCacheKeyForKey() { let threeMileInstruction = "Continue on I-80 East for 3 miles" let sixMileInstruction = "Continue on I-80 East for 6 miles" - XCTAssertNotEqual(cache.fileCache.cacheKeyForKey(threeMileInstruction), cache.fileCache.cacheKeyForKey(sixMileInstruction)) - XCTAssertNotEqual(cache.fileCache.cacheKeyForKey(""), cache.fileCache.cacheKeyForKey(" ")) - XCTAssertNotEqual(cache.fileCache.cacheKeyForKey("i"), cache.fileCache.cacheKeyForKey("I")) - XCTAssertNotEqual(cache.fileCache.cacheKeyForKey("{"), cache.fileCache.cacheKeyForKey("}")) - XCTAssertEqual(cache.fileCache.cacheKeyForKey("hello"), cache.fileCache.cacheKeyForKey("hello")) - XCTAssertEqual(cache.fileCache.cacheKeyForKey("https://cool.com/neat"), cache.fileCache.cacheKeyForKey("https://cool.com/neat")) - XCTAssertEqual(cache.fileCache.cacheKeyForKey("-"), cache.fileCache.cacheKeyForKey("-")) + XCTAssertNotEqual(self.cache.fileCache.cacheKeyForKey(threeMileInstruction), self.cache.fileCache.cacheKeyForKey(sixMileInstruction)) + XCTAssertNotEqual(self.cache.fileCache.cacheKeyForKey(""), self.cache.fileCache.cacheKeyForKey(" ")) + XCTAssertNotEqual(self.cache.fileCache.cacheKeyForKey("i"), self.cache.fileCache.cacheKeyForKey("I")) + XCTAssertNotEqual(self.cache.fileCache.cacheKeyForKey("{"), self.cache.fileCache.cacheKeyForKey("}")) + XCTAssertEqual(self.cache.fileCache.cacheKeyForKey("hello"), self.cache.fileCache.cacheKeyForKey("hello")) + XCTAssertEqual(self.cache.fileCache.cacheKeyForKey("https://cool.com/neat"), self.cache.fileCache.cacheKeyForKey("https://cool.com/neat")) + XCTAssertEqual(self.cache.fileCache.cacheKeyForKey("-"), self.cache.fileCache.cacheKeyForKey("-")) } /// NOTE: This test is disabled pending https://github.com/mapbox/mapbox-navigation-ios/issues/1468 @@ -124,9 +120,9 @@ class DataCacheTests: XCTestCase { let instructionTurn = "Turn left" let instructionContinue = "Continue on I-80 East for 3 miles" measure { - for _ in 0...1000 { - _ = cache.fileCache.cacheKeyForKey(instructionTurn) - _ = cache.fileCache.cacheKeyForKey(instructionContinue) + for _ in 0 ... 1000 { + _ = self.cache.fileCache.cacheKeyForKey(instructionTurn) + _ = self.cache.fileCache.cacheKeyForKey(instructionContinue) } } } diff --git a/MapboxNavigationTests/Sources/Tests/ImageCacheTests.swift b/MapboxNavigationTests/Sources/Tests/ImageCacheTests.swift index 33ad1d6d4..9c7f7f8dc 100644 --- a/MapboxNavigationTests/Sources/Tests/ImageCacheTests.swift +++ b/MapboxNavigationTests/Sources/Tests/ImageCacheTests.swift @@ -1,15 +1,13 @@ -import XCTest @testable import MapboxNavigation - +import XCTest class ImageCacheTests: XCTestCase { - - let cache: ImageCache = ImageCache() + let cache: ImageCache = .init() let asyncTimeout: TimeInterval = 10.0 private func clearDiskCache() { let semaphore = DispatchSemaphore(value: 0) - cache.clearDisk { + self.cache.clearDisk { semaphore.signal() } let semaphoreResult = semaphore.wait(timeout: XCTestCase.NavigationTests.timeout) @@ -18,17 +16,17 @@ class ImageCacheTests: XCTestCase { override func setUp() { super.setUp() - self.continueAfterFailure = false + continueAfterFailure = false - cache.clearMemory() - clearDiskCache() + self.cache.clearMemory() + self.clearDiskCache() } let imageKey = "imageKey" private func storeImageInMemory() { let semaphore = DispatchSemaphore(value: 0) - cache.store(ShieldImage.i280.image, forKey: imageKey, toDisk: false) { + self.cache.store(ShieldImage.i280.image, forKey: self.imageKey, toDisk: false) { semaphore.signal() } let semaphoreResult = semaphore.wait(timeout: XCTestCase.NavigationTests.timeout) @@ -37,7 +35,7 @@ class ImageCacheTests: XCTestCase { private func storeImageOnDisk() { let semaphore = DispatchSemaphore(value: 0) - cache.store(ShieldImage.i280.image, forKey: imageKey, toDisk: true) { + self.cache.store(ShieldImage.i280.image, forKey: self.imageKey, toDisk: true) { semaphore.signal() } let semaphoreResult = semaphore.wait(timeout: XCTestCase.NavigationTests.timeout) @@ -48,82 +46,82 @@ class ImageCacheTests: XCTestCase { func testUsingURLStringAsCacheKey() { let cacheKeyURLString = "https://zombo.com/lulz/shieldKey.xyz" - let expectation = self.expectation(description: "Storing image in disk cache") - cache.store(ShieldImage.i280.image, forKey: cacheKeyURLString, toDisk: true) { + let expectation = expectation(description: "Storing image in disk cache") + self.cache.store(ShieldImage.i280.image, forKey: cacheKeyURLString, toDisk: true) { expectation.fulfill() } - self.wait(for: [expectation], timeout: asyncTimeout) + wait(for: [expectation], timeout: self.asyncTimeout) - let returnedImage = cache.image(forKey: cacheKeyURLString) + let returnedImage = self.cache.image(forKey: cacheKeyURLString) XCTAssertTrue((returnedImage?.isKind(of: UIImage.self))!) } func testUsingPathStringAsCacheKey() { let cacheKeyURLString = "/path/to/something.xyz" - let expectation = self.expectation(description: "Storing image in disk cache") - cache.store(ShieldImage.i280.image, forKey: cacheKeyURLString, toDisk: true) { + let expectation = expectation(description: "Storing image in disk cache") + self.cache.store(ShieldImage.i280.image, forKey: cacheKeyURLString, toDisk: true) { expectation.fulfill() } - self.wait(for: [expectation], timeout: asyncTimeout) + wait(for: [expectation], timeout: self.asyncTimeout) - let returnedImage = cache.image(forKey: cacheKeyURLString) + let returnedImage = self.cache.image(forKey: cacheKeyURLString) XCTAssertTrue((returnedImage?.isKind(of: UIImage.self))!) } func testStoringImageInMemoryOnly() { - storeImageInMemory() + self.storeImageInMemory() - let returnedImage = cache.image(forKey: imageKey) + let returnedImage = self.cache.image(forKey: self.imageKey) XCTAssertTrue((returnedImage?.isKind(of: UIImage.self))!) } func testStoringImageOnDisk() { - storeImageOnDisk() + self.storeImageOnDisk() - var returnedImage = cache.image(forKey: imageKey) + var returnedImage = self.cache.image(forKey: self.imageKey) XCTAssertTrue((returnedImage?.isKind(of: UIImage.self))!) - cache.clearMemory() + self.cache.clearMemory() - returnedImage = cache.image(forKey: imageKey) + returnedImage = self.cache.image(forKey: self.imageKey) XCTAssertNotNil(returnedImage) XCTAssertTrue((returnedImage?.isKind(of: UIImage.self))!) } func testResettingCache() { - storeImageInMemory() + self.storeImageInMemory() - cache.clearMemory() + self.cache.clearMemory() - XCTAssertNil(cache.image(forKey: imageKey)) + XCTAssertNil(self.cache.image(forKey: self.imageKey)) - storeImageOnDisk() + self.storeImageOnDisk() - cache.clearMemory() - clearDiskCache() + self.cache.clearMemory() + self.clearDiskCache() - XCTAssertNil(cache.image(forKey: imageKey)) + XCTAssertNil(self.cache.image(forKey: self.imageKey)) } func testClearingMemoryCacheOnMemoryWarning() { - storeImageInMemory() + self.storeImageInMemory() NotificationCenter.default.post(name: UIApplication.didReceiveMemoryWarningNotification, object: nil) - XCTAssertNil(cache.image(forKey: imageKey)) + XCTAssertNil(self.cache.image(forKey: self.imageKey)) } func testJPEGSupport() { let imageJPEGData = ShieldImage.i280.image.jpegData(compressionQuality: 9.0)! - let image = UIImage.init(data: imageJPEGData)! + let image = UIImage(data: imageJPEGData)! - let expectation = self.expectation(description: "Storing image in disk cache") - cache.store(image, forKey: "JPEG Test", toDisk: true) { + let expectation = expectation(description: "Storing image in disk cache") + self.cache.store(image, forKey: "JPEG Test", toDisk: true) { expectation.fulfill() } - wait(for: [expectation], timeout: asyncTimeout) + wait(for: [expectation], timeout: self.asyncTimeout) - let retrievedImage = cache.image(forKey: "JPEG Test")! + let retrievedImage = self.cache.image(forKey: "JPEG Test")! XCTAssertTrue(retrievedImage.isKind(of: UIImage.self)) } diff --git a/MapboxNavigationTests/Sources/Tests/ImageDownloaderTests.swift b/MapboxNavigationTests/Sources/Tests/ImageDownloaderTests.swift index d045b229b..319094e73 100644 --- a/MapboxNavigationTests/Sources/Tests/ImageDownloaderTests.swift +++ b/MapboxNavigationTests/Sources/Tests/ImageDownloaderTests.swift @@ -1,9 +1,7 @@ -import XCTest @testable import MapboxNavigation - +import XCTest class ImageDownloaderTests: XCTestCase { - lazy var sessionConfig: URLSessionConfiguration = { let config = URLSessionConfiguration.default config.protocolClasses = [ImageLoadingURLProtocolSpy.self] @@ -16,24 +14,24 @@ class ImageDownloaderTests: XCTestCase { override func setUp() { super.setUp() - self.continueAfterFailure = false + continueAfterFailure = false ImageLoadingURLProtocolSpy.reset() let imageData = ShieldImage.i280.image.pngData()! - ImageLoadingURLProtocolSpy.registerData(imageData, forURL: imageURL) + ImageLoadingURLProtocolSpy.registerData(imageData, forURL: self.imageURL) - downloader = ImageDownloader(sessionConfiguration: sessionConfig) + self.downloader = ImageDownloader(sessionConfiguration: self.sessionConfig) } override func tearDown() { - downloader = nil + self.downloader = nil super.tearDown() } func testDownloadingAnImage() { - guard let downloader = downloader else { + guard let downloader else { XCTFail() return } @@ -42,7 +40,7 @@ class ImageDownloaderTests: XCTestCase { var errorReturned: Error? let semaphore = DispatchSemaphore(value: 0) - downloader.downloadImage(with: imageURL) { (image, data, error) in + downloader.downloadImage(with: self.imageURL) { image, data, error in imageReturned = image dataReturned = data errorReturned = error @@ -52,7 +50,7 @@ class ImageDownloaderTests: XCTestCase { XCTAssert(semaphoreResult == .success, "Semaphore timed out") // The ImageDownloader is meant to be used with an external caching mechanism - let request = ImageLoadingURLProtocolSpy.pastRequestForURL(imageURL)! + let request = ImageLoadingURLProtocolSpy.pastRequestForURL(self.imageURL)! XCTAssertEqual(request.cachePolicy, .reloadIgnoringCacheData) XCTAssertNotNil(imageReturned) @@ -62,7 +60,7 @@ class ImageDownloaderTests: XCTestCase { } func testDownloadingImageWhileAlreadyInProgressAddsCallbacksWithoutAddingAnotherRequest() { - guard let downloader = downloader else { + guard let downloader else { XCTFail() return } @@ -73,26 +71,26 @@ class ImageDownloaderTests: XCTestCase { // URL loading is delayed in order to simulate conditions under which multiple requests for the same asset would be made ImageLoadingURLProtocolSpy.delayImageLoading() - downloader.downloadImage(with: imageURL) { (image, data, error) in + downloader.downloadImage(with: self.imageURL) { _, _, _ in firstCallbackCalled = true } - operation = downloader.activeOperation(with: imageURL)! + operation = downloader.activeOperation(with: self.imageURL)! - downloader.downloadImage(with: imageURL) { (image, data, error) in + downloader.downloadImage(with: self.imageURL) { _, _, _ in secondCallbackCalled = true } ImageLoadingURLProtocolSpy.resumeImageLoading() - XCTAssertTrue(operation === downloader.activeOperation(with: imageURL)!, - "Expected \(String(describing: operation)) to be identical to \(String(describing: downloader.activeOperation(with: imageURL)))") + XCTAssertTrue(operation === downloader.activeOperation(with: self.imageURL)!, + "Expected \(String(describing: operation)) to be identical to \(String(describing: downloader.activeOperation(with: self.imageURL)))") var spinCount = 0 - runUntil({ + runUntil { spinCount += 1 return operation.isFinished - }) + } print("Succeeded after evaluating condition \(spinCount) times.") @@ -101,22 +99,22 @@ class ImageDownloaderTests: XCTestCase { } func testDownloadingImageAgainAfterFirstDownloadCompletes() { - guard let downloader = downloader else { + guard let downloader else { XCTFail() return } var callbackCalled = false var spinCount = 0 - downloader.downloadImage(with: imageURL) { (image, data, error) in + downloader.downloadImage(with: self.imageURL) { _, _, _ in callbackCalled = true } - var operation = downloader.activeOperation(with: imageURL)! + var operation = downloader.activeOperation(with: self.imageURL)! - runUntil({ + runUntil { spinCount += 1 return operation.isFinished - }) + } print("Succeeded after evaluating first condition \(spinCount) times.") XCTAssertTrue(callbackCalled) @@ -124,15 +122,15 @@ class ImageDownloaderTests: XCTestCase { callbackCalled = false spinCount = 0 - downloader.downloadImage(with: imageURL) { (image, data, error) in + downloader.downloadImage(with: self.imageURL) { _, _, _ in callbackCalled = true } - operation = downloader.activeOperation(with: imageURL)! + operation = downloader.activeOperation(with: self.imageURL)! - runUntil({ + runUntil { spinCount += 1 return operation.isFinished - }) + } print("Succeeded after evaluating second condition \(spinCount) times.") XCTAssertTrue(callbackCalled) diff --git a/MapboxNavigationTests/Sources/Tests/ImageRepositoryTests.swift b/MapboxNavigationTests/Sources/Tests/ImageRepositoryTests.swift index 0f357cac1..75e4f1cd5 100644 --- a/MapboxNavigationTests/Sources/Tests/ImageRepositoryTests.swift +++ b/MapboxNavigationTests/Sources/Tests/ImageRepositoryTests.swift @@ -1,8 +1,7 @@ -import XCTest @testable import MapboxNavigation +import XCTest class ImageRepositoryTests: XCTestCase { - lazy var repository: ImageRepository = { let repo = ImageRepository.shared let config = URLSessionConfiguration.default @@ -14,12 +13,12 @@ class ImageRepositoryTests: XCTestCase { override func setUp() { super.setUp() - self.continueAfterFailure = false + continueAfterFailure = false ImageLoadingURLProtocolSpy.reset() let semaphore = DispatchSemaphore(value: 0) - repository.resetImageCache { + self.repository.resetImageCache { semaphore.signal() } let semaphoreResult = semaphore.wait(timeout: XCTestCase.NavigationTests.timeout) @@ -27,7 +26,6 @@ class ImageRepositoryTests: XCTestCase { } override func tearDown() { - super.tearDown() } @@ -36,12 +34,12 @@ class ImageRepositoryTests: XCTestCase { let fakeURL = URL(string: "http://an.image.url/\(imageName)")! ImageLoadingURLProtocolSpy.registerData(ShieldImage.i280.image.pngData()!, forURL: fakeURL) - XCTAssertNil(repository.cachedImageForKey(imageName)) + XCTAssertNil(self.repository.cachedImageForKey(imageName)) var imageReturned: UIImage? = nil let semaphore = DispatchSemaphore(value: 0) - repository.imageWithURL(fakeURL, cacheKey: imageName) { (image) in + self.repository.imageWithURL(fakeURL, cacheKey: imageName) { image in imageReturned = image semaphore.signal() } @@ -57,12 +55,12 @@ class ImageRepositoryTests: XCTestCase { let imageName = "1.png" let fakeURL = URL(string: "http://an.image.url/\(imageName)")! - repository.storeImage(ShieldImage.i280.image, forKey: imageName, toDisk: false) + self.repository.storeImage(ShieldImage.i280.image, forKey: imageName, toDisk: false) var imageReturned: UIImage? = nil let semaphore = DispatchSemaphore(value: 0) - repository.imageWithURL(fakeURL, cacheKey: imageName) { (image) in + self.repository.imageWithURL(fakeURL, cacheKey: imageName) { image in imageReturned = image semaphore.signal() } diff --git a/MapboxNavigationTests/Sources/Tests/InstructionPresenterTests.swift b/MapboxNavigationTests/Sources/Tests/InstructionPresenterTests.swift index 119ab421f..203606034 100644 --- a/MapboxNavigationTests/Sources/Tests/InstructionPresenterTests.swift +++ b/MapboxNavigationTests/Sources/Tests/InstructionPresenterTests.swift @@ -1,21 +1,19 @@ import Foundation -import XCTest import MapboxCoreNavigation -@testable import MapboxNavigation import MapboxDirections +@testable import MapboxNavigation +import XCTest class InstructionPresenterTests: XCTestCase { - func testExitInstructionProvidesExit() { - let exitAttribute = VisualInstructionComponent(type: .exit, text: "Exit", imageURL: nil, abbreviation: nil, abbreviationPriority: 0) let exitCodeAttribute = VisualInstructionComponent(type: .exitCode, text: "123A", imageURL: nil, abbreviation: nil, abbreviationPriority: 0) let exitInstruction = VisualInstruction(text: nil, maneuverType: .takeOffRamp, maneuverDirection: .right, components: [exitAttribute, exitCodeAttribute]) - let label = InstructionLabel(frame: CGRect(origin: .zero, size:CGSize(width: 50, height: 50))) + let label = InstructionLabel(frame: CGRect(origin: .zero, size: CGSize(width: 50, height: 50))) - //FIXME: not ideal -- UIAutoLayout? - label.availableBounds = { return CGRect(origin: .zero, size: CGSize.iPhoneX) } + // FIXME: not ideal -- UIAutoLayout? + label.availableBounds = { CGRect(origin: .zero, size: CGSize.iPhoneX) } let presenter = InstructionPresenter(exitInstruction, dataSource: label, downloadCompletion: nil) let attributed = presenter.attributedText() @@ -29,13 +27,13 @@ class InstructionPresenterTests: XCTestCase { func x_testAbbreviationPerformance() { let route = Fixture.routeWithBannerInstructions() - let steps = route.legs.flatMap { $0.steps } + let steps = route.legs.flatMap(\.steps) let instructions = steps.compactMap { $0.instructionsDisplayedAlongStep?.first?.primaryInstruction } let label = InstructionLabel(frame: CGRect(origin: .zero, size: CGSize.iPhone5)) - label.availableBounds = { return CGRect(origin: .zero, size: CGSize.iPhone5) } + label.availableBounds = { CGRect(origin: .zero, size: CGSize.iPhone5) } - self.measure { + measure { for instruction in instructions { let presenter = InstructionPresenter(instruction, dataSource: label, downloadCompletion: nil) label.attributedText = presenter.attributedText() diff --git a/MapboxNavigationTests/Sources/Tests/InstructionsBannerViewIntegrationTests.swift b/MapboxNavigationTests/Sources/Tests/InstructionsBannerViewIntegrationTests.swift index 53f320540..c631b7168 100644 --- a/MapboxNavigationTests/Sources/Tests/InstructionsBannerViewIntegrationTests.swift +++ b/MapboxNavigationTests/Sources/Tests/InstructionsBannerViewIntegrationTests.swift @@ -1,8 +1,7 @@ -import XCTest +@testable import MapboxCoreNavigation import MapboxDirections @testable import MapboxNavigation -@testable import MapboxCoreNavigation - +import XCTest func instructionsView(size: CGSize = .iPhone6Plus) -> InstructionsBannerView { let bannerHeight: CGFloat = 96 @@ -13,18 +12,16 @@ func makeVisualInstruction(_ maneuverType: ManeuverType = .arrive, _ maneuverDirection: ManeuverDirection = .left, primaryInstruction: [VisualInstructionComponent], secondaryInstruction: [VisualInstructionComponent]?) -> VisualInstructionBanner { - let primary = VisualInstruction(text: "Instruction", maneuverType: maneuverType, maneuverDirection: maneuverDirection, components: primaryInstruction) var secondary: VisualInstruction? = nil - if let secondaryInstruction = secondaryInstruction { + if let secondaryInstruction { secondary = VisualInstruction(text: "Instruction", maneuverType: maneuverType, maneuverDirection: maneuverDirection, components: secondaryInstruction) } - return VisualInstructionBanner(distanceAlongStep: 482.803, primaryInstruction: primary, secondaryInstruction: secondary, tertiaryInstruction: nil, drivingSide: .right) + return VisualInstructionBanner(distanceAlongStep: 482.803, primaryInstruction: primary, secondaryInstruction: secondary, tertiaryInstruction: nil, drivingSide: .right) } class InstructionsBannerViewIntegrationTests: XCTestCase { - private lazy var reverseDelegate = TextReversingDelegate() private lazy var silentDelegate = DefaultBehaviorDelegate() @@ -35,7 +32,7 @@ class InstructionsBannerViewIntegrationTests: XCTestCase { }() lazy var instructions: [VisualInstructionComponent] = { - let components = [ + let components = [ VisualInstructionComponent(type: .image, text: "US 101", imageURL: ShieldImage.us101.url, abbreviation: nil, abbreviationPriority: 0), VisualInstructionComponent(type: .delimiter, text: "/", imageURL: nil, abbreviation: nil, abbreviationPriority: 0), VisualInstructionComponent(type: .image, text: "I 280", imageURL: ShieldImage.i280.url, abbreviation: nil, abbreviationPriority: 0) @@ -52,7 +49,7 @@ class InstructionsBannerViewIntegrationTests: XCTestCase { private func resetImageCache() { let semaphore = DispatchSemaphore(value: 0) - imageRepository.resetImageCache { + self.imageRepository.resetImageCache { semaphore.signal() } let semaphoreResult = semaphore.wait(timeout: XCTestCase.NavigationTests.timeout) @@ -63,64 +60,61 @@ class InstructionsBannerViewIntegrationTests: XCTestCase { super.setUp() continueAfterFailure = false - imageRepository.disableDiskCache() - resetImageCache() + self.imageRepository.disableDiskCache() + self.resetImageCache() ImageDownloadOperationSpy.reset() - imageRepository.imageDownloader.setOperationType(ImageDownloadOperationSpy.self) + self.imageRepository.imageDownloader.setOperationType(ImageDownloadOperationSpy.self) } override func tearDown() { - imageRepository.imageDownloader.setOperationType(nil) + self.imageRepository.imageDownloader.setOperationType(nil) super.tearDown() } func testCustomVisualInstructionDelegate() { let view = instructionsView() - view.instructionDelegate = reverseDelegate + view.instructionDelegate = self.reverseDelegate - view.update(for: typicalInstruction) + view.update(for: self.typicalInstruction) XCTAssert(view.primaryLabel.attributedText?.string == "teertS niaM") - } func testCustomDelegateReturningNilTriggersDefaultBehavior() { let view = instructionsView() - view.instructionDelegate = silentDelegate + view.instructionDelegate = self.silentDelegate - view.update(for: typicalInstruction) + view.update(for: self.typicalInstruction) XCTAssert(view.primaryLabel.attributedText?.string == "Main Street") - } - func testDelimiterIsShownWhenShieldsNotLoaded() { let view = instructionsView() - view.update(for: makeVisualInstruction(primaryInstruction: instructions, secondaryInstruction: nil)) + view.update(for: makeVisualInstruction(primaryInstruction: self.instructions, secondaryInstruction: nil)) - XCTAssertNotNil(view.primaryLabel.text!.index(of: "/")) + XCTAssertNotNil(view.primaryLabel.text!.firstIndex(of: "/")) } func testDelimiterIsHiddenWhenAllShieldsAreAlreadyLoaded() { - //prime the cache to simulate images having already been loaded + // prime the cache to simulate images having already been loaded let instruction1 = VisualInstructionComponent(type: .image, text: "I 280", imageURL: ShieldImage.i280.url, abbreviation: nil, abbreviationPriority: 0) let instruction2 = VisualInstructionComponent(type: .image, text: "US 101", imageURL: ShieldImage.us101.url, abbreviation: nil, abbreviationPriority: 0) - imageRepository.storeImage(ShieldImage.i280.image, forKey: instruction1.cacheKey!, toDisk: false) - imageRepository.storeImage(ShieldImage.us101.image, forKey: instruction2.cacheKey!, toDisk: false) + self.imageRepository.storeImage(ShieldImage.i280.image, forKey: instruction1.cacheKey!, toDisk: false) + self.imageRepository.storeImage(ShieldImage.us101.image, forKey: instruction2.cacheKey!, toDisk: false) let view = instructionsView() - view.update(for: makeVisualInstruction(primaryInstruction: instructions, secondaryInstruction: nil)) + view.update(for: makeVisualInstruction(primaryInstruction: self.instructions, secondaryInstruction: nil)) - //the delimiter should NOT be present since both shields are already in the cache - XCTAssertNil(view.primaryLabel.text!.index(of: "/")) + // the delimiter should NOT be present since both shields are already in the cache + XCTAssertNil(view.primaryLabel.text!.firstIndex(of: "/")) - //explicitly reset the cache - resetImageCache() + // explicitly reset the cache + self.resetImageCache() } func testDelimiterDisappearsOnlyWhenAllShieldsHaveLoaded() { @@ -135,49 +129,48 @@ class InstructionsBannerViewIntegrationTests: XCTestCase { XCTFail("ImageDownloadCompletion should not have been called on the secondary label.") } - //set visual instructions on the view, which triggers the instruction image fetch - view.update(for: makeVisualInstruction(primaryInstruction: instructions, secondaryInstruction: nil)) + // set visual instructions on the view, which triggers the instruction image fetch + view.update(for: makeVisualInstruction(primaryInstruction: self.instructions, secondaryInstruction: nil)) - //Slash should be present until an adjacent shield is downloaded - XCTAssertNotNil(view.primaryLabel.text!.index(of: "/")) + // Slash should be present until an adjacent shield is downloaded + XCTAssertNotNil(view.primaryLabel.text!.firstIndex(of: "/")) - //simulate the downloads - let firstDestinationComponent: VisualInstructionComponent = instructions[0] - simulateDownloadingShieldForComponent(firstDestinationComponent) + // simulate the downloads + let firstDestinationComponent: VisualInstructionComponent = self.instructions[0] + self.simulateDownloadingShieldForComponent(firstDestinationComponent) - //ensure that first callback fires + // ensure that first callback fires wait(for: [firstExpectation], timeout: 1) - //change the callback to track the second shield component + // change the callback to track the second shield component view.primaryLabel.imageDownloadCompletion = secondExpectation.fulfill - let secondDestinationComponent = instructions[2] - simulateDownloadingShieldForComponent(secondDestinationComponent) + let secondDestinationComponent = self.instructions[2] + self.simulateDownloadingShieldForComponent(secondDestinationComponent) - //ensure that second callback fires + // ensure that second callback fires wait(for: [secondExpectation], timeout: 1) - //Slash should no longer be present - XCTAssertNil(view.primaryLabel.text!.index(of: "/"), "Expected instruction text not to contain a slash: \(view.primaryLabel.text!)") + // Slash should no longer be present + XCTAssertNil(view.primaryLabel.text!.firstIndex(of: "/"), "Expected instruction text not to contain a slash: \(view.primaryLabel.text!)") } func testGenericRouteShieldInstructionsArePresentedProperly() { let view = instructionsView() let instruction = makeVisualInstruction(primaryInstruction: genericInstructions, secondaryInstruction: nil) - //set the instruction, triggering the generic shield generation + // set the instruction, triggering the generic shield generation view.update(for: instruction) guard let attributed = view.primaryLabel.attributedText else { return XCTFail("No attributed string") } let stringRange = NSRange(location: 0, length: attributed.length) let foundAttachment = XCTestExpectation(description: "Attachment found") - attributed.enumerateAttribute(.attachment, in: stringRange, options: []) { (value, range, stop) in + attributed.enumerateAttribute(.attachment, in: stringRange, options: []) { value, range, _ in guard let attachment = value else { return } foundAttachment.fulfill() XCTAssert(range == NSRange(location: 0, length: 1), "Unexpected Range:" + String(describing: range)) XCTAssert(type(of: attachment) == GenericShieldAttachment.self, "Unexpected Attachment type:" + String(describing: attachment)) } wait(for: [foundAttachment], timeout: 0) - } func testRouteShieldsAreGenericUntilTheyLoad() { @@ -201,70 +194,70 @@ class InstructionsBannerViewIntegrationTests: XCTestCase { XCTFail("ImageDownloadCompletion should not have been called on the secondary label.") } - //set visual instructions on the view, which triggers the instruction image fetch - view.update(for: makeVisualInstruction(primaryInstruction: instructions, secondaryInstruction: nil)) + // set visual instructions on the view, which triggers the instruction image fetch + view.update(for: makeVisualInstruction(primaryInstruction: self.instructions, secondaryInstruction: nil)) let firstAttachmentRange = NSRange(location: 0, length: 1) let secondAttachmentRange = NSRange(location: 4, length: 1) - //instructions should contain generic shields + // instructions should contain generic shields let firstStringRange = NSRange(location: 0, length: view.primaryLabel.attributedText!.length) view.primaryLabel.attributedText!.enumerateAttribute(.attachment, in: firstStringRange, options: [], - using: { (value, range, stop) in - guard let attachment = value else { return } - firstRunHasAttachments.fulfill() + using: { value, range, _ in + guard let attachment = value else { return } + firstRunHasAttachments.fulfill() - if attachment is GenericShieldAttachment { - if range == firstAttachmentRange { - return firstGeneric.fulfill() - } else if range == secondAttachmentRange { - return secondGeneric.fulfill() - } - } - XCTFail("First run: Unexpected attachment encountered at:" + String(describing: range) + " value: " + String(describing: value)) - }) - - //simulate the downloads - let firstDestinationComponent: VisualInstructionComponent = instructions[0] - simulateDownloadingShieldForComponent(firstDestinationComponent) - - //ensure that first callback fires + if attachment is GenericShieldAttachment { + if range == firstAttachmentRange { + return firstGeneric.fulfill() + } else if range == secondAttachmentRange { + return secondGeneric.fulfill() + } + } + XCTFail("First run: Unexpected attachment encountered at:" + String(describing: range) + " value: " + String(describing: value)) + }) + + // simulate the downloads + let firstDestinationComponent: VisualInstructionComponent = self.instructions[0] + self.simulateDownloadingShieldForComponent(firstDestinationComponent) + + // ensure that first callback fires wait(for: [firstExpectation], timeout: 1) - //This range has to be recomputed because the string changes on download + // This range has to be recomputed because the string changes on download let secondStringRange = NSRange(location: 0, length: view.primaryLabel.attributedText!.length) - //check that the first component is now loaded + // check that the first component is now loaded view.primaryLabel.attributedText!.enumerateAttribute(.attachment, in: secondStringRange, - options: [], using: { (value, range, stop) in - guard let attachment = value else { return } - secondRunHasAttachments.fulfill() + options: [], using: { value, range, _ in + guard let attachment = value else { return } + secondRunHasAttachments.fulfill() - if attachment is GenericShieldAttachment, range == secondAttachmentRange { - return secondStillGeneric.fulfill() - } else if attachment is ShieldAttachment, range == firstAttachmentRange { - return firstNowLoaded.fulfill() - } - XCTFail("Second Run: Unexpected attachment encountered at:" + String(describing: range) + " value: " + String(describing: value)) - }) - - //change the callback to track the second shield component + if attachment is GenericShieldAttachment, range == secondAttachmentRange { + return secondStillGeneric.fulfill() + } else if attachment is ShieldAttachment, range == firstAttachmentRange { + return firstNowLoaded.fulfill() + } + XCTFail("Second Run: Unexpected attachment encountered at:" + String(describing: range) + " value: " + String(describing: value)) + }) + + // change the callback to track the second shield component view.primaryLabel.imageDownloadCompletion = secondExpectation.fulfill - let secondDestinationComponent = instructions[2] - simulateDownloadingShieldForComponent(secondDestinationComponent) + let secondDestinationComponent = self.instructions[2] + self.simulateDownloadingShieldForComponent(secondDestinationComponent) - //ensure that second callback fires + // ensure that second callback fires wait(for: [secondExpectation], timeout: 1) - //we recompute this again because the string once again changes + // we recompute this again because the string once again changes let thirdStringRange = NSRange(location: 0, length: view.primaryLabel.attributedText!.length) let noDelimiterSecondAttachmentRange = NSRange(location: 2, length: 1) - //check that all attachments are now loaded - view.primaryLabel.attributedText!.enumerateAttribute(.attachment, in: thirdStringRange, options: [], using: { (value, range, stop) in + // check that all attachments are now loaded + view.primaryLabel.attributedText!.enumerateAttribute(.attachment, in: thirdStringRange, options: [], using: { value, range, _ in guard let attachment = value else { return } thirdRunHasAttachments.fulfill() @@ -276,7 +269,7 @@ class InstructionsBannerViewIntegrationTests: XCTestCase { XCTFail("Third run: Unexpected attachment encountered at:" + String(describing: range) + " value: " + String(describing: value)) }) - //make sure everything happened as expected + // make sure everything happened as expected let expectations = [firstRunHasAttachments, firstGeneric, secondGeneric, secondRunHasAttachments, firstNowLoaded, secondStillGeneric, thirdRunHasAttachments, firstStillLoaded, secondNowLoaded] @@ -284,27 +277,27 @@ class InstructionsBannerViewIntegrationTests: XCTestCase { } func testExitBannerIntegration() { - let exitAttribute = VisualInstructionComponent(type: .exit, text: "Exit", imageURL: nil, abbreviation: nil, abbreviationPriority: 0) + let exitAttribute = VisualInstructionComponent(type: .exit, text: "Exit", imageURL: nil, abbreviation: nil, abbreviationPriority: 0) let exitCodeAttribute = VisualInstructionComponent(type: .exitCode, text: "123A", imageURL: nil, abbreviation: nil, abbreviationPriority: 0) let mainStreetString = VisualInstructionComponent(type: .text, text: "Main Street", imageURL: nil, abbreviation: "Main St", abbreviationPriority: 0) let exitInstruction = VisualInstruction(text: nil, maneuverType: .takeOffRamp, maneuverDirection: .right, components: [exitAttribute, exitCodeAttribute, mainStreetString]) - let label = InstructionLabel(frame: CGRect(origin: .zero, size:CGSize(width: 375, height: 100))) + let label = InstructionLabel(frame: CGRect(origin: .zero, size: CGSize(width: 375, height: 100))) - label.availableBounds = { return label.frame } + label.availableBounds = { label.frame } let presenter = InstructionPresenter(exitInstruction, dataSource: label, downloadCompletion: nil) let attributed = presenter.attributedText() let key = [exitCodeAttribute.cacheKey!, ExitView.criticalHash(side: .right, dataSource: label)].joined(separator: "-") - XCTAssertNotNil(imageRepository.cachedImageForKey(key), "Expected cached image") + XCTAssertNotNil(self.imageRepository.cachedImageForKey(key), "Expected cached image") let spaceRange = NSMakeRange(1, 1) let space = attributed.attributedSubstring(from: spaceRange) - //Do we have spacing between the attachment and the road name? + // Do we have spacing between the attachment and the road name? XCTAssert(space.string == " ", "Should be a space between exit attachment and name") - //Road Name should be present and not abbreviated + // Road Name should be present and not abbreviated XCTAssert(attributed.length == 13, "Road name should not be abbreviated") let roadNameRange = NSMakeRange(2, 11) @@ -313,12 +306,11 @@ class InstructionsBannerViewIntegrationTests: XCTestCase { } private func simulateDownloadingShieldForComponent(_ component: VisualInstructionComponent) { - let operation: ImageDownloadOperationSpy = ImageDownloadOperationSpy.operationForURL(component.imageURL!)! + let operation = ImageDownloadOperationSpy.operationForURL(component.imageURL!)! operation.fireAllCompletions(ShieldImage.i280.image, data: ShieldImage.i280.image.pngData(), error: nil) - XCTAssertNotNil(imageRepository.cachedImageForKey(component.cacheKey!)) + XCTAssertNotNil(self.imageRepository.cachedImageForKey(component.cacheKey!)) } - } private class TextReversingDelegate: VisualInstructionDelegate { @@ -333,6 +325,6 @@ private class TextReversingDelegate: VisualInstructionDelegate { private class DefaultBehaviorDelegate: VisualInstructionDelegate { func label(_ label: InstructionLabel, willPresent instruction: VisualInstruction, as presented: NSAttributedString) -> NSAttributedString? { - return nil + nil } } diff --git a/MapboxNavigationTests/Sources/Tests/NavigationMapViewTests.swift b/MapboxNavigationTests/Sources/Tests/NavigationMapViewTests.swift index bebe70597..aee3d6b76 100644 --- a/MapboxNavigationTests/Sources/Tests/NavigationMapViewTests.swift +++ b/MapboxNavigationTests/Sources/Tests/NavigationMapViewTests.swift @@ -1,29 +1,28 @@ -import XCTest import CoreLocation -@testable import MapboxNavigation import MapboxDirections +@testable import MapboxNavigation +import XCTest class NavigationMapViewTests: XCTestCase { - let coordinates: [CLLocationCoordinate2D] = [ CLLocationCoordinate2D(latitude: 0, longitude: 0), CLLocationCoordinate2D(latitude: 1, longitude: 1), CLLocationCoordinate2D(latitude: 2, longitude: 2), CLLocationCoordinate2D(latitude: 3, longitude: 3), CLLocationCoordinate2D(latitude: 4, longitude: 4), - CLLocationCoordinate2D(latitude: 5, longitude: 5), - ] + CLLocationCoordinate2D(latitude: 5, longitude: 5) + ] func testNavigationMapViewCombineWithSimilarCongestions() { let navigationMapView = NavigationMapView(frame: CGRect(origin: .zero, size: .iPhone6Plus)) - let congestionSegments = navigationMapView.combine(coordinates, with: [ + let congestionSegments = navigationMapView.combine(self.coordinates, with: [ .low, .low, .low, .low, .low - ]) + ]) XCTAssertEqual(congestionSegments.count, 1) XCTAssertEqual(congestionSegments[0].0.count, 10) @@ -33,13 +32,13 @@ class NavigationMapViewTests: XCTestCase { func testNavigationMapViewCombineWithDissimilarCongestions() { let navigationMapView = NavigationMapView(frame: CGRect(origin: .zero, size: .iPhone6Plus)) - let congestionSegmentsSevere = navigationMapView.combine(coordinates, with: [ + let congestionSegmentsSevere = navigationMapView.combine(self.coordinates, with: [ .low, .low, .severe, .low, .low - ]) + ]) // The severe breaks the trend of .low. // Any time the current congestion level is different than the previous segment, we have to create a new congestion segment. diff --git a/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift b/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift index f11d97ea3..5253487c7 100644 --- a/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift +++ b/MapboxNavigationTests/Sources/Tests/NavigationViewControllerTests.swift @@ -1,22 +1,20 @@ -import XCTest -import MapLibre -import MapboxDirections import MapboxCoreNavigation -import Turf +import MapboxDirections @testable import MapboxNavigation +import MapLibre +import Turf +import XCTest let response = Fixture.JSONFromFileNamed(name: "route-with-instructions", bundle: .module) let otherResponse = Fixture.JSONFromFileNamed(name: "route-for-lane-testing", bundle: .module) class NavigationViewControllerTests: XCTestCase { - let fakeDirections = Directions(accessToken: "abc", host: "ab") var customRoadName = [CLLocationCoordinate2D: String?]() var updatedStyleNumberOfTimes = 0 lazy var dependencies: (navigationViewController: NavigationViewController, startLocation: CLLocation, poi: [CLLocation], endLocation: CLLocation, voice: RouteVoiceController) = { - let voice = FakeVoiceController() let nav = NavigationViewController(for: initialRoute, directions: Directions(accessToken: "garbage", host: nil), @@ -25,19 +23,19 @@ class NavigationViewControllerTests: XCTestCase { nav.delegate = self let routeController = nav.routeController! - let firstCoord = routeController.routeProgress.currentLegProgress.nearbyCoordinates.first! - let firstLocation = location(at: firstCoord) + let firstCoord = routeController.routeProgress.currentLegProgress.nearbyCoordinates.first! + let firstLocation = location(at: firstCoord) var poi = [CLLocation]() let taylorStreetIntersection = routeController.routeProgress.route.legs.first!.steps.first!.intersections!.first! - let turkStreetIntersection = routeController.routeProgress.route.legs.first!.steps[3].intersections!.first! + let turkStreetIntersection = routeController.routeProgress.route.legs.first!.steps[3].intersections!.first! let fultonStreetIntersection = routeController.routeProgress.route.legs.first!.steps[5].intersections!.first! poi.append(location(at: taylorStreetIntersection.location)) poi.append(location(at: turkStreetIntersection.location)) poi.append(location(at: fultonStreetIntersection.location)) - let lastCoord = routeController.routeProgress.currentLegProgress.remainingSteps.last!.coordinates!.first! + let lastCoord = routeController.routeProgress.currentLegProgress.remainingSteps.last!.coordinates!.first! let lastLocation = location(at: lastCoord) return (navigationViewController: nav, startLocation: firstLocation, poi: poi, endLocation: lastLocation, voice: voice) @@ -47,7 +45,7 @@ class NavigationViewControllerTests: XCTestCase { let jsonRoute = (response["routes"] as! [AnyObject]).first as! [String: Any] let waypoint1 = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.795042, longitude: -122.413165)) let waypoint2 = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.7727, longitude: -122.433378)) - let route = Route(json: jsonRoute, waypoints: [waypoint1, waypoint2], options: NavigationRouteOptions(waypoints: [waypoint1, waypoint2])) + let route = Route(json: jsonRoute, waypoints: [waypoint1, waypoint2], options: NavigationRouteOptions(waypoints: [waypoint1, waypoint2])) route.accessToken = "foo" @@ -58,7 +56,7 @@ class NavigationViewControllerTests: XCTestCase { let jsonRoute = (otherResponse["routes"] as! [AnyObject]).first as! [String: Any] let waypoint1 = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.901166, longitude: -77.036548)) let waypoint2 = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.900206, longitude: -77.033792)) - let route = Route(json: jsonRoute, waypoints: [waypoint1, waypoint2], options: NavigationRouteOptions(waypoints: [waypoint1, waypoint2])) + let route = Route(json: jsonRoute, waypoints: [waypoint1, waypoint2], options: NavigationRouteOptions(waypoints: [waypoint1, waypoint2])) route.accessToken = "bar" @@ -67,20 +65,19 @@ class NavigationViewControllerTests: XCTestCase { override func setUp() { super.setUp() - customRoadName.removeAll() + self.customRoadName.removeAll() } // Brief: navigationViewController(_:roadNameAt:) delegate method is implemented, // with a road name provided and wayNameView label is visible. func testNavigationViewControllerDelegateRoadNameAtLocationImplemented() { - - let navigationViewController = dependencies.navigationViewController + let navigationViewController = self.dependencies.navigationViewController let routeController = navigationViewController.routeController! // Identify a location to set the custom road name. - let taylorStreetLocation = dependencies.poi.first! + let taylorStreetLocation = self.dependencies.poi.first! let roadName = "Taylor Swift Street" - customRoadName[taylorStreetLocation.coordinate] = roadName + self.customRoadName[taylorStreetLocation.coordinate] = roadName routeController.locationManager(routeController.locationManager, didUpdateLocations: [taylorStreetLocation]) @@ -95,14 +92,14 @@ class NavigationViewControllerTests: XCTestCase { let routeController = navigationViewController.routeController! navigationViewController.styleManager.delegate = self - let someLocation = dependencies.poi.first! + let someLocation = self.dependencies.poi.first! routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) - XCTAssertEqual(updatedStyleNumberOfTimes, 0, "The style should not be updated.") - updatedStyleNumberOfTimes = 0 + XCTAssertEqual(self.updatedStyleNumberOfTimes, 0, "The style should not be updated.") + self.updatedStyleNumberOfTimes = 0 } // If tunnel flags are enabled and we need to switch styles, we should not force refresh the map style because we have only 1 style. @@ -111,14 +108,14 @@ class NavigationViewControllerTests: XCTestCase { let routeController = navigationViewController.routeController! navigationViewController.styleManager.delegate = self - let someLocation = dependencies.poi.first! + let someLocation = self.dependencies.poi.first! routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) - XCTAssertEqual(updatedStyleNumberOfTimes, 0, "The style should not be updated.") - updatedStyleNumberOfTimes = 0 + XCTAssertEqual(self.updatedStyleNumberOfTimes, 0, "The style should not be updated.") + self.updatedStyleNumberOfTimes = 0 } func testNavigationShouldNotCallStyleManagerDidRefreshAppearanceMoreThanOnceWithTwoStyles() { @@ -126,27 +123,26 @@ class NavigationViewControllerTests: XCTestCase { let routeController = navigationViewController.routeController! navigationViewController.styleManager.delegate = self - let someLocation = dependencies.poi.first! + let someLocation = self.dependencies.poi.first! routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) routeController.locationManager(routeController.locationManager, didUpdateLocations: [someLocation]) - XCTAssertEqual(updatedStyleNumberOfTimes, 0, "The style should not be updated.") - updatedStyleNumberOfTimes = 0 + XCTAssertEqual(self.updatedStyleNumberOfTimes, 0, "The style should not be updated.") + self.updatedStyleNumberOfTimes = 0 } // Brief: navigationViewController(_:roadNameAt:) delegate method is implemented, // with a blank road name (empty string) provided and wayNameView label is hidden. func testNavigationViewControllerDelegateRoadNameAtLocationEmptyString() { - - let navigationViewController = dependencies.navigationViewController + let navigationViewController = self.dependencies.navigationViewController let routeController = navigationViewController.routeController! // Identify a location to set the custom road name. - let turkStreetLocation = dependencies.poi[1] + let turkStreetLocation = self.dependencies.poi[1] let roadName = "" - customRoadName[turkStreetLocation.coordinate] = roadName + self.customRoadName[turkStreetLocation.coordinate] = roadName routeController.locationManager(routeController.locationManager, didUpdateLocations: [turkStreetLocation]) @@ -157,8 +153,7 @@ class NavigationViewControllerTests: XCTestCase { } func testNavigationViewControllerDelegateRoadNameAtLocationUmimplemented() { - - let navigationViewController = dependencies.navigationViewController + let navigationViewController = self.dependencies.navigationViewController // We break the communication between CLLocation and MBRouteController // Intent: Prevent the routecontroller from being fed real location updates @@ -167,9 +162,9 @@ class NavigationViewControllerTests: XCTestCase { let routeController = navigationViewController.routeController! // Identify a location without a custom road name. - let fultonStreetLocation = dependencies.poi[2] + let fultonStreetLocation = self.dependencies.poi[2] - navigationViewController.mapViewController!.labelRoadNameCompletionHandler = { (defaultRaodNameAssigned) in + navigationViewController.mapViewController!.labelRoadNameCompletionHandler = { defaultRaodNameAssigned in XCTAssertTrue(defaultRaodNameAssigned, "label road name was not successfully set") } @@ -180,30 +175,29 @@ class NavigationViewControllerTests: XCTestCase { let styleLoaded = XCTestExpectation(description: "Style Loaded") let navigationViewController = NavigationViewControllerTestable(for: initialRoute, styles: [TestableDayStyle()], styleLoaded: styleLoaded) - //wait for the style to load -- routes won't show without it. + // wait for the style to load -- routes won't show without it. wait(for: [styleLoaded], timeout: 5) - navigationViewController.route = initialRoute + navigationViewController.route = self.initialRoute - runUntil({ - return !(navigationViewController.mapView!.annotations?.isEmpty ?? true) - }) + runUntil { + !(navigationViewController.mapView!.annotations?.isEmpty ?? true) + } - guard let annotations = navigationViewController.mapView?.annotations else { return XCTFail("Annotations not found.")} + guard let annotations = navigationViewController.mapView?.annotations else { return XCTFail("Annotations not found.") } - let firstDestination = initialRoute.routeOptions.waypoints.last!.coordinate - let destinations = annotations.filter(annotationFilter(matching: firstDestination)) + let firstDestination = self.initialRoute.routeOptions.waypoints.last!.coordinate + let destinations = annotations.filter(self.annotationFilter(matching: firstDestination)) XCTAssert(!destinations.isEmpty, "Destination annotation does not exist on map") - //lets set the second route - navigationViewController.route = newRoute + // lets set the second route + navigationViewController.route = self.newRoute - guard let newAnnotations = navigationViewController.mapView?.annotations else { return XCTFail("New annotations not found.")} - let secondDestination = newRoute.routeOptions.waypoints.last!.coordinate + guard let newAnnotations = navigationViewController.mapView?.annotations else { return XCTFail("New annotations not found.") } + let secondDestination = self.newRoute.routeOptions.waypoints.last!.coordinate - //do we have a destination on the second route? - let newDestinations = newAnnotations.filter(annotationFilter(matching: secondDestination)) + // do we have a destination on the second route? + let newDestinations = newAnnotations.filter(self.annotationFilter(matching: secondDestination)) XCTAssert(!newDestinations.isEmpty, "New destination annotation does not exist on map") - } private func annotationFilter(matching coordinate: CLLocationCoordinate2D) -> ((MLNAnnotation) -> Bool) { @@ -217,38 +211,38 @@ class NavigationViewControllerTests: XCTestCase { extension NavigationViewControllerTests: NavigationViewControllerDelegate, StyleManagerDelegate { func locationFor(styleManager: StyleManager) -> CLLocation? { - return dependencies.poi.first! + self.dependencies.poi.first! } func styleManagerDidRefreshAppearance(_ styleManager: StyleManager) { - updatedStyleNumberOfTimes += 1 + self.updatedStyleNumberOfTimes += 1 } func navigationViewController(_ navigationViewController: NavigationViewController, roadNameAt location: CLLocation) -> String? { - return customRoadName[location.coordinate] ?? nil + self.customRoadName[location.coordinate] ?? nil } } extension CLLocationCoordinate2D: Hashable { - // Hash value property multiplied by a prime constant. - public var hashValue: Int { - return latitude.hashValue ^ longitude.hashValue &* 16777619 + public func hash(into hasher: inout Hasher) { + hasher.combine(latitude) + hasher.combine(longitude) } static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool { - return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude + lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude } } -extension NavigationViewControllerTests { - fileprivate func location(at coordinate: CLLocationCoordinate2D) -> CLLocation { - return CLLocation(coordinate: coordinate, - altitude: 5, - horizontalAccuracy: 10, - verticalAccuracy: 5, - course: 20, - speed: 15, - timestamp: Date()) +private extension NavigationViewControllerTests { + func location(at coordinate: CLLocationCoordinate2D) -> CLLocation { + CLLocation(coordinate: coordinate, + altitude: 5, + horizontalAccuracy: 10, + verticalAccuracy: 5, + course: 20, + speed: 15, + timestamp: Date()) } } @@ -260,8 +254,8 @@ class NavigationViewControllerTestable: NavigationViewController { styles: [Style]? = [DayStyle(), NightStyle()], locationManager: NavigationLocationManager? = NavigationLocationManager(), styleLoaded: XCTestExpectation) { - styleLoadedExpectation = styleLoaded - super.init(for: route, directions: directions,styles: styles, locationManager: locationManager, voiceController: FakeVoiceController()) + self.styleLoadedExpectation = styleLoaded + super.init(for: route, directions: directions, styles: styles, locationManager: locationManager, voiceController: FakeVoiceController()) } @objc(initWithRoute:directions:styles:routeController:locationManager:voiceController:) @@ -270,13 +264,13 @@ class NavigationViewControllerTestable: NavigationViewController { } func mapView(_ mapView: MLNMapView, didFinishLoading style: MLNStyle) { - styleLoadedExpectation.fulfill() + self.styleLoadedExpectation.fulfill() } + @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("This initalizer is not supported in this testing subclass.") } - } class TestableDayStyle: DayStyle { @@ -286,13 +280,12 @@ class TestableDayStyle: DayStyle { } } - class FakeVoiceController: RouteVoiceController { override func speak(_ instruction: SpokenInstruction, with locale: Locale?, ignoreProgress: Bool = false) { - //no-op + // no-op } override func pauseSpeechAndPlayReroutingDing(notification: NSNotification) { - //no-op + // no-op } } diff --git a/MapboxNavigationTests/Sources/Tests/StepsViewControllerTests.swift b/MapboxNavigationTests/Sources/Tests/StepsViewControllerTests.swift index 37a96c145..83e3e44b6 100644 --- a/MapboxNavigationTests/Sources/Tests/StepsViewControllerTests.swift +++ b/MapboxNavigationTests/Sources/Tests/StepsViewControllerTests.swift @@ -1,18 +1,16 @@ -import XCTest -import MapboxDirections import CoreLocation @testable import MapboxCoreNavigation +import MapboxDirections @testable import MapboxNavigation +import XCTest class StepsViewControllerTests: XCTestCase { - - struct Constants { + enum Constants { static let jsonRoute = (response["routes"] as! [AnyObject]).first as! [String: Any] static let accessToken = "nonsense" } lazy var dependencies: (stepsViewController: StepsViewController, routeController: RouteController, firstLocation: CLLocation, lastLocation: CLLocation) = { - let bogusToken = "pk.feedCafeDeadBeefBadeBede" let directions = Directions(accessToken: bogusToken) @@ -38,8 +36,7 @@ class StepsViewControllerTests: XCTestCase { }() func testRebuildStepsInstructionsViewDataSource() { - - let stepsViewController = dependencies.stepsViewController + let stepsViewController = self.dependencies.stepsViewController measure { // Measure Performance - stepsViewController.rebuildDataSourceIfNecessary() @@ -53,8 +50,7 @@ class StepsViewControllerTests: XCTestCase { /// NOTE: This test is disabled pending https://github.com/mapbox/mapbox-navigation-ios/issues/1468 func x_testUpdateCellPerformance() { - - let stepsViewController = dependencies.stepsViewController + let stepsViewController = self.dependencies.stepsViewController // Test that Steps ViewController viewLoads XCTAssertNotNil(stepsViewController.view, "StepsViewController not initiated properly") @@ -62,7 +58,7 @@ class StepsViewControllerTests: XCTestCase { let stepsTableView = stepsViewController.tableView! measure { - for i in 0.. CLLocation { - return CLLocation(coordinate: coordinate, - altitude: 5, - horizontalAccuracy: 10, - verticalAccuracy: 5, - course: 20, - speed: 15, - timestamp: Date()) +private extension StepsViewControllerTests { + func location(at coordinate: CLLocationCoordinate2D) -> CLLocation { + CLLocation(coordinate: coordinate, + altitude: 5, + horizontalAccuracy: 10, + verticalAccuracy: 5, + course: 20, + speed: 15, + timestamp: Date()) } } diff --git a/MapboxNavigationTests/Sources/Tests/StyleManagerTests.swift b/MapboxNavigationTests/Sources/Tests/StyleManagerTests.swift index e13fa04f2..f79676d39 100644 --- a/MapboxNavigationTests/Sources/Tests/StyleManagerTests.swift +++ b/MapboxNavigationTests/Sources/Tests/StyleManagerTests.swift @@ -1,27 +1,26 @@ -import XCTest -import Solar import CoreLocation @testable import MapboxNavigation +import Solar +import XCTest -struct Location { +enum Location { static let sf = CLLocation(latitude: 37.78, longitude: -122.40) static let london = CLLocation(latitude: 51.50, longitude: -0.12) static let paris = CLLocation(latitude: 48.85, longitude: 2.35) } class StyleManagerTests: XCTestCase { - var location = Location.london var styleManager: StyleManager! override func setUp() { super.setUp() - styleManager = StyleManager(self) - styleManager.automaticallyAdjustsStyleForTimeOfDay = true + self.styleManager = StyleManager(self) + self.styleManager.automaticallyAdjustsStyleForTimeOfDay = true } func testStyleManagerLondon() { - location = Location.london + self.location = Location.london let dateFormatter = DateFormatter() dateFormatter.dateFormat = "HH:mm" dateFormatter.timeZone = TimeZone(identifier: "UTC") @@ -33,27 +32,27 @@ class StyleManagerTests: XCTestCase { let afterSunset = dateFormatter.date(from: "21:00")! let midnight = dateFormatter.date(from: "00:00")! - styleManager.date = beforeSunrise - XCTAssert(styleManager.styleType(for: location) == .night) - styleManager.date = afterSunrise - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = noonDate - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = beforeSunset - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = afterSunset - XCTAssert(styleManager.styleType(for: location) == .night) - styleManager.date = midnight - XCTAssert(styleManager.styleType(for: location) == .night) + self.styleManager.date = beforeSunrise + XCTAssert(self.styleManager.styleType(for: self.location) == .night) + self.styleManager.date = afterSunrise + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = noonDate + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = beforeSunset + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = afterSunset + XCTAssert(self.styleManager.styleType(for: self.location) == .night) + self.styleManager.date = midnight + XCTAssert(self.styleManager.styleType(for: self.location) == .night) } func testStyleManagerParisWithSeconds() { - location = Location.paris + self.location = Location.paris let dateFormatter = DateFormatter() dateFormatter.dateFormat = "HH:mm:ss" dateFormatter.timeZone = TimeZone(identifier: "CET") - NSTimeZone.default = NSTimeZone.init(abbreviation: "CET")! as TimeZone + NSTimeZone.default = NSTimeZone(abbreviation: "CET")! as TimeZone let justBeforeSunrise = dateFormatter.date(from: "08:44:05")! let justAfterSunrise = dateFormatter.date(from: "08:44:30")! @@ -62,27 +61,27 @@ class StyleManagerTests: XCTestCase { let justAfterSunset = dateFormatter.date(from: "17:04:30")! let midnight = dateFormatter.date(from: "00:00:00")! - styleManager.date = justBeforeSunrise - XCTAssert(styleManager.styleType(for: location) == .night) - styleManager.date = justAfterSunrise - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = noonDate - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = juetBeforeSunset - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = justAfterSunset - XCTAssert(styleManager.styleType(for: location) == .night) - styleManager.date = midnight - XCTAssert(styleManager.styleType(for: location) == .night) + self.styleManager.date = justBeforeSunrise + XCTAssert(self.styleManager.styleType(for: self.location) == .night) + self.styleManager.date = justAfterSunrise + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = noonDate + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = juetBeforeSunset + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = justAfterSunset + XCTAssert(self.styleManager.styleType(for: self.location) == .night) + self.styleManager.date = midnight + XCTAssert(self.styleManager.styleType(for: self.location) == .night) } func testStyleManagerSanFrancisco() { - location = Location.sf + self.location = Location.sf let dateFormatter = DateFormatter() dateFormatter.dateFormat = "hh:mm a" dateFormatter.timeZone = TimeZone(identifier: "PST") - NSTimeZone.default = NSTimeZone.init(abbreviation: "PST")! as TimeZone + NSTimeZone.default = NSTimeZone(abbreviation: "PST")! as TimeZone let beforeSunrise = dateFormatter.date(from: "05:00 AM")! let afterSunrise = dateFormatter.date(from: "09:00 AM")! @@ -91,27 +90,27 @@ class StyleManagerTests: XCTestCase { let afterSunset = dateFormatter.date(from: "09:00 PM")! let midnight = dateFormatter.date(from: "00:00 AM")! - styleManager.date = beforeSunrise - XCTAssert(styleManager.styleType(for: location) == .night) - styleManager.date = afterSunrise - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = noonDate - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = beforeSunset - XCTAssert(styleManager.styleType(for: location) == .day) - styleManager.date = afterSunset - XCTAssert(styleManager.styleType(for: location) == .night) - styleManager.date = midnight - XCTAssert(styleManager.styleType(for: location) == .night) + self.styleManager.date = beforeSunrise + XCTAssert(self.styleManager.styleType(for: self.location) == .night) + self.styleManager.date = afterSunrise + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = noonDate + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = beforeSunset + XCTAssert(self.styleManager.styleType(for: self.location) == .day) + self.styleManager.date = afterSunset + XCTAssert(self.styleManager.styleType(for: self.location) == .night) + self.styleManager.date = midnight + XCTAssert(self.styleManager.styleType(for: self.location) == .night) } func testTimeIntervalsUntilTimeOfDayChanges() { - location = Location.paris + self.location = Location.paris let dateFormatter = DateFormatter() dateFormatter.dateFormat = "HH:mm" dateFormatter.timeZone = TimeZone(identifier: "CET") - NSTimeZone.default = NSTimeZone.init(abbreviation: "CET")! as TimeZone + NSTimeZone.default = NSTimeZone(abbreviation: "CET")! as TimeZone let sunrise = dateFormatter.date(from: "08:00")! let sunset = dateFormatter.date(from: "18:00")! @@ -127,10 +126,10 @@ class StyleManagerTests: XCTestCase { } extension StyleManagerTests: StyleManagerDelegate { - func styleManagerDidRefreshAppearance(_ styleManager: StyleManager) { } - func styleManager(_ styleManager: StyleManager, didApply style: Style) { } + func styleManagerDidRefreshAppearance(_ styleManager: StyleManager) {} + func styleManager(_ styleManager: StyleManager, didApply style: Style) {} func locationFor(styleManager: StyleManager) -> CLLocation? { - return location + self.location } } diff --git a/MapboxNavigationTests/Sources/Tests/XCTestCase.swift b/MapboxNavigationTests/Sources/Tests/XCTestCase.swift index 34f25ded7..bb16ff74a 100644 --- a/MapboxNavigationTests/Sources/Tests/XCTestCase.swift +++ b/MapboxNavigationTests/Sources/Tests/XCTestCase.swift @@ -4,25 +4,25 @@ import XCTest extension XCTestCase { enum NavigationTests { static var timeout: DispatchTime { - return DispatchTime.now() + DispatchTimeInterval.seconds(10) + DispatchTime.now() + DispatchTimeInterval.seconds(10) } static let pollingInterval: TimeInterval = 0.05 } func runUntil(_ condition: () -> Bool, testCase: String = #function) { - runUntil(condition: condition, testCase: testCase, pollingInterval: NavigationTests.pollingInterval, until: NavigationTests.timeout) + self.runUntil(condition: condition, testCase: testCase, pollingInterval: NavigationTests.pollingInterval, until: NavigationTests.timeout) } func runUntil(condition: () -> Bool, testCase: String, pollingInterval: TimeInterval, until timeout: DispatchTime) { - guard (timeout >= DispatchTime.now()) else { + guard timeout >= DispatchTime.now() else { XCTFail("Timeout occurred in \(testCase)") return } if condition() == false { RunLoop.current.run(until: Date(timeIntervalSinceNow: pollingInterval)) - runUntil(condition: condition, testCase: testCase, pollingInterval: pollingInterval, until: timeout) + self.runUntil(condition: condition, testCase: testCase, pollingInterval: pollingInterval, until: timeout) } } } diff --git a/Package.resolved b/Package.resolved index 861056cce..ff52e3ab8 100644 --- a/Package.resolved +++ b/Package.resolved @@ -36,6 +36,15 @@ "version" : "3.0.1" } }, + { + "identity" : "swiftformat", + "kind" : "remoteSourceControl", + "location" : "https://github.com/nicklockwood/SwiftFormat", + "state" : { + "revision" : "9df3b01f477163b33d5e63c5e2e5b9f946a49c56", + "version" : "0.53.6" + } + }, { "identity" : "turf-swift", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index bf39d6833..8209ce581 100644 --- a/Package.swift +++ b/Package.swift @@ -3,103 +3,104 @@ import PackageDescription let package = Package( - name: "maplibre-navigation-ios", - defaultLocalization: "en", - platforms: [.iOS(.v12)], - products: [ - .library( - name: "MapboxNavigation", - targets: [ - "MapboxNavigation" - ] - ) - ], - dependencies: [ - .package(url: "https://github.com/flitsmeister/mapbox-directions-swift", exact: "0.23.3"), - .package(url: "https://github.com/flitsmeister/turf-swift", exact: "0.2.2"), - .package(url: "https://github.com/maplibre/maplibre-gl-native-distribution.git", from: "6.0.0"), - .package(url: "https://github.com/ceeK/Solar.git", exact: "3.0.1") - ], - targets: [ - .target( - name: "MapboxCoreNavigation", - dependencies: [ - .product(name: "Turf", package: "turf-swift"), - .product(name: "MapboxDirections", package: "mapbox-directions-swift"), - "MapboxCoreNavigationObjC" - ], - path: "MapboxCoreNavigation", - resources: [.process("resources")] - ), - .target( - name: "MapboxCoreNavigationObjC", - path: "MapboxCoreNavigationObjC" - ), - .target( - name: "MapboxNavigation", - dependencies: [ - "MapboxCoreNavigation", - "MapboxNavigationObjC", - .product(name: "Solar", package: "Solar") - ], - path: "MapboxNavigation", - resources: [ - .copy("Resources/Assets.xcassets") - ] - ), - .target( - name: "MapboxNavigationObjC", - dependencies: [ - .product(name: "MapLibre", package: "maplibre-gl-native-distribution") - ], - path: "MapboxNavigationObjC" - ), - .testTarget( - name: "MapboxNavigationTests", - dependencies: [ - "MapboxNavigation", - "MapboxCoreNavigation", - "Solar" - ], - path: "MapboxNavigationTests", - resources: [ - // NOTE: Ideally we would just put all resources like route.json and Assets.xcassets in Folder 'Resources' - // but an Xcode/SPM bug is preventing us from doing so. It is not possible to copy and process files into the same - // destination directiory ('*.bundle/Resources') without a code signing error: - // This is the error message: - // CodeSign ~/Library/Developer/Xcode/DerivedData/maplibre-navigation-ios-cdijqyqwjamndzfaqhxchbiayzsb/Build/Products/Debug-iphonesimulator/maplibre-navigation-ios_MapboxNavigationTests.bundle (in target 'maplibre-navigation-ios_MapboxNavigationTests' from project 'maplibre-navigation-ios') - // cd ~/Developer/maplibre-navigation-ios - // - // Signing Identity: "-" - // - // /usr/bin/codesign --force --sign - --timestamp\=none --generate-entitlement-der ~/Library/Developer/Xcode/DerivedData/maplibre-navigation-ios-cdijqyqwjamndzfaqhxchbiayzsb/Build/Products/Debug-iphonesimulator/maplibre-navigation-ios_MapboxNavigationTests.bundle - // - // ~/Library/Developer/Xcode/DerivedData/maplibre-navigation-ios-cdijqyqwjamndzfaqhxchbiayzsb/Build/Products/Debug-iphonesimulator/maplibre-navigation-ios_MapboxNavigationTests.bundle: bundle format unrecognized, invalid, or unsuitable - // Command CodeSign failed with a nonzero exit code - // - // Instead the json files are placed in a Folder called 'Fixtures' and manually specified for copying - // The Assets.xcassets is compiled into an Assets.car - // This results in a flat Bundle file structure however the tests pass. + name: "maplibre-navigation-ios", + defaultLocalization: "en", + platforms: [.iOS(.v12)], + products: [ + .library( + name: "MapboxNavigation", + targets: [ + "MapboxNavigation" + ] + ) + ], + dependencies: [ + .package(url: "https://github.com/flitsmeister/mapbox-directions-swift", exact: "0.23.3"), + .package(url: "https://github.com/flitsmeister/turf-swift", exact: "0.2.2"), + .package(url: "https://github.com/maplibre/maplibre-gl-native-distribution.git", from: "6.0.0"), + .package(url: "https://github.com/ceeK/Solar.git", exact: "3.0.1"), + .package(url: "https://github.com/nicklockwood/SwiftFormat.git", from: "0.53.6") + ], + targets: [ + .target( + name: "MapboxCoreNavigation", + dependencies: [ + .product(name: "Turf", package: "turf-swift"), + .product(name: "MapboxDirections", package: "mapbox-directions-swift"), + "MapboxCoreNavigationObjC" + ], + path: "MapboxCoreNavigation", + resources: [.process("resources")] + ), + .target( + name: "MapboxCoreNavigationObjC", + path: "MapboxCoreNavigationObjC" + ), + .target( + name: "MapboxNavigation", + dependencies: [ + "MapboxCoreNavigation", + "MapboxNavigationObjC", + .product(name: "Solar", package: "Solar") + ], + path: "MapboxNavigation", + resources: [ + .copy("Resources/Assets.xcassets") + ] + ), + .target( + name: "MapboxNavigationObjC", + dependencies: [ + .product(name: "MapLibre", package: "maplibre-gl-native-distribution") + ], + path: "MapboxNavigationObjC" + ), + .testTarget( + name: "MapboxNavigationTests", + dependencies: [ + "MapboxNavigation", + "MapboxCoreNavigation", + "Solar" + ], + path: "MapboxNavigationTests", + resources: [ + // NOTE: Ideally we would just put all resources like route.json and Assets.xcassets in Folder 'Resources' + // but an Xcode/SPM bug is preventing us from doing so. It is not possible to copy and process files into the same + // destination directiory ('*.bundle/Resources') without a code signing error: + // This is the error message: + // CodeSign ~/Library/Developer/Xcode/DerivedData/maplibre-navigation-ios-cdijqyqwjamndzfaqhxchbiayzsb/Build/Products/Debug-iphonesimulator/maplibre-navigation-ios_MapboxNavigationTests.bundle (in target 'maplibre-navigation-ios_MapboxNavigationTests' from project 'maplibre-navigation-ios') + // cd ~/Developer/maplibre-navigation-ios + // + // Signing Identity: "-" + // + // /usr/bin/codesign --force --sign - --timestamp\=none --generate-entitlement-der ~/Library/Developer/Xcode/DerivedData/maplibre-navigation-ios-cdijqyqwjamndzfaqhxchbiayzsb/Build/Products/Debug-iphonesimulator/maplibre-navigation-ios_MapboxNavigationTests.bundle + // + // ~/Library/Developer/Xcode/DerivedData/maplibre-navigation-ios-cdijqyqwjamndzfaqhxchbiayzsb/Build/Products/Debug-iphonesimulator/maplibre-navigation-ios_MapboxNavigationTests.bundle: bundle format unrecognized, invalid, or unsuitable + // Command CodeSign failed with a nonzero exit code + // + // Instead the json files are placed in a Folder called 'Fixtures' and manually specified for copying + // The Assets.xcassets is compiled into an Assets.car + // This results in a flat Bundle file structure however the tests pass. - .process("Assets.xcassets"), - .copy("Fixtures/EmptyStyle.json"), - .copy("Fixtures/route.json"), - .copy("Fixtures/route-for-lane-testing.json"), - .copy("Fixtures/route-with-banner-instructions.json"), - .copy("Fixtures/route-with-instructions.json"), - .copy("Fixtures/route-with-lanes.json") - ] - ), - .testTarget( - name: "MapboxCoreNavigationTests", - dependencies: [ - "MapboxNavigation", - "MapboxCoreNavigation" - ], - path: "MapboxCoreNavigationTests", - resources: [ - .copy("Resources") - ] - ) - ] + .process("Assets.xcassets"), + .copy("Fixtures/EmptyStyle.json"), + .copy("Fixtures/route.json"), + .copy("Fixtures/route-for-lane-testing.json"), + .copy("Fixtures/route-with-banner-instructions.json"), + .copy("Fixtures/route-with-instructions.json"), + .copy("Fixtures/route-with-lanes.json") + ] + ), + .testTarget( + name: "MapboxCoreNavigationTests", + dependencies: [ + "MapboxNavigation", + "MapboxCoreNavigation" + ], + path: "MapboxCoreNavigationTests", + resources: [ + .copy("Resources") + ] + ) + ] )