DemoListViewController.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*
  2. * Copyright 2016 Google LLC. All rights reserved.
  3. *
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  6. * file except in compliance with the License. You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software distributed under
  11. * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  12. * ANY KIND, either express or implied. See the License for the specific language governing
  13. * permissions and limitations under the License.
  14. */
  15. #import "GooglePlacesDemos/DemoListViewController.h"
  16. #import <GooglePlaces/GooglePlaces.h>
  17. // The cell reuse identifier we are going to use.
  18. static NSString *gOverrideVersion = nil;
  19. static NSString *const kCellIdentifier = @"DemoCellIdentifier";
  20. static const CGFloat kSelectionHeight = 40;
  21. static const CGFloat kSelectionSwitchWidth = 50;
  22. static const CGFloat kEdgeBuffer = 8;
  23. @implementation DemoListViewController {
  24. UIViewController *_editSelectionsViewController;
  25. NSMutableDictionary<NSNumber *, UISwitch *> *_autocompleteFiltersSelectionMap;
  26. NSMutableDictionary<NSNumber *, UISwitch *> *_placeFieldsSelectionMap;
  27. NSMutableDictionary<NSString *, UISwitch *> *_restrictionBoundsMap;
  28. CGFloat _nextSelectionYPos;
  29. DemoData *_demoData;
  30. }
  31. - (instancetype)initWithDemoData:(DemoData *)demoData {
  32. if ((self = [self init])) {
  33. _demoData = demoData;
  34. _autocompleteFiltersSelectionMap = [NSMutableDictionary dictionary];
  35. _placeFieldsSelectionMap = [NSMutableDictionary dictionary];
  36. _restrictionBoundsMap = [NSMutableDictionary dictionary];
  37. }
  38. return self;
  39. }
  40. - (void)viewWillAppear:(BOOL)animated {
  41. // Set up the title for view to be displayed.
  42. self.title = [DemoListViewController titleText];
  43. [super viewWillAppear:animated];
  44. }
  45. - (void)viewWillDisappear:(BOOL)animated {
  46. // Clear the title to make room for next view to share the header space in splitscreen view.
  47. self.title = nil;
  48. [super viewWillDisappear:animated];
  49. }
  50. - (void)viewDidLoad {
  51. [super viewDidLoad];
  52. UINavigationBar *navBar = self.navigationController.navigationBar;
  53. if (@available(iOS 13, *)) {
  54. UINavigationBarAppearance *navBarAppearance = [[UINavigationBarAppearance alloc] init];
  55. [navBarAppearance configureWithOpaqueBackground];
  56. navBarAppearance.backgroundColor = [UIColor systemBackgroundColor];
  57. [navBarAppearance
  58. setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor labelColor]}];
  59. navBar.standardAppearance = navBarAppearance;
  60. navBar.scrollEdgeAppearance = navBarAppearance;
  61. } else {
  62. navBar.translucent = NO;
  63. }
  64. [self setUpEditSelectionsUI];
  65. // Add button to the header to edit the place field selections.
  66. self.navigationItem.rightBarButtonItem =
  67. [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit
  68. target:self
  69. action:@selector(beginEditSelections)];
  70. // Register a plain old UITableViewCell as this will be sufficient for our list.
  71. [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellIdentifier];
  72. // [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
  73. [[NSNotificationCenter defaultCenter] addObserver:self
  74. selector:@selector(orientationChanged:)
  75. name:UIDeviceOrientationDidChangeNotification
  76. object:[UIDevice currentDevice]];
  77. }
  78. /**
  79. * Private method which is called when a demo is selected. Constructs the demo view controller and
  80. * displays it.
  81. *
  82. * @param demo The demo to show.
  83. */
  84. - (void)showDemo:(Demo *)demo {
  85. CLLocationCoordinate2D northEast = kCLLocationCoordinate2DInvalid;
  86. CLLocationCoordinate2D southWest = kCLLocationCoordinate2DInvalid;
  87. GMSAutocompleteFilter *autocompleteFilter = [self autocompleteFilter];
  88. // Check for restriction bounds settings.
  89. if (_restrictionBoundsMap[@"Kansas"].on) {
  90. northEast = CLLocationCoordinate2DMake(39.0, -95.0);
  91. southWest = CLLocationCoordinate2DMake(37.5, -100.0);
  92. autocompleteFilter.origin = [[CLLocation alloc] initWithLatitude:northEast.latitude
  93. longitude:northEast.longitude];
  94. autocompleteFilter.locationRestriction =
  95. GMSPlaceRectangularLocationOption(northEast, southWest);
  96. } else if (_restrictionBoundsMap[@"Canada"].on) {
  97. northEast = CLLocationCoordinate2DMake(70.0, -60.0);
  98. southWest = CLLocationCoordinate2DMake(50.0, -140.0);
  99. autocompleteFilter.origin = [[CLLocation alloc] initWithLatitude:northEast.latitude
  100. longitude:northEast.longitude];
  101. autocompleteFilter.locationRestriction =
  102. GMSPlaceRectangularLocationOption(northEast, southWest);
  103. }
  104. // Create view controller with the autocomplete filters, bounds and selected place fields.
  105. UIViewController *viewController =
  106. [demo createViewControllerWithAutocompleteFilter:autocompleteFilter
  107. placeFields:[self selectedPlaceFields]];
  108. [self.navigationController pushViewController:viewController animated:YES];
  109. }
  110. #pragma mark - Edit Autocomplete Filters and Place Fields selections UI
  111. - (void)setUpEditSelectionsUI {
  112. // Initialize the place fields selection UI.
  113. UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
  114. #if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
  115. if (@available(iOS 13.0, *)) {
  116. scrollView.backgroundColor = [UIColor systemBackgroundColor];
  117. } else {
  118. scrollView.backgroundColor = [UIColor whiteColor];
  119. }
  120. #else
  121. scrollView.backgroundColor = [UIColor whiteColor];
  122. #endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
  123. // Add heading for the autocomplete type filters.
  124. _nextSelectionYPos = [UIApplication sharedApplication].statusBarFrame.size.height;
  125. [scrollView addSubview:[self headerLabelForTitle:@"Autocomplete Filters"]];
  126. // Set up the individual autocomplete type filters we can limit the results to.
  127. // Add a heading for the place fields that we can request.
  128. _nextSelectionYPos += kSelectionHeight;
  129. for (NSInteger autocompleteFilterType = kGMSPlacesAutocompleteTypeFilterGeocode;
  130. autocompleteFilterType <= kGMSPlacesAutocompleteTypeFilterCity; ++autocompleteFilterType) {
  131. [scrollView
  132. addSubview:[self selectionButtonForAutocompleteFilterType:(GMSPlacesAutocompleteTypeFilter)
  133. autocompleteFilterType]];
  134. }
  135. // Add heading for the autocomplete restriction bounds.
  136. [scrollView addSubview:[self headerLabelForTitle:@"Autocomplete Restriction Bounds"]];
  137. // Set up the restriction bounds for testing purposes.
  138. _nextSelectionYPos += kSelectionHeight;
  139. [scrollView addSubview:[self selectionButtonForRestrictionBoundsArea:@"Canada"]];
  140. _nextSelectionYPos += kSelectionHeight;
  141. [scrollView addSubview:[self selectionButtonForRestrictionBoundsArea:@"Kansas"]];
  142. // Add heading for the place fields that we can request.
  143. _nextSelectionYPos += kSelectionHeight;
  144. [scrollView addSubview:[self headerLabelForTitle:@"Place Fields"]];
  145. // Set up the individual place fields that we can request.
  146. _nextSelectionYPos += kSelectionHeight;
  147. for (NSUInteger placeField = GMSPlaceFieldName; placeField <= GMSPlaceFieldIconBackgroundColor;
  148. placeField <<= 1) {
  149. [scrollView addSubview:[self selectionButtonForPlaceField:(GMSPlaceField)placeField]];
  150. }
  151. // Add the close button to dismiss the selection UI.
  152. UIButton *close =
  153. [[UIButton alloc] initWithFrame:CGRectMake(0, _nextSelectionYPos, self.view.frame.size.width,
  154. kSelectionHeight)];
  155. close.backgroundColor = [UIColor blueColor];
  156. [close setTitle:@"Close" forState:UIControlStateNormal];
  157. [close setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
  158. [close addTarget:self
  159. action:@selector(endEditSelections:)
  160. forControlEvents:UIControlEventTouchUpInside];
  161. [scrollView addSubview:close];
  162. // Set the content size for the scroll view after we determine the height of all the contents.
  163. scrollView.contentSize = CGSizeMake(scrollView.frame.size.width,
  164. close.frame.origin.y + close.frame.size.height + kEdgeBuffer);
  165. // Initialize the edit selections view controller.
  166. _editSelectionsViewController = [[UIViewController alloc] init];
  167. _editSelectionsViewController.view = scrollView;
  168. }
  169. - (void)updateFrameForSelectionViews:(UIView *)view {
  170. CGFloat horizontalInset = [self horizontalInset];
  171. view.frame =
  172. CGRectMake(horizontalInset, view.frame.origin.y,
  173. self.view.frame.size.width - (2 * horizontalInset), view.frame.size.height);
  174. for (UIView *subView in view.subviews) {
  175. if ([subView isKindOfClass:[UISwitch class]]) {
  176. subView.frame =
  177. CGRectMake(view.frame.size.width - kSelectionSwitchWidth, subView.frame.origin.y,
  178. subView.frame.size.width, subView.frame.size.height);
  179. }
  180. }
  181. }
  182. - (UILabel *)headerLabelForTitle:(NSString *)title {
  183. UILabel *headerLabel =
  184. [[UILabel alloc] initWithFrame:CGRectMake(0, _nextSelectionYPos, self.view.frame.size.width,
  185. kSelectionHeight)];
  186. headerLabel.backgroundColor = [UIColor lightGrayColor];
  187. headerLabel.text = title;
  188. headerLabel.textAlignment = NSTextAlignmentCenter;
  189. headerLabel.textColor = [UIColor whiteColor];
  190. headerLabel.userInteractionEnabled = NO;
  191. return headerLabel;
  192. }
  193. - (UIButton *)selectionButtonForTitle:(NSString *)title {
  194. UIButton *selectionButton =
  195. [[UIButton alloc] initWithFrame:CGRectMake(0, _nextSelectionYPos, self.view.frame.size.width,
  196. kSelectionHeight)];
  197. UISwitch *selectionSwitch = [[UISwitch alloc]
  198. initWithFrame:CGRectMake(selectionButton.frame.size.width - kSelectionSwitchWidth,
  199. kEdgeBuffer / 2, kSelectionSwitchWidth, kSelectionHeight)];
  200. selectionSwitch.userInteractionEnabled = NO;
  201. [selectionButton addTarget:self
  202. action:@selector(selectionButtonTapped:)
  203. forControlEvents:UIControlEventTouchUpInside];
  204. [selectionButton setBackgroundColor:[UIColor whiteColor]];
  205. [selectionButton setTitle:title forState:UIControlStateNormal];
  206. [selectionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  207. selectionButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
  208. [selectionButton addSubview:selectionSwitch];
  209. return selectionButton;
  210. }
  211. - (UIButton *)selectionButtonForPlaceField:(GMSPlaceField)placeField {
  212. NSDictionary<NSNumber *, NSString *> *fieldsMapping = @{
  213. @(GMSPlaceFieldName) : @"Name",
  214. @(GMSPlaceFieldPlaceID) : @"Place ID",
  215. @(GMSPlaceFieldPlusCode) : @"Plus Code",
  216. @(GMSPlaceFieldCoordinate) : @"Coordinate",
  217. @(GMSPlaceFieldOpeningHours) : @"Opening Hours",
  218. @(GMSPlaceFieldPhoneNumber) : @"Phone Number",
  219. @(GMSPlaceFieldFormattedAddress) : @"Formatted Address",
  220. @(GMSPlaceFieldRating) : @"Rating",
  221. @(GMSPlaceFieldUserRatingsTotal) : @"User Ratings Total",
  222. @(GMSPlaceFieldPriceLevel) : @"Price Level",
  223. @(GMSPlaceFieldTypes) : @"Types",
  224. @(GMSPlaceFieldWebsite) : @"Website",
  225. @(GMSPlaceFieldViewport) : @"Viewport",
  226. @(GMSPlaceFieldAddressComponents) : @"Address Components",
  227. @(GMSPlaceFieldPhotos) : @"Photos",
  228. @(GMSPlaceFieldUTCOffsetMinutes) : @"UTC Offset Minutes",
  229. @(GMSPlaceFieldBusinessStatus) : @"Business Status",
  230. @(GMSPlaceFieldIconImageURL) : @"Icon Image URL",
  231. @(GMSPlaceFieldIconBackgroundColor) : @"Icon Background Color",
  232. };
  233. UIButton *selectionButton = [self selectionButtonForTitle:fieldsMapping[@(placeField)]];
  234. UISwitch *selectionSwitch = [self switchFromButton:selectionButton];
  235. [selectionSwitch setOn:YES];
  236. _placeFieldsSelectionMap[@(placeField)] = selectionSwitch;
  237. _nextSelectionYPos += selectionButton.frame.size.height;
  238. return selectionButton;
  239. }
  240. - (UIButton *)selectionButtonForAutocompleteFilterType:
  241. (GMSPlacesAutocompleteTypeFilter)autocompleteFilter {
  242. NSDictionary<NSNumber *, NSString *> *fieldsMapping = @{
  243. @(kGMSPlacesAutocompleteTypeFilterGeocode) : @"Geocode",
  244. @(kGMSPlacesAutocompleteTypeFilterAddress) : @"Address",
  245. @(kGMSPlacesAutocompleteTypeFilterEstablishment) : @"Establishment",
  246. @(kGMSPlacesAutocompleteTypeFilterRegion) : @"Region",
  247. @(kGMSPlacesAutocompleteTypeFilterCity) : @"City",
  248. };
  249. UIButton *selectionButton = [self selectionButtonForTitle:fieldsMapping[@(autocompleteFilter)]];
  250. [selectionButton addTarget:self
  251. action:@selector(disableOtherAutocompleteFilterExceptForTapped:)
  252. forControlEvents:UIControlEventTouchUpInside];
  253. UISwitch *selectionSwitch = [self switchFromButton:selectionButton];
  254. [selectionSwitch setOn:NO];
  255. _autocompleteFiltersSelectionMap[@(autocompleteFilter)] = selectionSwitch;
  256. _nextSelectionYPos += selectionButton.frame.size.height;
  257. return selectionButton;
  258. }
  259. - (UIButton *)selectionButtonForRestrictionBoundsArea:(NSString *)area {
  260. UIButton *selectionButton = [self selectionButtonForTitle:area];
  261. [selectionButton addTarget:self
  262. action:@selector(disableOtherRestrictionBoundsExceptForTapped:)
  263. forControlEvents:UIControlEventTouchUpInside];
  264. _restrictionBoundsMap[area] = [self switchFromButton:selectionButton];
  265. [_restrictionBoundsMap[area] setOn:NO];
  266. return selectionButton;
  267. }
  268. - (UISwitch *)switchFromButton:(UIButton *)button {
  269. for (UIView *subView in button.subviews) {
  270. if ([subView isKindOfClass:[UISwitch class]]) {
  271. return (UISwitch *)subView;
  272. }
  273. }
  274. return nil;
  275. }
  276. - (void)selectionButtonTapped:(UIButton *)sender {
  277. UISwitch *selectionSwitch = [self switchFromButton:sender];
  278. [selectionSwitch setOn:selectionSwitch.on ? NO : YES animated:YES];
  279. }
  280. - (void)disableOtherAutocompleteFilterExceptForTapped:(UIButton *)sender {
  281. UISwitch *tappedSwitch = [self switchFromButton:sender];
  282. for (NSNumber *number in _autocompleteFiltersSelectionMap) {
  283. UISwitch *selectionSwitch = _autocompleteFiltersSelectionMap[number];
  284. if (selectionSwitch != tappedSwitch) {
  285. [selectionSwitch setOn:NO animated:YES];
  286. }
  287. }
  288. }
  289. - (void)disableOtherRestrictionBoundsExceptForTapped:(UIButton *)sender {
  290. UISwitch *tappedSwitch = [self switchFromButton:sender];
  291. for (NSString *key in [_restrictionBoundsMap allKeys]) {
  292. UISwitch *selectionSwitch = _restrictionBoundsMap[key];
  293. if (selectionSwitch != tappedSwitch) {
  294. [selectionSwitch setOn:NO animated:YES];
  295. }
  296. }
  297. }
  298. - (void)beginEditSelections {
  299. // Update the selection views to fit the current device frame and orientation.
  300. for (UIView *view in _editSelectionsViewController.view.subviews) {
  301. [self updateFrameForSelectionViews:view];
  302. }
  303. // Scroll the contents to the top before presenting the selection UI.
  304. UIScrollView *scrollView = (UIScrollView *)_editSelectionsViewController.view;
  305. [scrollView setContentOffset:CGPointZero animated:NO];
  306. // Present the selection UI to edit which place fields to request.
  307. [self.navigationController presentViewController:_editSelectionsViewController
  308. animated:YES
  309. completion:nil];
  310. }
  311. - (void)endEditSelections:(UIButton *)sender {
  312. [_editSelectionsViewController dismissViewControllerAnimated:YES completion:nil];
  313. }
  314. - (GMSAutocompleteFilter *)autocompleteFilter {
  315. GMSAutocompleteFilter *filter = [[GMSAutocompleteFilter alloc] init];
  316. for (NSNumber *number in _autocompleteFiltersSelectionMap) {
  317. UISwitch *selectionSwitch = _autocompleteFiltersSelectionMap[number];
  318. if ([selectionSwitch isOn]) {
  319. filter.type = (GMSPlacesAutocompleteTypeFilter)[number integerValue];
  320. break;
  321. }
  322. }
  323. return filter;
  324. }
  325. - (GMSPlaceField)selectedPlaceFields {
  326. GMSPlaceField placeFields = 0;
  327. for (NSNumber *number in _placeFieldsSelectionMap) {
  328. UISwitch *selectionSwitch = _placeFieldsSelectionMap[number];
  329. if ([selectionSwitch isOn]) {
  330. placeFields |= [number integerValue];
  331. }
  332. }
  333. return placeFields;
  334. }
  335. - (CGFloat)horizontalInset {
  336. // Take into account the safe areas of the device screen and do not use that space.
  337. return MAX(self.view.safeAreaInsets.left, self.view.safeAreaInsets.right) + kEdgeBuffer;
  338. }
  339. #pragma mark - UITableViewDataSource/Delegate
  340. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  341. return _demoData.sections.count;
  342. }
  343. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  344. return _demoData.sections[section].demos.count;
  345. }
  346. - (UITableViewCell *)tableView:(UITableView *)tableView
  347. cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  348. // Dequeue a table view cell to use.
  349. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier
  350. forIndexPath:indexPath];
  351. // Grab the demo object.
  352. Demo *demo = _demoData.sections[indexPath.section].demos[indexPath.row];
  353. // Configure the demo title on the cell.
  354. cell.textLabel.text = demo.title;
  355. return cell;
  356. }
  357. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
  358. return _demoData.sections[section].title;
  359. }
  360. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  361. // Get the demo which was selected.
  362. Demo *demo = _demoData.sections[indexPath.section].demos[indexPath.row];
  363. [self showDemo:demo];
  364. }
  365. + (NSString *)overrideVersion {
  366. return [[[NSProcessInfo processInfo] environment] objectForKey:@"PLACES_VERSION_NUMBER_OVERRIDE"];
  367. }
  368. + (NSString *)titleText {
  369. NSString *titleFormat = NSLocalizedString(
  370. @"App.NameAndVersion", @"The name of the app to display in a navigation bar along with a "
  371. @"placeholder for the SDK version number");
  372. return [NSString
  373. stringWithFormat:titleFormat, [self overrideVersion] ?: [GMSPlacesClient SDKLongVersion]];
  374. }
  375. #pragma mark - Handle Orientation Changes
  376. - (void)orientationChanged:(NSNotification *)notification {
  377. // Dismiss the selections UI if currently active.
  378. if (_editSelectionsViewController.isViewLoaded && _editSelectionsViewController.view.window) {
  379. [self endEditSelections:nil];
  380. }
  381. }
  382. @end