diff --git a/Examples/ArcGISToolkitExamples.xcodeproj/project.pbxproj b/Examples/ArcGISToolkitExamples.xcodeproj/project.pbxproj
index 3ba84b0b..2e0889be 100644
--- a/Examples/ArcGISToolkitExamples.xcodeproj/project.pbxproj
+++ b/Examples/ArcGISToolkitExamples.xcodeproj/project.pbxproj
@@ -439,7 +439,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -493,7 +493,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@@ -514,7 +514,9 @@
"$(USER_LIBRARY_DIR)/SDKs/ArcGIS/iOS/Frameworks/Dynamic",
);
INFOPLIST_FILE = ArcGISToolkitExamples/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MARKETING_VERSION = 100.8;
PRODUCT_BUNDLE_IDENTIFIER = "com.esri.${PRODUCT_NAME}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -534,7 +536,9 @@
"$(USER_LIBRARY_DIR)/SDKs/ArcGIS/iOS/Frameworks/Dynamic",
);
INFOPLIST_FILE = ArcGISToolkitExamples/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MARKETING_VERSION = 100.8;
PRODUCT_BUNDLE_IDENTIFIER = "com.esri.${PRODUCT_NAME}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
diff --git a/Examples/ArcGISToolkitExamples/AppDelegate.swift b/Examples/ArcGISToolkitExamples/AppDelegate.swift
index 9dce979a..baf37d18 100644
--- a/Examples/ArcGISToolkitExamples/AppDelegate.swift
+++ b/Examples/ArcGISToolkitExamples/AppDelegate.swift
@@ -44,10 +44,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
-
- // Here is where we forward background fetch to the JobManager
- // so that jobs can be updated in the background
- func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
- JobManager.shared.application(application: application, performFetchWithCompletionHandler: completionHandler)
- }
}
diff --git a/Examples/ArcGISToolkitExamples/Info.plist b/Examples/ArcGISToolkitExamples/Info.plist
index 39f45da8..4e298317 100644
--- a/Examples/ArcGISToolkitExamples/Info.plist
+++ b/Examples/ArcGISToolkitExamples/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 100.6
+ $(MARKETING_VERSION)
CFBundleVersion
1
LSRequiresIPhoneOS
@@ -32,9 +32,7 @@
NSPhotoLibraryUsageDescription
For collecting attachments in popups
UIBackgroundModes
-
- fetch
-
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
diff --git a/Examples/ArcGISToolkitExamples/JobManagerExample.swift b/Examples/ArcGISToolkitExamples/JobManagerExample.swift
index 9516bbe9..aed80b8b 100644
--- a/Examples/ArcGISToolkitExamples/JobManagerExample.swift
+++ b/Examples/ArcGISToolkitExamples/JobManagerExample.swift
@@ -22,17 +22,10 @@ import UserNotifications
// restart the application, and find out what jobs were running and have the ability to
// resume them.
//
-// The other aspect of this sample is that if you just background the app then it will
-// provide a helper method that helps with background fetch.
-//
-// See the AppDelegate.swift for implementation of the function:
-// `func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)`
-// We forward that call to the shared JobManager so that it can perform the background fetch.
-//
class JobTableViewCell: UITableViewCell {
var job: AGSJob?
- var statusObservation: NSKeyValueObservation?
+ var observation: NSKeyValueObservation?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
@@ -44,15 +37,15 @@ class JobTableViewCell: UITableViewCell {
func configureWithJob(job: AGSJob?) {
// invalidate previous observation
- statusObservation?.invalidate()
- statusObservation = nil
+ observation?.invalidate()
+ observation = nil
self.job = job
self.updateUI()
- // observe job status
- statusObservation = self.job?.observe(\.status, options: .new) { [weak self] (_, _) in
+ // observe job
+ observation = self.job?.progress.observe(\.fractionCompleted) { [weak self] (_, _) in
DispatchQueue.main.async {
self?.updateUI()
}
@@ -108,6 +101,8 @@ class JobManagerExample: TableViewController {
return JobManager.shared.jobs
}
+ var backgroundTaskIdentifiers = Set()
+
var toolbar: UIToolbar?
override func viewDidLoad() {
@@ -129,19 +124,6 @@ class JobManagerExample: TableViewController {
// now anchor toolbar below new safe area
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
- // button to kick off a new job
- let kickOffJobItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(kickOffJob))
-
- // button to resume all paused jobs
- // use this to resume the paused jobs you have after restarting your app
- let resumeAllPausedJobsItem = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(resumeAllPausedJobs))
-
- // button to clear the finished jobs
- let clearFinishedJobsItem = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(clearFinishedJobs))
-
- let flex = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
- toolbar.items = [kickOffJobItem, flex, resumeAllPausedJobsItem, flex, clearFinishedJobsItem]
-
// request authorization for user notifications, this way we can notify user in bg when job complete
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, _) in
@@ -154,9 +136,69 @@ class JobManagerExample: TableViewController {
tableView.register(JobTableViewCell.self, forCellReuseIdentifier: "JobCell")
}
- @objc
- func resumeAllPausedJobs() {
- JobManager.shared.resumeAllPausedJobs(statusHandler: self.jobStatusHandler, completion: self.jobCompletionHandler)
+ override func viewDidAppear(_ animated: Bool) {
+ // resume any paused jobs when this view controller is shown
+ JobManager.shared.resumeAllPausedJobs(
+ statusHandler: { [weak self] in
+ self?.jobStatusHandler(status: $0)
+ },
+ completion: { [weak self] in
+ self?.jobCompletionHandler(result: $0, error: $1)
+ }
+ )
+
+ super.viewDidAppear(animated)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ // When the view controller is popped, we pause all running jobs.
+ // In a normal app you would not need to do this, but this view controller
+ // is acting as an app example. Thus when it is not being shown, we pause
+ // the jobs so that when the view controller is re-shown we can resume and rewire
+ // the handlers up to them. Otherwise we would have no way to hook into the status
+ // of any currently running jobs. A normal app would not likely need this as it would
+ // have an object globally wiring up status and completion handlers to jobs.
+ // But since this sample view controller can be pushed/pop, we need this.
+ JobManager.shared.pauseAllJobs()
+
+ // clear out background tasks that we started for the jobs
+ backgroundTaskIdentifiers.forEach { UIApplication.shared.endBackgroundTask($0) }
+ backgroundTaskIdentifiers.removeAll()
+
+ super.viewWillDisappear(animated)
+ }
+
+ deinit {
+ // clear out background tasks that we started for the jobs
+ backgroundTaskIdentifiers.forEach { UIApplication.shared.endBackgroundTask($0) }
+ }
+
+ func startBackgroundTask() -> UIBackgroundTaskIdentifier {
+ var backgroundTaskIdentifier: UIBackgroundTaskIdentifier = .invalid
+ backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask {
+ UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
+ }
+ backgroundTaskIdentifiers.insert(backgroundTaskIdentifier)
+ return backgroundTaskIdentifier
+ }
+
+ func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) {
+ UIApplication.shared.endBackgroundTask(identifier)
+ backgroundTaskIdentifiers.remove(identifier)
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+ if let toolbar = toolbar, toolbar.items == nil {
+ // button to kick off a new job
+ let kickOffJobItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(kickOffJob))
+
+ // button to clear the finished jobs
+ let clearFinishedJobsItem = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(clearFinishedJobs))
+
+ let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+ toolbar.items = [kickOffJobItem, flexibleSpace, clearFinishedJobsItem]
+ }
}
@objc
@@ -211,6 +253,9 @@ class JobManagerExample: TableViewController {
}
func generateGDB(URL: URL, syncModel: AGSSyncModel, extent: AGSEnvelope?) {
+ // try to keep the app running in the background for this job if possible
+ let backgroundTaskIdentifier = self.startBackgroundTask()
+
let task = AGSGeodatabaseSyncTask(url: URL)
// hold on to task so that it stays retained while it's loading
@@ -218,11 +263,9 @@ class JobManagerExample: TableViewController {
task.load { [weak self, weak task] error in
// make sure we are still around...
- guard let self = self else {
- return
- }
-
- guard let strongTask = task else {
+ guard let self = self, let strongTask = task else {
+ // don't need to end the background task here as that
+ // would have been done in deinit
return
}
@@ -232,11 +275,8 @@ class JobManagerExample: TableViewController {
}
// return if error or no featureServiceInfo
- guard error == nil else {
- return
- }
-
- guard let fsi = strongTask.featureServiceInfo else {
+ guard error == nil, let fsi = strongTask.featureServiceInfo else {
+ self.endBackgroundTask(backgroundTaskIdentifier)
return
}
@@ -271,7 +311,15 @@ class JobManagerExample: TableViewController {
JobManager.shared.register(job: job)
// start the job
- job.start(statusHandler: self.jobStatusHandler, completion: self.jobCompletionHandler)
+ job.start(
+ statusHandler: { [weak self] in
+ self?.jobStatusHandler(status: $0)
+ },
+ completion: { [weak self] in
+ self?.jobCompletionHandler(result: $0, error: $1)
+ self?.endBackgroundTask(backgroundTaskIdentifier)
+ }
+ )
// refresh the tableview
self.tableView.reloadData()
@@ -279,6 +327,9 @@ class JobManagerExample: TableViewController {
}
func takeOffline(map: AGSMap, extent: AGSEnvelope) {
+ // try to keep the app running in the background for this job if possible
+ let backgroundTaskIdentifier = self.startBackgroundTask()
+
let task = AGSOfflineMapTask(onlineMap: map)
let uuid = NSUUID()
@@ -287,6 +338,8 @@ class JobManagerExample: TableViewController {
task.defaultGenerateOfflineMapParameters(withAreaOfInterest: extent) { [weak self] params, error in
// make sure we are still around...
guard let self = self else {
+ // don't need to end the background task here as that
+ // would have been done in deinit
return
}
@@ -297,13 +350,22 @@ class JobManagerExample: TableViewController {
JobManager.shared.register(job: job)
// start the job
- job.start(statusHandler: self.jobStatusHandler, completion: self.jobCompletionHandler)
+ job.start(
+ statusHandler: { [weak self] in
+ self?.jobStatusHandler(status: $0)
+ },
+ completion: { [weak self] in
+ self?.jobCompletionHandler(result: $0, error: $1)
+ self?.endBackgroundTask(backgroundTaskIdentifier)
+ }
+ )
// refresh the tableview
self.tableView.reloadData()
} else {
// if could not get default parameters, then fire completion with the error
self.jobCompletionHandler(result: nil, error: error)
+ self.endBackgroundTask(backgroundTaskIdentifier)
}
}
}
diff --git a/README.md b/README.md
index 52d49933..5a010164 100644
--- a/README.md
+++ b/README.md
@@ -24,11 +24,10 @@ To use Toolkit in your project:
* **[TimeSlider](Documentation/TimeSlider)** - Allows interactively defining a temporal range (i.e. time extent) and animating time moving forward or backward. Can be used to manipulate the time extent in a MapView or SceneView.
## Requirements
+* [ArcGIS Runtime SDK for iOS](https://developers.arcgis.com/en/ios/) 100.8.0 (or higher)
+* Xcode 11.0 (or higher)
-* [ArcGIS Runtime SDK for iOS](https://developers.arcgis.com/en/ios/) 100.7.0 (or higher)
-* Xcode 10.2 (or higher)
-
-The *ArcGIS Runtime Toolkit for iOS* has a *Target SDK* version of *11.0*, meaning that it can run on devices with *iOS 11.0* or newer.
+The *ArcGIS Runtime Toolkit for iOS* has a *Target SDK* version of *12.0*, meaning that it can run on devices with *iOS 12.0* or newer.
## Instructions
diff --git a/Toolkit/ArcGISToolkit.xcodeproj/project.pbxproj b/Toolkit/ArcGISToolkit.xcodeproj/project.pbxproj
index 03a31225..0c664e90 100644
--- a/Toolkit/ArcGISToolkit.xcodeproj/project.pbxproj
+++ b/Toolkit/ArcGISToolkit.xcodeproj/project.pbxproj
@@ -426,7 +426,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -483,7 +483,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@@ -509,7 +509,9 @@
);
INFOPLIST_FILE = ArcGISToolkit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MARKETING_VERSION = 100.8;
PRODUCT_BUNDLE_IDENTIFIER = com.esri.ArcGISToolkit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -533,7 +535,9 @@
);
INFOPLIST_FILE = ArcGISToolkit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MARKETING_VERSION = 100.8;
PRODUCT_BUNDLE_IDENTIFIER = com.esri.ArcGISToolkit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -553,7 +557,6 @@
FRAMEWORK_SEARCH_PATHS = "$(USER_LIBRARY_DIR)/SDKs/ArcGIS/iOS/Frameworks/Dynamic";
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ArcGISToolkitTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
@@ -576,7 +579,6 @@
FRAMEWORK_SEARCH_PATHS = "$(USER_LIBRARY_DIR)/SDKs/ArcGIS/iOS/Frameworks/Dynamic";
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ArcGISToolkitTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.esri.ArcGISToolkitTests;
diff --git a/Toolkit/ArcGISToolkit/Info.plist b/Toolkit/ArcGISToolkit/Info.plist
index aa1b0165..ec0cc7b0 100644
--- a/Toolkit/ArcGISToolkit/Info.plist
+++ b/Toolkit/ArcGISToolkit/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 100.6
+ $(MARKETING_VERSION)
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSPrincipalClass
diff --git a/Toolkit/ArcGISToolkit/JobManager.swift b/Toolkit/ArcGISToolkit/JobManager.swift
index c4384d12..9f073926 100644
--- a/Toolkit/ArcGISToolkit/JobManager.swift
+++ b/Toolkit/ArcGISToolkit/JobManager.swift
@@ -62,14 +62,14 @@ public class JobManager: NSObject {
}
didSet {
keyedJobs.values.forEach { observeJobStatus(job: $0) }
-
+
// If there was a change, then re-store the serialized AGSJobs in UserDefaults
if keyedJobs != oldValue {
saveJobsToUserDefaults()
}
}
}
-
+
/// A convenience accessor to the `AGSJob`s that the `JobManager` is managing.
public var jobs: [AGSJob] {
return Array(keyedJobs.values)
@@ -112,7 +112,7 @@ public class JobManager: NSObject {
jobStatusObservations.removeValue(forKey: job.serverJobID)
}
}
-
+
/// Register an `AGSJob` with the `JobManager`.
///
/// - Parameter job: The AGSJob to register.
@@ -123,7 +123,7 @@ public class JobManager: NSObject {
keyedJobs[jobUniqueID] = job
return jobUniqueID
}
-
+
/// Unregister an `AGSJob` from the `JobManager`.
///
/// - Parameter job: The job to unregister.
@@ -198,6 +198,7 @@ public class JobManager: NSObject {
/// - Parameters:
/// - application: See [Apple's documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623125-application)
/// - completionHandler: See [Apple's documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623125-application)
+ @available(iOS, deprecated: 13.0, message: "Please use 'UIApplication.shared.beginBackgroundTask(expirationHandler:)' when kicking off your job instead")
public func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if keyedJobs.isEmpty {
return completionHandler(.noData)
@@ -228,6 +229,14 @@ public class JobManager: NSObject {
}
}
+ /// Pauses any currently running job.
+ public func pauseAllJobs() {
+ keyedJobs.values.forEach {
+ guard $0.status == .started else { return }
+ $0.progress.pause()
+ }
+ }
+
/// Saves all managed `AGSJob`s to User Defaults.
///
/// This happens automatically when the `AGSJob`s are registered/unregistered.
diff --git a/Toolkit/ArcGISToolkit/MeasureToolbar.swift b/Toolkit/ArcGISToolkit/MeasureToolbar.swift
index baae9de5..3b07d4f1 100644
--- a/Toolkit/ArcGISToolkit/MeasureToolbar.swift
+++ b/Toolkit/ArcGISToolkit/MeasureToolbar.swift
@@ -37,6 +37,7 @@ class MeasureResultView: UIView {
valueLabel.text = helpText
unitButton.isHidden = true
unitButton.setTitle(nil, for: .normal)
+ invalidateIntrinsicContentSize()
}
}
}
@@ -220,16 +221,9 @@ public class MeasureToolbar: UIToolbar, AGSGeoViewTouchDelegate {
private var undoButton: UIBarButtonItem!
private var redoButton: UIBarButtonItem!
private var clearButton: UIBarButtonItem!
- private var rightHiddenPlaceholderView: UIView!
- private var leftHiddenPlaceholderView: UIView!
private var segControl: UISegmentedControl!
private var segControlItem: UIBarButtonItem!
- private var mode: MeasureToolbarMode? = nil {
- didSet {
- guard mode != oldValue else { return }
- updateMeasurement()
- }
- }
+ private var mode: MeasureToolbarMode?
private let geodeticCurveType: AGSGeodeticCurveType = .geodesic
// This is the threshold for which when the planar measurements are above,
@@ -266,47 +260,33 @@ public class MeasureToolbar: UIToolbar, AGSGeoViewTouchDelegate {
private func sharedInitialization() {
let bundle = Bundle(for: type(of: self))
- let measureLengthImage = UIImage(named: "MeasureLength", in: bundle, compatibleWith: nil)
- let measureAreaImage = UIImage(named: "MeasureArea", in: bundle, compatibleWith: nil)
- let measureFeatureImage = UIImage(named: "MeasureFeature", in: bundle, compatibleWith: nil)
- let undoImage = UIImage(named: "Undo", in: bundle, compatibleWith: nil)
- let redoImage = UIImage(named: "Redo", in: bundle, compatibleWith: nil)
-
- undoButton = UIBarButtonItem(image: undoImage, style: .plain, target: nil, action: nil)
- redoButton = UIBarButtonItem(image: redoImage, style: .plain, target: nil, action: nil)
- clearButton = UIBarButtonItem(barButtonSystemItem: .trash, target: nil, action: nil)
-
- segControl = UISegmentedControl(items: ["Length", "Area", "Select"])
- segControl.setImage(measureLengthImage, forSegmentAt: 0)
- segControl.setImage(measureAreaImage, forSegmentAt: 1)
- segControl.setImage(measureFeatureImage, forSegmentAt: 2)
+ let measureLengthImage = UIImage(named: "MeasureLength", in: bundle, compatibleWith: traitCollection)!
+ let measureAreaImage = UIImage(named: "MeasureArea", in: bundle, compatibleWith: traitCollection)!
+ let measureFeatureImage = UIImage(named: "MeasureFeature", in: bundle, compatibleWith: traitCollection)!
+ let undoImage = UIImage(named: "Undo", in: bundle, compatibleWith: traitCollection)
+ let redoImage = UIImage(named: "Redo", in: bundle, compatibleWith: traitCollection)
+
+ undoButton = UIBarButtonItem(image: undoImage, style: .plain, target: self, action: #selector(undoButtonTap))
+ redoButton = UIBarButtonItem(image: redoImage, style: .plain, target: self, action: #selector(redoButtonTap))
+ clearButton = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(clearButtonTap))
+
+ segControl = UISegmentedControl(items: [measureLengthImage, measureAreaImage, measureFeatureImage])
segControlItem = UIBarButtonItem(customView: segControl)
resultView.buttonTapHandler = { [weak self] in
self?.unitsButtonTap()
}
- undoButton.target = self
- undoButton.action = #selector(undoButtonTap)
- redoButton.target = self
- redoButton.action = #selector(redoButtonTap)
- clearButton.target = self
- clearButton.action = #selector(clearButtonTap)
-
segControl.addTarget(self, action: #selector(segmentControlValueChanged), for: .valueChanged)
- let flexButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
-
- rightHiddenPlaceholderView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
- let rightHiddenPlaceholderButton = UIBarButtonItem(customView: rightHiddenPlaceholderView)
- rightHiddenPlaceholderButton.isEnabled = false
-
- leftHiddenPlaceholderView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
- let leftHiddenPlaceholderButton = UIBarButtonItem(customView: leftHiddenPlaceholderView)
- leftHiddenPlaceholderButton.isEnabled = false
-
- sketchModeButtons = [segControlItem, leftHiddenPlaceholderButton, flexButton, rightHiddenPlaceholderButton, undoButton, redoButton, clearButton]
- selectModeButtons = [segControlItem, leftHiddenPlaceholderButton, flexButton, rightHiddenPlaceholderButton]
+ let flexibleSpaceItem1 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+ let resultViewItem = UIBarButtonItem(customView: resultView)
+ let flexibleSpaceItem2 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+ selectModeButtons = [segControlItem,
+ flexibleSpaceItem1,
+ resultViewItem,
+ flexibleSpaceItem2]
+ sketchModeButtons = selectModeButtons + [undoButton, redoButton, clearButton]
// notification
NotificationCenter.default.addObserver(self, selector: #selector(sketchEditorGeometryDidChange(_:)), name: .AGSSketchEditorGeometryDidChange, object: nil)
@@ -341,46 +321,16 @@ public class MeasureToolbar: UIToolbar, AGSGeoViewTouchDelegate {
}
}
- private var didSetConstraints: Bool = false
-
- override public func updateConstraints() {
- super.updateConstraints()
-
- guard !didSetConstraints else {
- return
+ override public func layoutSubviews() {
+ switch mode {
+ case .length, .area:
+ items = sketchModeButtons
+ case .feature:
+ items = selectModeButtons
+ case .none:
+ items = []
}
-
- // NOTE: Cannot add resultView as a subview until updateConstraints
- // or else the constraints wont be setup correctly.
- addSubview(resultView)
-
- resultView.translatesAutoresizingMaskIntoConstraints = false
-
- resultView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
-
- // The following constraints cause the results view to be centered,
- // however if the content is too big it is allowed to grow to the right.
- // The two centerX constraints are arranged with specific priorities to cause this.
-
- let space: CGFloat = 2
-
- let c1 = resultView.leadingAnchor.constraint(greaterThanOrEqualTo: leftHiddenPlaceholderView.trailingAnchor, constant: space)
- c1.priority = .required
-
- // have to give this just below required, otherwise before the left and right views are setup in
- // their proper locations we can get constraint errors
- let c2 = resultView.trailingAnchor.constraint(lessThanOrEqualTo: rightHiddenPlaceholderView.leadingAnchor, constant: -space)
- c2.priority = UILayoutPriority(rawValue: 999)
-
- let c3 = NSLayoutConstraint(item: resultView, attribute: .centerX, relatedBy: .greaterThanOrEqual, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)
- c3.priority = .required
-
- let c4 = NSLayoutConstraint(item: resultView, attribute: .centerX, relatedBy: .lessThanOrEqual, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)
- c4.priority = .defaultLow
-
- NSLayoutConstraint.activate([c1, c2, c3, c4])
-
- didSetConstraints = true
+ super.layoutSubviews()
}
override public class var requiresConstraintBasedLayout: Bool {
@@ -396,6 +346,7 @@ public class MeasureToolbar: UIToolbar, AGSGeoViewTouchDelegate {
} else if segControl.selectedSegmentIndex == 2 {
startFeatureMode()
}
+ setNeedsLayout()
}
private func startLineMode() {
@@ -405,12 +356,15 @@ public class MeasureToolbar: UIToolbar, AGSGeoViewTouchDelegate {
mode = .length
selectionOverlay?.isVisible = false
- self.items = sketchModeButtons
mapView?.sketchEditor = lineSketchEditor
if !lineSketchEditor.isStarted {
lineSketchEditor.start(with: AGSSketchCreationMode.polyline)
}
+
+ // updateMeasurement() requires mode property and sketch editor
+ // properties to be current, so we do this last when changing modes
+ updateMeasurement()
}
private func startAreaMode() {
@@ -420,12 +374,15 @@ public class MeasureToolbar: UIToolbar, AGSGeoViewTouchDelegate {
mode = .area
selectionOverlay?.isVisible = false
- self.items = sketchModeButtons
mapView?.sketchEditor = areaSketchEditor
if !areaSketchEditor.isStarted {
areaSketchEditor.start(with: AGSSketchCreationMode.polygon)
}
+
+ // updateMeasurement() requires mode property and sketch editor
+ // properties to be current, so we do this last when changing modes
+ updateMeasurement()
}
private func startFeatureMode() {
@@ -435,8 +392,11 @@ public class MeasureToolbar: UIToolbar, AGSGeoViewTouchDelegate {
mode = .feature
selectionOverlay?.isVisible = true
- self.items = selectModeButtons
mapView?.sketchEditor = nil
+
+ // updateMeasurement() requires mode property and sketch editor
+ // properties to be current, so we do this last when changing modes
+ updateMeasurement()
}
@objc
@@ -454,27 +414,49 @@ public class MeasureToolbar: UIToolbar, AGSGeoViewTouchDelegate {
mapView?.sketchEditor?.clearGeometry()
}
+ private lazy var linearUnits: [AGSLinearUnit] = {
+ let linearUnitIDs: [AGSLinearUnitID] = [.centimeters, .feet, .inches, .kilometers, .meters, .miles, .millimeters, .nauticalMiles, .yards]
+ return linearUnitIDs
+ .compactMap(AGSLinearUnit.init)
+ .sorted { $0.pluralDisplayName < $1.pluralDisplayName }
+ }()
+
+ private lazy var areaUnits: [AGSAreaUnit] = {
+ let areaUnitIDs: [AGSAreaUnitID] = [.acres, .hectares, .squareCentimeters, .squareDecimeters, .squareFeet, .squareKilometers, .squareMeters, .squareMillimeters, .squareMiles, .squareYards]
+ return areaUnitIDs
+ .compactMap(AGSAreaUnit.init)
+ .sorted { $0.pluralDisplayName < $1.pluralDisplayName }
+ }()
+
private func unitsButtonTap() {
let units: [AGSUnit]
let selectedUnit: AGSUnit
- if mapView?.sketchEditor == lineSketchEditor ||
- selectedGeometry?.geometryType == .polyline {
- let linearUnitIDs: [AGSLinearUnitID] = [.centimeters, .feet, .inches, .kilometers, .meters, .miles, .millimeters, .nauticalMiles, .yards]
- units = linearUnitIDs.compactMap { AGSLinearUnit(unitID: $0) }
+
+ guard let mode = mode else { return }
+
+ switch mode {
+ case .length:
+ units = linearUnits
selectedUnit = selectedLinearUnit
- } else if mapView?.sketchEditor == areaSketchEditor ||
- selectedGeometry?.geometryType == .envelope ||
- selectedGeometry?.geometryType == .polygon {
- let areaUnitIDs: [AGSAreaUnitID] = [.acres, .hectares, .squareCentimeters, .squareDecimeters, .squareFeet, .squareKilometers, .squareMeters, .squareMillimeters, .squareMiles, .squareYards]
- units = areaUnitIDs.compactMap { AGSAreaUnit(unitID: $0) }
+ case .area:
+ units = areaUnits
selectedUnit = selectedAreaUnit
- } else {
- return
+ case .feature:
+ if selectedGeometry?.geometryType == .polyline {
+ units = linearUnits
+ selectedUnit = selectedLinearUnit
+ } else if selectedGeometry?.geometryType == .envelope ||
+ selectedGeometry?.geometryType == .polygon {
+ units = areaUnits
+ selectedUnit = selectedAreaUnit
+ } else {
+ return
+ }
}
let unitsViewController = UnitsViewController()
unitsViewController.delegate = self
- unitsViewController.units = units.sorted { $0.pluralDisplayName < $1.pluralDisplayName }
+ unitsViewController.units = units
unitsViewController.selectedUnit = selectedUnit
let navigationController = UINavigationController(rootViewController: unitsViewController)
diff --git a/Toolkit/ArcGISToolkit/Scalebar.swift b/Toolkit/ArcGISToolkit/Scalebar.swift
index 092669ee..faf76e41 100644
--- a/Toolkit/ArcGISToolkit/Scalebar.swift
+++ b/Toolkit/ArcGISToolkit/Scalebar.swift
@@ -181,7 +181,7 @@ public class Scalebar: UIView {
}
}
- @IBInspectable public var lineColor: UIColor = UIColor.white {
+ @IBInspectable public var lineColor: UIColor = .white {
didSet {
setNeedsDisplay()
}
diff --git a/Toolkit/ArcGISToolkit/TemplatePickerViewController.swift b/Toolkit/ArcGISToolkit/TemplatePickerViewController.swift
index 9b7317d8..26e86350 100644
--- a/Toolkit/ArcGISToolkit/TemplatePickerViewController.swift
+++ b/Toolkit/ArcGISToolkit/TemplatePickerViewController.swift
@@ -98,7 +98,7 @@ public class TemplatePickerViewController: TableViewController {
private func makeSearchController() -> UISearchController {
let searchController = UISearchController(searchResultsController: nil)
- searchController.dimsBackgroundDuringPresentation = false
+ searchController.obscuresBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchResultsUpdater = self
diff --git a/Toolkit/ArcGISToolkit/UnitsViewController.swift b/Toolkit/ArcGISToolkit/UnitsViewController.swift
index 9083663e..f7014155 100644
--- a/Toolkit/ArcGISToolkit/UnitsViewController.swift
+++ b/Toolkit/ArcGISToolkit/UnitsViewController.swift
@@ -111,7 +111,7 @@ public class UnitsViewController: TableViewController {
/// - Returns: A configured search controller.
private func makeSearchController() -> UISearchController {
let searchController = UISearchController(searchResultsController: nil)
- searchController.dimsBackgroundDuringPresentation = false
+ searchController.obscuresBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchResultsUpdater = self