Skip to content

Commit

Permalink
Merge pull request #104 from Esri/update8
Browse files Browse the repository at this point in the history
Update8
  • Loading branch information
mhdostal committed May 8, 2020
2 parents a4ddfde + 31b47db commit b57fc2e
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 163 deletions.
8 changes: 6 additions & 2 deletions Examples/ArcGISToolkitExamples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand All @@ -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";
Expand All @@ -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;
Expand Down
6 changes: 0 additions & 6 deletions Examples/ArcGISToolkitExamples/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
6 changes: 2 additions & 4 deletions Examples/ArcGISToolkitExamples/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>100.6</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
Expand All @@ -32,9 +32,7 @@
<key>NSPhotoLibraryUsageDescription</key>
<string>For collecting attachments in popups</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
<array/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
142 changes: 102 additions & 40 deletions Examples/ArcGISToolkitExamples/JobManagerExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
}
Expand Down Expand Up @@ -108,6 +101,8 @@ class JobManagerExample: TableViewController {
return JobManager.shared.jobs
}

var backgroundTaskIdentifiers = Set<UIBackgroundTaskIdentifier>()

var toolbar: UIToolbar?

override func viewDidLoad() {
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -211,18 +253,19 @@ 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
self.tasks.append(task)

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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -271,14 +311,25 @@ 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()
}
}

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()
Expand All @@ -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
}

Expand All @@ -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)
}
}
}
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 6 additions & 4 deletions Toolkit/ArcGISToolkit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Toolkit/ArcGISToolkit/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>100.6</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
Expand Down
Loading

0 comments on commit b57fc2e

Please sign in to comment.