diff options
-rw-r--r-- | platform/darwin/include/MGLOfflinePack.h | 104 | ||||
-rw-r--r-- | platform/darwin/include/MGLOfflineStorage.h | 119 | ||||
-rw-r--r-- | platform/darwin/src/MGLOfflinePack.mm | 24 | ||||
-rw-r--r-- | platform/darwin/src/MGLOfflinePack_Private.h | 54 | ||||
-rw-r--r-- | platform/darwin/src/MGLOfflineStorage.mm | 60 | ||||
-rw-r--r-- | platform/ios/app/MBXOfflinePacksTableViewController.m | 30 |
6 files changed, 238 insertions, 153 deletions
diff --git a/platform/darwin/include/MGLOfflinePack.h b/platform/darwin/include/MGLOfflinePack.h index fd8080523c..666e442d26 100644 --- a/platform/darwin/include/MGLOfflinePack.h +++ b/platform/darwin/include/MGLOfflinePack.h @@ -4,8 +4,6 @@ NS_ASSUME_NONNULL_BEGIN -@protocol MGLOfflinePackDelegate; - /** The state an offline pack is currently in. */ @@ -13,11 +11,9 @@ typedef NS_ENUM (NSInteger, MGLOfflinePackState) { /** It is unknown whether the pack is inactive, active, or complete. - This is the initial state of a pack that is obtained using the - `-[MGLOfflineStorage getPacksWithCompletionHandler:]` method. The state - becomes known by the time the pack’s delegate receives its first progress - update. For inactive packs, you must explicitly request a progress update - using the `-[MGLOfflinePack requestProgress]` method. + This is the initial state of a pack. The state becomes known by the time + the shared `MGLOfflineStorage` object sends its first + `MGLOfflinePackProgressChangedNotification`. An invalid pack always has a state of `MGLOfflinePackStateInvalid`, never `MGLOfflinePackStateUnknown`. @@ -86,9 +82,7 @@ typedef struct MGLOfflinePackProgress { /** An `MGLOfflinePack` represents a collection of resources necessary for viewing - a region offline to a local database. It provides an optional - `MGLOfflinePackDelegate` object with progress updates as data or errors arrive - from the server. + a region offline to a local database. */ @interface MGLOfflinePack : NSObject @@ -109,9 +103,11 @@ typedef struct MGLOfflinePackProgress { The pack’s current state. The state of an inactive or completed pack is computed lazily and is set to - `MGLOfflinePackStateUnknown` by default. If you need the state of a pack - inside an `MGLOfflinePackListingCompletionHandler`, set the `delegate` property - then call the `-requestProgress` method. + `MGLOfflinePackStateUnknown` by default. To get notified when the state becomes + known, observe KVO change notifications on this pack’s `state` key path. + Alternatively, you can add an observer for + `MGLOfflinePackProgressChangedNotification`s about this pack that come from the + default notification center. */ @property (nonatomic, readonly) MGLOfflinePackState state; @@ -119,79 +115,47 @@ typedef struct MGLOfflinePackProgress { The pack’s current progress. The progress of an inactive or completed pack is computed lazily, and all its - fields are set to 0 by default. If you need the progress of a pack inside an - `MGLOfflinePackListingCompletionHandler`, set the `delegate` property then call - the `-requestProgress` method. + fields are set to 0 by default. To get notified when the progress becomes + known, observe KVO change notifications on this pack’s `state` key path. + Alternatively, you can add an observer for + `MGLOfflinePackProgressChangedNotification`s about this pack that come from the + default notification center. */ @property (nonatomic, readonly) MGLOfflinePackProgress progress; -/** - The pack’s delegate. - - You can use the offline pack delegate to be notified of any changes in the - pack’s progress and of any errors while downloading. For more information, see - the `MGLOfflinePackDelegate` documentation. - */ -@property (nonatomic, weak, nullable) id <MGLOfflinePackDelegate> delegate; - - (instancetype)init NS_UNAVAILABLE; /** Resumes downloading if the pack is inactive. - */ -- (void)resume; - -/** - Temporarily stops downloading if the pack is active. - To resume downloading, call the `-resume` method. - */ -- (void)suspend; - -@end - -/** - The `MGLOfflinePackDelegate` protocol defines methods that a delegate of an - `MGLOfflinePack` object can optionally implement to be notified of any changes - in the pack’s download progress and of any errors while downloading. - */ -@protocol MGLOfflinePackDelegate <NSObject> - -@optional - -/** - Sent whenever the pack’s state or download progress changes. Every change to a - field in the `progress` property corresponds to an invocation of this method. + A pack resumes asynchronously. To get notified when this pack resumes, observe + KVO change notifications on this pack’s `state` key path. Alternatively, you + can add an observer for `MGLOfflinePackProgressChangedNotification`s about this + pack that come from the default notification center. + + When a pack resumes after being suspended, it may begin by iterating over the + already downloaded resources. As a result, the `progress` structure’s + `countOfResourcesCompleted` field may revert to 0 before rapidly returning to + the level of progress at the time the pack was suspended. - @param pack The pack whose state of progress changed. - @param progress The updated progress. To get the updated state, refer to the - `state` property. + To temporarily suspend downloading, call the `-suspend` method. */ -- (void)offlinePack:(MGLOfflinePack *)pack progressDidChange:(MGLOfflinePackProgress)progress; +- (void)resume; /** - Sent whenever the pack encounters an error while downloading. + Temporarily stops downloading if the pack is active. - Download errors may be recoverable. For example, this pack’s implementation may - attempt to re-request failed resources based on an exponential backoff - strategy or upon the restoration of network access. + A pack suspends asynchronously. To get notified when this pack resumes, observe + KVO change notifications on this pack’s `state` key path. Alternatively, you + can add an observer for `MGLOfflinePackProgressChangedNotification` about this + pack that come from the default notification center. - @param pack The pack that encountered an error. - @param error A download error. For a list of possible error codes, see - `MGLErrorCode`. - */ -- (void)offlinePack:(MGLOfflinePack *)pack didReceiveError:(NSError *)error; - -/** - Sent when the maximum number of Mapbox-hosted tiles has been downloaded and - stored on the current device. + If the pack previously reached a higher level of progress before being + suspended, it may wait to suspend until it returns to that level. - Once this limit is reached, no instance of `MGLOfflinePack` can download - additional tiles from Mapbox APIs until already downloaded tiles are removed by - calling the `-[MGLOfflineStorage removePack:withCompletionHandler:]` method. - Contact your Mapbox sales representative to have the limit raised. + To resume downloading, call the `-resume` method. */ -- (void)offlinePack:(MGLOfflinePack *)pack didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount; +- (void)suspend; @end diff --git a/platform/darwin/include/MGLOfflineStorage.h b/platform/darwin/include/MGLOfflineStorage.h index 931982dce3..f9ddba0382 100644 --- a/platform/darwin/include/MGLOfflineStorage.h +++ b/platform/darwin/include/MGLOfflineStorage.h @@ -7,22 +7,76 @@ NS_ASSUME_NONNULL_BEGIN @class MGLOfflinePack; @protocol MGLOfflineRegion; +/** + Posted by the shared `MGLOfflineStorage` object when an `MGLOfflinePack` + object’s progress changes. The progress may change due to a resource being + downloaded or because the pack discovers during the download that more + resources are required for offline viewing. This notification is posted + whenever any field in the `progress` property changes. + + The `object` is the `MGLOfflinePack` object whose progress changed. For details + about the pack’s current progress, use the pack’s `progress` property. + + If you only need to observe changes in a particular pack’s progress, you can + alternatively observe KVO change notifications to the pack’s `progress` key + path. + */ extern NSString * const MGLOfflinePackProgressChangedNotification; + +/** + Posted by the shared `MGLOfflineStorage` object whenever an `MGLOfflinePack` + object encounters an error while downloading. The error may be recoverable and + may not warrant the user’s attention. For example, the pack’s implementation + may attempt to re-request failed resources based on an exponential backoff + strategy or upon the restoration of network access. + + The `object` is the `MGLOfflinePack` object that encountered the error. The + `userInfo` dictionary contains the error object in the + `MGLOfflinePackErrorUserInfoKey` key. + */ extern NSString * const MGLOfflinePackErrorNotification; + +/** + Posted by the shared `MGLOfflineStorage` object when the maximum number of + Mapbox-hosted tiles has been downloaded and stored on the current device. + + The `object` is the `MGLOfflinePack` object that reached the tile limit in the + course of downloading. The `userInfo` dictionary contains the tile limit in the + `MGLOfflinePackMaximumCountUserInfoKey` key. + + Once this limit is reached, no instance of `MGLOfflinePack` can download + additional tiles from Mapbox APIs until already downloaded tiles are removed by + calling the `-[MGLOfflineStorage removePack:withCompletionHandler:]` method. + Contact your Mapbox sales representative to have the limit raised. + */ extern NSString * const MGLOfflinePackMaximumMapboxTilesReachedNotification; +/** + The key for an `NSError` object that is encountered in the course of + downloading an offline pack. The error’s domain is `MGLErrorDomain`. See + `MGLErrorCode` for possible error codes. + */ extern NSString * const MGLOfflinePackErrorUserInfoKey; + +/** + The key for an `NSNumber` object that indicates the maximum number of + Mapbox-hosted tiles that may be downloaded and stored on the current device. + Call `-unsignedLongLongValue` on the object to receive the `uint64_t`-typed + tile limit. + */ extern NSString * const MGLOfflinePackMaximumCountUserInfoKey; /** A block to be called once an offline pack has been completely created and added. + An application typically calls the `-resume` method on the pack inside this + completion handler to begin the download. + @param pack Contains a pointer to the newly added pack, or `nil` if there was an error creating or adding the pack. @param error Contains a pointer to an error object (if any) indicating why the - pack could not be created or added. For a list of possible error codes, see - `MGLErrorCode`. + pack could not be created or added. */ typedef void (^MGLOfflinePackAdditionCompletionHandler)(MGLOfflinePack * _Nullable pack, NSError * _Nullable error); @@ -30,6 +84,10 @@ typedef void (^MGLOfflinePackAdditionCompletionHandler)(MGLOfflinePack * _Nullab A block to be called once an offline pack has been completely invalidated and removed. + Avoid any references to the pack inside this completion handler: by the time + this completion handler is executed, the pack has become invalid, and any + messages passed to it will raise an exception. + @param error Contains a pointer to an error object (if any) indicating why the pack could not be invalidated or removed. */ @@ -38,7 +96,8 @@ typedef void (^MGLOfflinePackRemovalCompletionHandler)(NSError * _Nullable error /** MGLOfflineStorage implements a singleton (shared object) that manages offline packs. All of this class’s instance methods are asynchronous, reflecting the - fact that offline resources are stored in a database. + fact that offline resources are stored in a database. The shared object + maintains a canonical collection of offline packs in its `packs` property. */ @interface MGLOfflineStorage : NSObject @@ -48,24 +107,35 @@ typedef void (^MGLOfflinePackRemovalCompletionHandler)(NSError * _Nullable error + (instancetype)sharedOfflineStorage; /** - An array of all known offline packs. + An array of all known offline packs, in the order in which they were created. This property is set to `nil`, indicating that the receiver does not yet know the existing packs, for an undefined amount of time starting from the moment the shared offline storage object is initialized until the packs are fetched from the database. After that point, this property is always non-nil, but it may be empty to indicate that no packs are present. + + To detect when the shared offline storage object has finished loading its + `packs` property, observe KVO change notifications on the `packs` key path. + The initial load results in an `NSKeyValueChangeSetting` change. */ -@property (nonatomic, copy, readonly, nullable) NS_ARRAY_OF(MGLOfflinePack *) *packs; +@property (nonatomic, strong, readonly, nullable) NS_ARRAY_OF(MGLOfflinePack *) *packs; /** Creates and registers an offline pack that downloads the resources needed to use the given region offline. - The resulting pack starts out with a state of `MGLOfflinePackStateInactive`. To - begin downloading resources, call `-[MGLOfflinePack resume]`. To monitor - download progress, set the pack’s `delegate` property to an object that - conforms to the `MGLOfflinePackDelegate` protocol. + The resulting pack is added to the shared offline storage object’s `packs` + property, then the `completion` block is executed with that pack passed in. + + The pack has an initial state of `MGLOfflinePackStateInactive`. To begin + downloading resources, call `-[MGLOfflinePack resume]` on the pack from within + the completion handler. To monitor download progress, add an observer for + `MGLOfflinePackProgressChangedNotification`s about that pack. + + To detect when any call to this method results in a new pack, observe KVO + change notifications on the shared offline storage object’s `packs` key path. + Additions to that array result in an `NSKeyValueChangeInsertion` change. @param region A region to download. @param context Arbitrary data to store alongside the downloaded resources. @@ -81,8 +151,12 @@ typedef void (^MGLOfflinePackRemovalCompletionHandler)(NSError * _Nullable error As soon as this method is called on a pack, the pack becomes invalid; any attempt to send it a message will result in an exception being thrown. If an error occurs and the pack cannot be removed, do not attempt to reuse the pack - object. Instead, use the `-getPacksWithCompletionHandler:` method to obtain a - valid pointer to the pack object. + object. Instead, if you need continued access to the pack, suspend all packs + and use the `-reloadPacks` method to obtain valid pointers to all the packs. + + To detect when any call to this method results in a pack being removed, observe + KVO change notifications on the shared offline storage object’s `packs` key + path. Removals from that array result in an `NSKeyValueChangeRemoval` change. @param pack The offline pack to remove. @param completion The completion handler to call once the pack has been @@ -91,13 +165,28 @@ typedef void (^MGLOfflinePackRemovalCompletionHandler)(NSError * _Nullable error - (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(nullable MGLOfflinePackRemovalCompletionHandler)completion; /** + Forcibly, asynchronously reloads the `packs` property. At some point after this + method is called, the pointer values of the `MGLOfflinePack` objects in the + `packs` property change, even if the underlying data for these packs has not + changed. If this method is called while a pack is actively downloading, the + behavior is undefined. + + You typically do not need to call this method. + + To detect when the shared offline storage object has finished reloading its + `packs` property, observe KVO change notifications on the `packs` key path. + A reload results in an `NSKeyValueChangeSetting` change. + */ +- (void)reloadPacks; + +/** Sets the maximum number of Mapbox-hosted tiles that may be downloaded and stored on the current device. - Once this limit is reached, - `-[MGLOfflinePackDelegate offlinePack:didReceiveMaximumAllowedMapboxTiles:]` is - called on every delegate of `MGLOfflinePack` until already downloaded tiles are - removed by calling the `-removePack:withCompletionHandler:` method. + Once this limit is reached, an + `MGLOfflinePackMaximumMapboxTilesReachedNotification` is posted for every + attempt to download additional tiles until already downloaded tiles are removed + by calling the `-removePack:withCompletionHandler:` method. @note The [Mapbox Terms of Service](https://www.mapbox.com/tos/) prohibits changing or bypassing this limit without permission from Mapbox. Contact diff --git a/platform/darwin/src/MGLOfflinePack.mm b/platform/darwin/src/MGLOfflinePack.mm index 42bd871e54..279261e9be 100644 --- a/platform/darwin/src/MGLOfflinePack.mm +++ b/platform/darwin/src/MGLOfflinePack.mm @@ -38,6 +38,7 @@ private: @interface MGLOfflinePack () +@property (nonatomic, weak, nullable) id <MGLOfflinePackDelegate> delegate; @property (nonatomic, nullable, readwrite) mbgl::OfflineRegion *mbglOfflineRegion; @property (nonatomic, readwrite) MGLOfflinePackState state; @property (nonatomic, readwrite) MGLOfflinePackProgress progress; @@ -66,10 +67,7 @@ private: } - (void)dealloc { - if (_mbglOfflineRegion && _state != MGLOfflinePackStateInvalid) { - mbgl::DefaultFileSource *mbglFileSource = [[MGLOfflineStorage sharedOfflineStorage] mbglFileSource]; - mbglFileSource->setOfflineRegionObserver(*_mbglOfflineRegion, nullptr); - } + NSAssert(_state == MGLOfflinePackStateInvalid, @"MGLOfflinePack was not invalided prior to deallocation."); } - (id <MGLOfflineRegion>)region { @@ -105,6 +103,8 @@ private: NSAssert(_state != MGLOfflinePackStateInvalid, @"Cannot invalidate an already invalid offline pack."); self.state = MGLOfflinePackStateInvalid; + mbgl::DefaultFileSource *mbglFileSource = [[MGLOfflineStorage sharedOfflineStorage] mbglFileSource]; + mbglFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr); self.mbglOfflineRegion = nil; } @@ -159,9 +159,7 @@ private: progress.maximumResourcesExpected = status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX; self.progress = progress; - if ([self.delegate respondsToSelector:@selector(offlinePack:progressDidChange:)]) { - [self.delegate offlinePack:self progressDidChange:progress]; - } + [self.delegate offlinePack:self progressDidChange:progress]; } NSError *MGLErrorFromResponseError(mbgl::Response::Error error) { @@ -187,6 +185,8 @@ NSError *MGLErrorFromResponseError(mbgl::Response::Error error) { }]; } +@end + void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ [pack offlineRegionStatusDidChange:status]; @@ -195,18 +195,12 @@ void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) { dispatch_async(dispatch_get_main_queue(), ^{ - if ([pack.delegate respondsToSelector:@selector(offlinePack:didReceiveError:)]) { - [pack.delegate offlinePack:pack didReceiveError:MGLErrorFromResponseError(error)]; - } + [pack.delegate offlinePack:pack didReceiveError:MGLErrorFromResponseError(error)]; }); } void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) { dispatch_async(dispatch_get_main_queue(), ^{ - if ([pack.delegate respondsToSelector:@selector(offlinePack:didReceiveMaximumAllowedMapboxTiles:)]) { - [pack.delegate offlinePack:pack didReceiveMaximumAllowedMapboxTiles:limit]; - } + [pack.delegate offlinePack:pack didReceiveMaximumAllowedMapboxTiles:limit]; }); } - -@end diff --git a/platform/darwin/src/MGLOfflinePack_Private.h b/platform/darwin/src/MGLOfflinePack_Private.h index c1637ab4a8..4a121af8ad 100644 --- a/platform/darwin/src/MGLOfflinePack_Private.h +++ b/platform/darwin/src/MGLOfflinePack_Private.h @@ -4,8 +4,19 @@ NS_ASSUME_NONNULL_BEGIN +@protocol MGLOfflinePackDelegate; + @interface MGLOfflinePack (Private) +/** + The pack’s delegate. + + You can use the offline pack delegate to be notified of any changes in the + pack’s progress and of any errors while downloading. For more information, see + the `MGLOfflinePackDelegate` documentation. + */ +@property (nonatomic, weak, nullable) id <MGLOfflinePackDelegate> delegate; + @property (nonatomic, nullable) mbgl::OfflineRegion *mbglOfflineRegion; - (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region; @@ -28,4 +39,47 @@ NS_ASSUME_NONNULL_BEGIN @end +/** + The `MGLOfflinePackDelegate` protocol defines methods that a delegate of an + `MGLOfflinePack` object can optionally implement to be notified of any changes + in the pack’s download progress and of any errors while downloading. + */ +@protocol MGLOfflinePackDelegate <NSObject> + +/** + Sent whenever the pack’s state or download progress changes. Every change to a + field in the `progress` property corresponds to an invocation of this method. + + @param pack The pack whose state of progress changed. + @param progress The updated progress. To get the updated state, refer to the + `state` property. + */ +- (void)offlinePack:(MGLOfflinePack *)pack progressDidChange:(MGLOfflinePackProgress)progress; + +/** + Sent whenever the pack encounters an error while downloading. + + Download errors may be recoverable. For example, this pack’s implementation may + attempt to re-request failed resources based on an exponential backoff + strategy or upon the restoration of network access. + + @param pack The pack that encountered an error. + @param error A download error. For a list of possible error codes, see + `MGLErrorCode`. + */ +- (void)offlinePack:(MGLOfflinePack *)pack didReceiveError:(NSError *)error; + +/** + Sent when the maximum number of Mapbox-hosted tiles has been downloaded and + stored on the current device. + + Once this limit is reached, no instance of `MGLOfflinePack` can download + additional tiles from Mapbox APIs until already downloaded tiles are removed by + calling the `-[MGLOfflineStorage removePack:withCompletionHandler:]` method. + Contact your Mapbox sales representative to have the limit raised. + */ +- (void)offlinePack:(MGLOfflinePack *)pack didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount; + +@end + NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLOfflineStorage.mm b/platform/darwin/src/MGLOfflineStorage.mm index 2add5e1051..4e69ba2115 100644 --- a/platform/darwin/src/MGLOfflineStorage.mm +++ b/platform/darwin/src/MGLOfflineStorage.mm @@ -18,19 +18,9 @@ NSString * const MGLOfflinePackMaximumMapboxTilesReachedNotification = @"MGLOffl NSString * const MGLOfflinePackErrorUserInfoKey = @"Error"; NSString * const MGLOfflinePackMaximumCountUserInfoKey = @"MaximumCount"; -/** - A block to be called with a complete list of offline packs. - - @param pack Contains a pointer an array of packs, or `nil` if there was an - error obtaining the packs. - @param error Contains a pointer to an error object (if any) indicating why the - list of packs could not be obtained. - */ -typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePack *) *packs, NSError * _Nullable error); - @interface MGLOfflineStorage () <MGLOfflinePackDelegate> -@property (nonatomic, copy, readwrite) NS_MUTABLE_ARRAY_OF(MGLOfflinePack *) *packs; +@property (nonatomic, strong, readwrite) NS_MUTABLE_ARRAY_OF(MGLOfflinePack *) *packs; @property (nonatomic) mbgl::DefaultFileSource *mbglFileSource; @end @@ -42,14 +32,7 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac static MGLOfflineStorage *sharedOfflineStorage; dispatch_once(&onceToken, ^{ sharedOfflineStorage = [[self alloc] init]; - [sharedOfflineStorage getPacksWithCompletionHandler:^(NS_ARRAY_OF(MGLOfflinePack *) *packs, __unused NSError *error) { - sharedOfflineStorage.packs = [packs mutableCopy]; - - for (MGLOfflinePack *pack in packs) { - pack.delegate = sharedOfflineStorage; - [pack requestProgress]; - } - }]; + [sharedOfflineStorage reloadPacks]; }); return sharedOfflineStorage; } @@ -121,12 +104,14 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac - (void)dealloc { [[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"]; + for (MGLOfflinePack *pack in self.packs) { + [pack invalidate]; + } + delete _mbglFileSource; _mbglFileSource = nullptr; } -#pragma mark KVO methods - - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS_DICTIONARY_OF(NSString *, id) *)change context:(void *)context { // Synchronize the file source’s access token with the global one in MGLAccountManager. if ([keyPath isEqualToString:@"accessToken"] && object == [MGLAccountManager sharedManager]) { @@ -139,29 +124,13 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac } } -- (void)insertPacks:(NSArray *)packs atIndexes:(NSIndexSet *)indices { - [(NSMutableArray *)self.packs insertObjects:packs atIndexes:indices]; -} - -- (void)addPacksObject:(MGLOfflinePack *)pack { - [(NSMutableArray *)self.packs addObject:pack]; -} - -- (void)removePacksAtIndexes:(NSIndexSet *)indices { - [(NSMutableArray *)self.packs removeObjectsAtIndexes:indices]; -} - -- (void)removePacksObject:(MGLOfflinePack *)pack { - [(NSMutableArray *)self.packs removeObject:pack]; -} - #pragma mark Pack management methods - (void)addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)context completionHandler:(MGLOfflinePackAdditionCompletionHandler)completion { __weak MGLOfflineStorage *weakSelf = self; [self _addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) { MGLOfflineStorage *strongSelf = weakSelf; - [strongSelf addPacksObject:pack]; + [[strongSelf mutableArrayValueForKey:@"packs"] addObject:pack]; pack.delegate = strongSelf; [pack requestProgress]; if (completion) { @@ -198,7 +167,7 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac } - (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePackRemovalCompletionHandler)completion { - [self removePacksObject:pack]; + [[self mutableArrayValueForKey:@"packs"] removeObject:pack]; [self _removePack:pack withCompletionHandler:^(NSError * _Nullable error) { if (completion) { completion(error); @@ -224,7 +193,18 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac }); } -- (void)getPacksWithCompletionHandler:(MGLOfflinePackListingCompletionHandler)completion { +- (void)reloadPacks { + [self getPacksWithCompletionHandler:^(NS_ARRAY_OF(MGLOfflinePack *) *packs, __unused NSError * _Nullable error) { + self.packs = [packs mutableCopy]; + + for (MGLOfflinePack *pack in packs) { + pack.delegate = self; + [pack requestProgress]; + } + }]; +} + +- (void)getPacksWithCompletionHandler:(void (^)(NS_ARRAY_OF(MGLOfflinePack *) *packs, NSError * _Nullable error))completion { self.mbglFileSource->listOfflineRegions([&, completion](std::exception_ptr exception, mbgl::optional<std::vector<mbgl::OfflineRegion>> regions) { NSError *error; if (exception) { diff --git a/platform/ios/app/MBXOfflinePacksTableViewController.m b/platform/ios/app/MBXOfflinePacksTableViewController.m index 4f399d3d4f..f4a2917d60 100644 --- a/platform/ios/app/MBXOfflinePacksTableViewController.m +++ b/platform/ios/app/MBXOfflinePacksTableViewController.m @@ -28,9 +28,7 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac @end -@implementation MBXOfflinePacksTableViewController { - NSUInteger _untitledRegionCount; -} +@implementation MBXOfflinePacksTableViewController - (void)viewDidLoad { [super viewDidLoad]; @@ -81,16 +79,19 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac - (IBAction)addCurrentRegion:(id)sender { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Add Offline Pack" message:@"Choose a name for the pack:" preferredStyle:UIAlertControllerStyleAlert]; - [alertController addTextFieldWithConfigurationHandler:nil]; + [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { + textField.placeholder = [NSString stringWithFormat:@"%@", MGLStringFromCoordinateBounds(self.mapView.visibleCoordinateBounds)]; + }]; [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; UIAlertAction *downloadAction = [UIAlertAction actionWithTitle:@"Download" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { MGLMapView *mapView = self.mapView; NSAssert(mapView, @"No map view to get the current region from."); - NSString *name = alertController.textFields.firstObject.text; + UITextField *nameField = alertController.textFields.firstObject; + NSString *name = nameField.text; if (!name.length) { - name = [NSString stringWithFormat:@"Untitled %lu", (unsigned long)++_untitledRegionCount]; + name = nameField.placeholder; } MGLTilePyramidOfflineRegion *region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:mapView.styleURL bounds:mapView.visibleCoordinateBounds fromZoomLevel:mapView.zoomLevel toZoomLevel:mapView.maximumZoomLevel]; @@ -98,14 +99,14 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac MBXOfflinePackContextNameKey: name, }]; - __weak MBXOfflinePacksTableViewController *weakSelf = self; [[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack *pack, NSError *error) { - MBXOfflinePacksTableViewController *strongSelf = weakSelf; if (error) { NSString *message = [NSString stringWithFormat:@"Mapbox GL was unable to add the offline pack “%@”.", name]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Can’t Add Offline Pack" message:message preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; + } else { + [pack resume]; } }]; }]; @@ -128,6 +129,12 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac NSString *reuseIdentifier = pack.state == MGLOfflinePackStateActive ? MBXOfflinePacksTableViewActiveCellReuseIdentifier : MBXOfflinePacksTableViewInactiveCellReuseIdentifier; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath]; + [self updateTableViewCell:cell atIndexPath:indexPath forPack:pack]; + + return cell; +} + +- (void)updateTableViewCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath forPack:(MGLOfflinePack *)pack { cell.textLabel.text = pack.name; MGLOfflinePackProgress progress = pack.progress; NSString *completedString = [NSNumberFormatter localizedStringFromNumber:@(progress.countOfResourcesCompleted) @@ -169,8 +176,6 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac break; } cell.detailTextLabel.text = statusString; - - return cell; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { @@ -199,12 +204,10 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac case MGLOfflinePackStateInactive: [pack resume]; - [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case MGLOfflinePackStateActive: [pack suspend]; - [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case MGLOfflinePackStateInvalid: @@ -225,7 +228,8 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac } NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; - [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + [self updateTableViewCell:cell atIndexPath:indexPath forPack:pack]; } - (void)offlinePackDidReceiveError:(NSNotification *)notification { |