├── TODocumentPickerViewControllerExample ├── ja.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── TOViewController.h ├── TOAppDelegate.h ├── TODocumentsDataSource.h ├── main.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── TOViewController.m ├── TOAppDelegate.m └── TODocumentsDataSource.m ├── .github └── FUNDING.yml ├── TOSearchBar ├── ko.lproj │ └── TOSearchBarLocalizable.strings ├── ja.lproj │ └── TOSearchBarLocalizable.strings ├── zh-Hans.lproj │ └── TOSearchBarLocalizable.strings ├── zh-Hant.lproj │ └── TOSearchBarLocalizable.strings ├── Base.lproj │ └── TOSearchBarLocalizable.strings ├── da.lproj │ └── TOSearchBarLocalizable.strings ├── en.lproj │ └── TOSearchBarLocalizable.strings ├── es.lproj │ └── TOSearchBarLocalizable.strings ├── it.lproj │ └── TOSearchBarLocalizable.strings ├── nl.lproj │ └── TOSearchBarLocalizable.strings ├── pl.lproj │ └── TOSearchBarLocalizable.strings ├── ru.lproj │ └── TOSearchBarLocalizable.strings ├── de.lproj │ └── TOSearchBarLocalizable.strings ├── fr.lproj │ └── TOSearchBarLocalizable.strings ├── TOSearchBar+Assets.h ├── TOSearchBar.h ├── TOSearchBar+Assets.m └── TOSearchBar.m ├── screenshot.jpg ├── screenshot2016.jpg ├── Design └── FileIcons.pcvd ├── TODocumentPickerViewControllerExample.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── TODocumentPickerViewController.podspec ├── LICENSE ├── CHANGELOG.md ├── TODocumentPickerViewController ├── Models │ ├── TODocumentPickerTheme.m │ ├── TODocumentPickerItem.h │ ├── TODocumentPickerLocalDiskDataSource.h │ ├── TODocumentPickerConstants.m │ ├── TODocumentPickerTheme.h │ ├── TODocumentPickerItemManager.h │ ├── TODocumentPickerConfiguration.m │ ├── TODocumentPickerConfiguration.h │ ├── TODocumentPickerItem.m │ ├── TODocumentPickerLocalDiskDataSource.m │ ├── TODocumentPickerConstants.h │ └── TODocumentPickerItemManager.m ├── Views │ ├── TODocumentPickerTableView.h │ ├── TODocumentPickerSegmentedControl.h │ ├── TODocumentPickerTableViewCell.h │ ├── TODocumentPickerTableView.m │ ├── TODocumentPickerHeaderView.h │ ├── TODocumentPickerTableViewCell.m │ ├── TODocumentPickerHeaderView.m │ └── TODocumentPickerSegmentedControl.m ├── Categories │ └── UIImage+TODocumentPickerIcons.h └── TODocumentPickerViewController.h ├── .gitignore └── README.md /TODocumentPickerViewControllerExample/ja.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: timoliver 2 | custom: https://tim.dev/paypal 3 | -------------------------------------------------------------------------------- /TOSearchBar/ko.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "검색"; 2 | "Cancel" = "취소"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/ja.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "検索"; 2 | "Cancel" = "キャンセル"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/zh-Hans.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "搜索"; 2 | "Cancel" = "取消"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/zh-Hant.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "搜尋"; 2 | "Cancel" = "取消"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/Base.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Search"; 2 | "Cancel" = "Cancel"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/da.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Søg"; 2 | "Cancel" = "Annuller"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/en.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Search"; 2 | "Cancel" = "Cancel"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/es.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Buscar"; 2 | "Cancel" = "Cancelar"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/it.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Cerca"; 2 | "Cancel" = "Annulla"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/nl.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Zoek"; 2 | "Cancel" = "Annuleer"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/pl.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Szukaj"; 2 | "Cancel" = "Anuluj"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/ru.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Поиск"; 2 | "Cancel" = "Отменить"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/de.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Suchen"; 2 | "Cancel" = "Abbrechen"; 3 | -------------------------------------------------------------------------------- /TOSearchBar/fr.lproj/TOSearchBarLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Search" = "Recherche"; 2 | "Cancel" = "Annuler"; 3 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TODocumentPickerViewController/HEAD/screenshot.jpg -------------------------------------------------------------------------------- /screenshot2016.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TODocumentPickerViewController/HEAD/screenshot2016.jpg -------------------------------------------------------------------------------- /Design/FileIcons.pcvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TODocumentPickerViewController/HEAD/Design/FileIcons.pcvd -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/ja.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "IBUIButton"; normalTitle = "Show Document Picker"; ObjectID = "G6f-9A-QZJ"; */ 3 | "G6f-9A-QZJ.normalTitle" = "Show Document Picker"; 4 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/TOViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TODocumentPickerViewControllerExample 4 | // 5 | // Created by Tim Oliver on 12/31/14. 6 | // Copyright (c) 2014 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TOViewController : UIViewController 12 | 13 | - (IBAction)buttonTapped:(id)sender; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/TOAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TODocumentPickerViewControllerExample 4 | // 5 | // Created by Tim Oliver on 12/31/14. 6 | // Copyright (c) 2014 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TOAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/TODocumentsDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentsDataSource.h 3 | // TODocumentPickerViewControllerExample 4 | // 5 | // Created by Tim Oliver on 1/1/15. 6 | // Copyright (c) 2015 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TODocumentPickerViewController.h" 11 | 12 | @interface TODocumentsDataSource : NSObject 13 | @end 14 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TODocumentPickerViewControllerExample 4 | // 5 | // Created by Tim Oliver on 12/31/14. 6 | // Copyright (c) 2014 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TOAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([TOAppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TODocumentPickerViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'TODocumentPickerViewController' 3 | spec.version = '0.2.2' 4 | spec.platform = :ios, '9.0' 5 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 6 | spec.homepage = 'https://github.com/TimOliver/TODocumentPickerViewController' 7 | spec.authors = { 'Tim Oliver' => 'info@timoliver.com.au' } 8 | spec.summary = 'A view controller for interacting with file systems on iOS.' 9 | spec.source = { :git => 'https://github.com/TimOliver/TODocumentPickerViewController.git', :tag => spec.version } 10 | spec.source_files = 'TODocumentPickerViewController/**/*.{h,m}' 11 | spec.dependency 'TOSearchBar' 12 | end 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Timothy Oliver. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 0.2.1 - 2017-12-30 10 | 11 | ### Fixed 12 | - Table header view not being properly obscured under the navigation bar on iPhone X. 13 | - Properly aligned the text label in the toolbar at the bottom. 14 | 15 | ## 0.2.0 - 2017-12-30 16 | 17 | ### Added 18 | - New icon design to align with iOS 11's new overall feel. 19 | - A system to automatically generate icons for specific file formats. 20 | 21 | ### Changed 22 | - Changed theme settings from `NSDictionary` to a dedicated object with properties. 23 | - Removed the alphabetical sectioning of the files list in favour of integrating `TOSearchBar` in a later release. 24 | 25 | ### Fixed 26 | - Fixed scrolling inset bugs introduced due to iOS 11. 27 | - Fixed header view insetting behaviour to be more consistent, especially when the refresh control is visible. 28 | 29 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerTheme.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerTheme.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerTheme.h" 24 | 25 | @implementation TODocumentPickerTheme 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Views/TODocumentPickerTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerTableView.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | @interface TODocumentPickerTableView : UITableView 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /TODocumentPickerViewController/Views/TODocumentPickerSegmentedControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerSegmentedControl.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import "TODocumentPickerConstants.h" 25 | 26 | @interface TODocumentPickerSegmentedControl : UISegmentedControl 27 | 28 | @property (nonatomic, assign) TODocumentPickerSortType sortingType; 29 | @property (nonatomic, copy) void (^sortTypeChangedHandler)(void); 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/Preview.html 54 | fastlane/screenshots 55 | fastlane/test_output 56 | 57 | # Code Injection 58 | # 59 | # After new code Injection tools there's a generated folder /iOSInjectionProject 60 | # https://github.com/johnno1962/injectionforxcode 61 | 62 | iOSInjectionProject/ 63 | .DS_Store 64 | -------------------------------------------------------------------------------- /TOSearchBar/TOSearchBar+Assets.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOSearchBar+ImageAssets.h 3 | // 4 | // Copyright 2015-2016 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOSearchBar.h" 24 | 25 | @interface TOSearchBar (ImageAssets) 26 | 27 | + (UIImage *)sharedSearchBarBackground; /* The rounded rectangle for the background */ 28 | + (UIImage *)sharedSearchIcon; /* The magnifying glads icon */ 29 | + (UIImage *)sharedClearIcon; /* The clear button icon. */ 30 | 31 | + (void)cleanUpSharedAssets; /* Called upon each `dealloc` method call to clean up any freed assets. */ 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/TOViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TODocumentPickerViewControllerExample 4 | // 5 | // Created by Tim Oliver on 12/31/14. 6 | // Copyright (c) 2014 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "TOViewController.h" 10 | #import "TODocumentPickerViewController.h" 11 | #import "TODocumentsDataSource.h" 12 | 13 | @interface TOViewController () 14 | 15 | @end 16 | 17 | @implementation TOViewController 18 | 19 | - (IBAction)buttonTapped:(id)sender 20 | { 21 | TODocumentPickerViewController *documentPicker = [[TODocumentPickerViewController alloc] initWithFilePath:nil]; 22 | documentPicker.dataSource = [[TODocumentsDataSource alloc] init]; 23 | documentPicker.documentPickerDelegate = self; 24 | 25 | UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:documentPicker]; 26 | controller.modalPresentationStyle = UIModalPresentationFormSheet; 27 | 28 | [self presentViewController:controller animated:YES completion:nil]; 29 | } 30 | 31 | - (void)documentPickerViewController:(TODocumentPickerViewController *)documentPicker didSelectItems:(nonnull NSArray *)items inFilePath:(NSString *)filePath 32 | { 33 | [self dismissViewControllerAnimated:YES completion:nil]; 34 | 35 | NSMutableArray *absoluteItemPaths = [NSMutableArray array]; 36 | for (TODocumentPickerItem *item in items) { 37 | NSString *absoluteFilePath = [filePath stringByAppendingPathComponent:item.fileName]; 38 | [absoluteItemPaths addObject:absoluteFilePath]; 39 | } 40 | 41 | NSLog(@"Paths for items selected: %@", absoluteItemPaths); 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Views/TODocumentPickerTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerTableViewCell.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import "TODocumentPickerConstants.h" 25 | 26 | @interface TODocumentPickerTableViewCell : UITableViewCell 27 | 28 | @property (nonatomic, readonly) UILabel *textLabel; 29 | @property (nonatomic, readonly) UILabel *detailTextLabel; 30 | @property (nonatomic, readonly) UIImageView *imageView; 31 | 32 | - (void)configureCellForStyle:(TODocumentPickerViewControllerStyle)style; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Views/TODocumentPickerTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerTableView.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerTableView.h" 24 | 25 | @implementation TODocumentPickerTableView 26 | 27 | - (void)setContentSize:(CGSize)contentSize 28 | { 29 | CGFloat scrollInset = self.contentInset.top + self.contentInset.bottom; 30 | if (@available(iOS 11.0, *)) { 31 | scrollInset = self.adjustedContentInset.top + self.adjustedContentInset.bottom; 32 | } 33 | CGFloat height = (CGRectGetHeight(self.bounds) - scrollInset) + CGRectGetHeight(self.tableHeaderView.frame); 34 | contentSize.height = MAX(height, contentSize.height); 35 | [super setContentSize:contentSize]; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerItem.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | @class TODocumentPickerViewController; 26 | 27 | @interface TODocumentPickerItem : NSObject 28 | 29 | @property (nonatomic, copy) NSString *fileName; /** The full name of this file */ 30 | @property (nonatomic, assign) NSUInteger fileSize; /** The size of the file in bytes */ 31 | @property (nonatomic, strong) NSDate *lastModifiedDate; /** Where possible, the modification date of this file */ 32 | @property (nonatomic, assign) BOOL isFolder; /** Whether the file is a folder or now */ 33 | 34 | /** A pre-generated description of the item that is displayed in the picker view */ 35 | @property (nonatomic, copy, readonly) NSString *localizedMetadata; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerLocalDiskDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerFileDataSource.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import "TODocumentPickerConstants.h" 25 | 26 | @interface TODocumentPickerLocalDiskDataSource : NSObject 27 | 28 | /** Allows you to override the title of the root folder displayed initially. */ 29 | @property (nonatomic, copy) NSString *rootFolderName; 30 | 31 | /** Shows files that have been prefixed with a '.' character. */ 32 | @property (nonatomic, assign) BOOL showHiddenFiles; 33 | 34 | /* Create a new instance of this data source, with the supplied file path 35 | pointing to the folder that will be initially displayed. 36 | If the file path is relative (eg, starts with '/'), the app's top-most directory 37 | in the sandbox will be used. 38 | */ 39 | - (instancetype)initWithBaseFolderPath:(NSString *)folderPath; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Views/TODocumentPickerHeaderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerHeaderView.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import "TODocumentPickerSegmentedControl.h" 25 | 26 | @class TOSearchBar; 27 | 28 | @interface TODocumentPickerHeaderView : UIView 29 | 30 | /* Views */ 31 | @property (nonatomic, readonly) TOSearchBar *searchBar; 32 | @property (nonatomic, readonly) TODocumentPickerSegmentedControl *sortControl; 33 | 34 | /* The height from the top of the view that is clipped */ 35 | @property (nonatomic, assign) CGFloat clippingHeight; 36 | 37 | /* Whether to show the search bar or not */ 38 | @property (nonatomic, assign) BOOL showsSearchBar; 39 | 40 | /* Responding to text inserted into the search bar */ 41 | @property (nonatomic, copy) void (^searchTextChangedHandler)(NSString *searchText); 42 | 43 | /* Cancelling the search bar (i.e the user scrolls the table view) */ 44 | - (void)dismissKeyboard; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerConstants.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerDefines.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerConstants.h" 24 | 25 | NSString * const TODocumentPickerViewControllerThemeAttributeBackgroundColor = @""; 26 | NSString * const TODocumentPickerViewControllerThemeAttributeTableSeparatorColor = @""; 27 | NSString * const TODocumentPickerViewControllerThemeAttributeTableCellTitleColor = @""; 28 | NSString * const TODocumentPickerViewControllerThemeAttributeTableCellDetailTextColor = @""; 29 | NSString * const TODocumentPickerViewControllerThemeAttributeTableCellAccessoryTintColor = @""; 30 | NSString * const TODocumentPickerViewControllerThemeAttributeTableCellIconTintColor = @""; 31 | NSString * const TODocumentPickerViewControllerThemeAttributeTableSectionHeaderBackgroundColor = @""; 32 | NSString * const TODocumentPickerViewControllerThemeAttributeTableSectionTitleColor = @""; 33 | NSString * const TODocumentPickerViewControllerThemeAttributeTableSectionIndexColor = @""; 34 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerTheme.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerTheme.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | @interface TODocumentPickerTheme : NSObject 29 | 30 | @property (nonatomic, strong, nullable) UIColor *backgroundColor; /* Background color of the table view */ 31 | @property (nonatomic, strong, nullable) UIColor *tableSeparatorColor; /* Color of the table cell divider lines */ 32 | @property (nonatomic, strong, nullable) UIColor *tableCellTitleColor; /* Color of the title text label in each cell */ 33 | @property (nonatomic, strong, nullable) UIColor *tableCellDetailColor; /* Color of the subtitle text label in each cell */ 34 | @property (nonatomic, strong, nullable) UIColor *tableAccessoryViewColor; /* Color of the arrow accessory icon */ 35 | @property (nonatomic, strong, nullable) UIColor *scrollBarTintColor; /* Color of the scroll bar */ 36 | 37 | @end 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/TOAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TODocumentPickerViewControllerExample 4 | // 5 | // Created by Tim Oliver on 12/31/14. 6 | // Copyright (c) 2014 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "TOAppDelegate.h" 10 | 11 | @interface TOAppDelegate () 12 | 13 | @end 14 | 15 | @implementation TOAppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TODocumentPickerViewController 2 | 3 |

4 | 5 |

6 | 7 | [![Beerpay](https://beerpay.io/TimOliver/TODocumentPickerViewController/badge.svg?style=flat)](https://beerpay.io/TimOliver/TODocumentPickerViewController) 8 | [![PayPal](https://img.shields.io/badge/paypal-donate-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M4RKULAVKV7K8) 9 | 10 | `TODocumentPickerViewController` is an open source `UIViewController` framework that takes a list of files as input, and displays a navigable file system on iOS devices. 11 | 12 | The goal of this project is to provide a completely generic, modular frontend UI for a variety of file services, both online, and locally. With services like Dropbox and Google Drive exposing their file systems via a REST API, and more low-level protocols like SMB and FTP requiring direct object serialisation, `TODocumentPickerViewController` is being engineered in such a way that any file source can be integrated. 13 | 14 | # Features 15 | * Uses a delegate / data source model in order to integrate with any file source. 16 | * Can be configured using a configuration object passed to it upon init. 17 | * Self-replicates as users drill down folders, keeping configuration consistent between copies. 18 | * Data source provides an asynchronous mechanism to allow long lasting requests (like REST APIs) to not block the main thread. 19 | 20 | ## Installation 21 | 22 | #### As a CocoaPods Dependency 23 | 24 | Add the following to your Podfile: 25 | ``` ruby 26 | pod 'TODocumentPickerViewController' 27 | ``` 28 | 29 | #### Manual Installation 30 | 31 | All of the necessary source files are in `TODocumentPickerViewController`. Simply copy that folder to your app project folder, and then import it into Xcode. 32 | 33 | `TODocumentPickerViewController` also relies on the following libraries: 34 | 35 | * [`TOSearchBar`](https://github.com/TimOliver/TOSearchBar) 36 | 37 | While copies have been placed in this repo to minimise the need to download multiple other repos, be sure to check to see if there have been updates on their main repos. 38 | 39 | ## Technical Requirements 40 | iOS 9.0 or above. 41 | 42 | ## License 43 | TODocumentPickerViewControlleris licensed under the MIT License. Please see the LICENSE file for more information. ![analytics](https://ga-beacon.appspot.com/UA-5643664-16/TODocumentPickerViewController/README.md?pixel) 44 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerItemManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerItemManager.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import 25 | #import "TODocumentPickerConstants.h" 26 | 27 | @class TODocumentPickerItem; 28 | 29 | @interface TODocumentPickerItemManager : NSObject 30 | 31 | /* Main configuration properties (Changing any of these properties will cause a reload) */ 32 | @property (nonatomic, strong) NSArray *items; /* Items are passed to the manager by setting this property. */ 33 | @property (nonatomic, assign) TODocumentPickerSortType sortingType; /* The order in which items are displayed. */ 34 | @property (nonatomic, copy) NSString *searchString; /* Filter the items with this string */ 35 | 36 | @property (nonatomic, weak) UITableView *tableView; /* A weak reference to the relating table view. */ 37 | @property (nonatomic, copy) void (^contentReloadedHandler)(void); /* A handler called each time the manager decides to reload the table view. */ 38 | 39 | @property (nonatomic, readonly) NSInteger numberOfItems; /* Number of visible rows depending on search string */ 40 | 41 | /* Start sorting items, if not already done so. */ 42 | - (void)reloadItems; 43 | 44 | /* Retrieve the appropriate object at the provided index */ 45 | - (TODocumentPickerItem *)itemForIndexPath:(NSIndexPath *)indexPath; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Categories/UIImage+TODocumentPickerIcons.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+TODocumentPickerIcons.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import "TODocumentPickerConstants.h" 25 | 26 | @interface UIImage (TODocumentPickerIcons) 27 | 28 | + (UIImage *)TO_downloadIcon; 29 | 30 | + (UIImage *)TO_documentPickerDefaultFolderForStyle:(TODocumentPickerViewControllerStyle)style; 31 | 32 | + (UIImage *)TO_documentPickerFolderIconWithSize:(CGSize)size 33 | backgroundColor:(UIColor *)backgroundColor 34 | foregroundBottomColor:(UIColor *)foregroundBottomColor 35 | foregroundTopColor:(UIColor *)foregroundTopColor; 36 | 37 | + (UIImage *)TO_documentPickerDefaultFileIconWithExtension:(NSString *)extension 38 | tintColor:(UIColor *)tintColor 39 | style:(TODocumentPickerViewControllerStyle)style; 40 | 41 | + (UIImage *)TO_documentPickerIconWithSize:(CGSize)size 42 | outlineColor:(UIColor *)outlineColor 43 | backgroundColor:(UIColor *)backgroundColor 44 | cornerColor:(UIColor *)cornerColor 45 | formatNameString:(NSString *)formatNameString 46 | formatNameFont:(UIFont *)formatNameFont 47 | formatNameColor:(UIColor *)formatNameColor; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerConfiguration.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerConstants.h" 24 | 25 | #import "TODocumentPickerConfiguration.h" 26 | #import "TODocumentPickerTheme.h" 27 | #import "UIImage+TODocumentPickerIcons.h" 28 | 29 | @interface TODocumentPickerConfiguration () 30 | 31 | @property (nonatomic, strong, readwrite) TODocumentPickerTheme *theme; 32 | 33 | @end 34 | 35 | @implementation TODocumentPickerConfiguration 36 | 37 | - (instancetype)init 38 | { 39 | if (self = [super init]) { 40 | _showToolbar = YES; 41 | _theme = [[TODocumentPickerTheme alloc] init]; 42 | _fileIcons = [NSMutableDictionary dictionary]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (UIImage *)defaultIcon 49 | { 50 | if (_defaultIcon == nil) { 51 | _defaultIcon = [UIImage TO_documentPickerDefaultFileIconWithExtension:nil 52 | tintColor:nil 53 | style:self.style]; 54 | } 55 | 56 | return _defaultIcon; 57 | } 58 | 59 | 60 | - (void)setStyle:(TODocumentPickerViewControllerStyle)style 61 | { 62 | if (style == _style) { return; } 63 | _style = style; 64 | 65 | _defaultIcon = nil; 66 | _defaultIcon = nil; 67 | } 68 | 69 | - (void)setFileIcons:(NSMutableDictionary *)fileIcons 70 | { 71 | if (fileIcons == _fileIcons) { return; } 72 | 73 | _fileIcons = fileIcons; 74 | 75 | if (_fileIcons == nil) { 76 | _fileIcons = [NSMutableDictionary dictionary]; 77 | } 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerConfiguration.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerConfiguration.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerConstants.h" 24 | 25 | #import 26 | #import 27 | 28 | @class TODocumentPickerTheme; 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | @interface TODocumentPickerConfiguration : NSObject 33 | 34 | /* Sets the style of the view controller to be light or dark. (Can be customized with the `theme` property) */ 35 | @property (nonatomic, assign) TODocumentPickerViewControllerStyle style; 36 | 37 | /* Whether this controller shows and manages the navigation controller toolbar (Default is YES) */ 38 | @property (nonatomic, assign) BOOL showToolbar; 39 | 40 | /* When not in 'Select' mode, the bar button items on the left hand side of the toolbar */ 41 | @property (nonatomic, strong) NSArray *toolbarLeftItems; 42 | 43 | /* When not in 'Select' mode, the bar button items on the right hand side of the toolbar */ 44 | @property (nonatomic, strong) NSArray *toolbarRightItems; 45 | 46 | /* File formats that may be selected by this controller. (If nil, all files may be selected) */ 47 | @property (nonatomic, strong, nullable) NSArray *allowedFileExtensions; 48 | 49 | /* Shows files that weren't on the allowed extensions list, but grayed out (Default is NO) */ 50 | @property (nonatomic, assign) BOOL showExcludedFileExtensions; 51 | 52 | /* The default icon if the file format isn't recognized, or other icons aren't available. */ 53 | @property (nonatomic, strong, nullable) UIImage *defaultIcon; 54 | 55 | /* The icon used for folder entries. */ 56 | @property (nonatomic, strong, nullable) UIImage *folderIcon; 57 | 58 | /* A dictionary of file extension strings to images to use as icons for each file format. */ 59 | @property (nonatomic, strong, null_resettable) NSMutableDictionary *fileIcons; 60 | 61 | /* If desired, a custom table view cell class that can contain additional controls or formatting. */ 62 | @property (nonatomic, assign, nullable) Class tableViewCellClass; 63 | 64 | /* After setting the `style` property, certain visual aspects of the controller can be fine-tuned with this property. */ 65 | @property (nonatomic, readonly) TODocumentPickerTheme *theme; 66 | 67 | @end 68 | 69 | NS_ASSUME_NONNULL_END 70 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/TODocumentPickerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerTableViewController.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import "TODocumentPickerConstants.h" 25 | #import "TODocumentPickerItem.h" 26 | #import "TODocumentPickerConfiguration.h" 27 | #import "TODocumentPickerTheme.h" 28 | 29 | @interface TODocumentPickerViewController : UITableViewController 30 | 31 | @property (nonatomic, readonly, nonnull) TODocumentPickerConfiguration *configuration; /* An object that holds the configuration for all controllers */ 32 | 33 | @property (nonatomic, strong, nullable) id dataSource; /* Data source for file info. Retained by the document picker and shared among all children. */ 34 | @property (nonatomic, weak, nullable) id documentPickerDelegate; /* Sends out delegate events to the assigned object */ 35 | 36 | @property (nonatomic, readonly, nullable) NSString *filePath; /* The file path that this view controller corresponds to */ 37 | @property (nonatomic, strong, nullable) NSArray *items; /* All of the items displayed by this view controller. (Setting this will trigger a UI refresh) */ 38 | 39 | @property (nonatomic, readonly, nonnull) TODocumentPickerViewController *rootViewController; /* In a navigation chain of picker controllers, the root controller at the front. */ 40 | @property (nonatomic, readonly, nonnull) NSArray *viewControllers; /* The chain of document picker view controllers in the navigation stack */ 41 | 42 | /* Create the initial view controller with the file path string that will be the initial starting point */ 43 | - (nullable instancetype)initWithFilePath:(nullable NSString *)filePath; 44 | 45 | /* Create an instance with a custom created configuration object */ 46 | - (nullable instancetype)initWithConfiguration:(nullable TODocumentPickerConfiguration *)configuration filePath:(nullable NSString *)filePath; 47 | 48 | /* Sets the items for the view controller in this chain, controlling that file path */ 49 | - (void)setItems:(nullable NSArray *)items forFilePath:(nullable NSString *)filePath; 50 | 51 | /* An initial file path must be specified, so use initWithFilePath: */ 52 | - (nonnull instancetype)init __attribute__((unavailable("Must use initWithFilePath: instead."))); 53 | + (nonnull instancetype)new __attribute__((unavailable("Must use initWithFilePath: instead."))); 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerItem.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerItem.h" 24 | 25 | @interface TODocumentPickerItem () 26 | 27 | @property (nonatomic, readonly) NSByteCountFormatter *sharedFileSizeFormatter; 28 | @property (nonatomic, readonly) NSDateFormatter *sharedDateFormatter; 29 | @property (nonatomic, copy, readwrite) NSString *localizedMetadata; 30 | 31 | /* Rebuild the localized description string */ 32 | - (void)formatMetadata; 33 | 34 | @end 35 | 36 | @implementation TODocumentPickerItem 37 | 38 | #pragma mark Accessors - 39 | - (NSString *)localizedMetadata 40 | { 41 | if (_localizedMetadata == nil) 42 | [self formatMetadata]; 43 | 44 | return _localizedMetadata; 45 | } 46 | 47 | #pragma mark - Info formatter - 48 | - (void)formatMetadata 49 | { 50 | NSString *formattedSize = [self.sharedFileSizeFormatter stringFromByteCount:(long long)self.fileSize]; 51 | NSString *formattedDate = [self.sharedDateFormatter stringFromDate:self.lastModifiedDate]; 52 | 53 | if (!self.isFolder && self.lastModifiedDate && self.fileSize) { 54 | _localizedMetadata = [NSString stringWithFormat:@"%@ • %@", formattedSize, formattedDate]; 55 | } 56 | else if (self.lastModifiedDate) { 57 | _localizedMetadata = formattedDate; 58 | } 59 | else if (self.fileSize) { 60 | _localizedMetadata = formattedSize; 61 | } 62 | } 63 | 64 | - (NSByteCountFormatter *)sharedFileSizeFormatter 65 | { 66 | static NSByteCountFormatter *_sharedFileSizeFormatter = nil; 67 | static dispatch_once_t onceToken; 68 | dispatch_once(&onceToken, ^{ 69 | _sharedFileSizeFormatter = [[NSByteCountFormatter alloc] init]; 70 | _sharedFileSizeFormatter.allowsNonnumericFormatting = NO; 71 | }); 72 | 73 | return _sharedFileSizeFormatter; 74 | } 75 | 76 | - (NSDateFormatter *)sharedDateFormatter 77 | { 78 | static NSDateFormatter *_sharedDateFormatter = nil; 79 | static dispatch_once_t onceToken; 80 | dispatch_once(&onceToken, ^{ 81 | _sharedDateFormatter = [[NSDateFormatter alloc] init]; 82 | _sharedDateFormatter.dateStyle = NSDateFormatterMediumStyle; 83 | _sharedDateFormatter.timeStyle = NSDateFormatterShortStyle; 84 | }); 85 | 86 | return _sharedDateFormatter; 87 | } 88 | 89 | #pragma mark - System Description - 90 | - (NSString *)description 91 | { 92 | return [NSString stringWithFormat:@"Name: %@ Size: %ld Folder: %d", self.fileName, (long)self.fileSize, self.isFolder]; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerLocalDiskDataSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerFileDataSource.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerLocalDiskDataSource.h" 24 | #import "TODocumentPickerViewController.h" 25 | 26 | @interface TODocumentPickerLocalDiskDataSource () 27 | 28 | @property (nonatomic, copy) NSString *rootFilePath; 29 | 30 | @end 31 | 32 | @implementation TODocumentPickerLocalDiskDataSource 33 | 34 | - (instancetype)init 35 | { 36 | if (self = [self initWithBaseFolderPath:nil]) { 37 | 38 | } 39 | 40 | return self; 41 | } 42 | 43 | - (instancetype)initWithBaseFolderPath:(NSString *)folderPath 44 | { 45 | if (self = [super init]) { 46 | _rootFilePath = [TODocumentPickerLocalDiskDataSource formattedRootFilePath:folderPath]; 47 | self.rootFolderName = _rootFilePath.lastPathComponent; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | + (NSString *)formattedRootFilePath:(NSString *)filePath 54 | { 55 | // If it doesn't start with '/', assume absolute 56 | if (filePath.length && [filePath characterAtIndex:0] != '/') { 57 | return filePath; 58 | } 59 | 60 | NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 61 | NSString *applicationSandboxPath = [documentPath stringByDeletingLastPathComponent]; 62 | return applicationSandboxPath; 63 | } 64 | 65 | #pragma mark - Data Source Protocol Methods- 66 | 67 | - (NSString *)documentPickerViewController:(TODocumentPickerViewController *)documentPicker titleForFilePath:(NSString *)filePath 68 | { 69 | if (!self.rootFolderName) { 70 | return filePath.lastPathComponent; 71 | } 72 | else { 73 | if (filePath.length == 0 || [filePath isEqualToString:@"/"]) { 74 | return self.rootFolderName; 75 | } 76 | } 77 | 78 | return filePath.lastPathComponent; 79 | } 80 | 81 | - (void)documentPickerViewController:(TODocumentPickerViewController *)documentPicker 82 | requestItemsForFilePath:(NSString *)filePath 83 | completionHandler:(void (^)(NSArray * _Nullable))completionHandler 84 | { 85 | NSString *fullFilePath = [self.rootFilePath stringByAppendingPathComponent:filePath]; 86 | NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fullFilePath error:nil]; 87 | 88 | NSMutableArray *items = [NSMutableArray array]; 89 | for (NSString *file in files) { 90 | // Skip hidden files if required 91 | if (!self.showHiddenFiles && [file characterAtIndex:0] == '.') { 92 | continue; 93 | } 94 | 95 | NSString *path = [fullFilePath stringByAppendingPathComponent:file]; 96 | 97 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; 98 | TODocumentPickerItem *item = [TODocumentPickerItem new]; 99 | item.fileName = file; 100 | item.isFolder = (attributes.fileType == NSFileTypeDirectory); 101 | item.fileSize = item.isFolder ? 0 : (NSUInteger)attributes.fileSize; 102 | item.lastModifiedDate = attributes.fileModificationDate; 103 | [items addObject:item]; 104 | } 105 | 106 | completionHandler(items); 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample/TODocumentsDataSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentsDataSource.m 3 | // TODocumentPickerViewControllerExample 4 | // 5 | // Created by Tim Oliver on 1/1/15. 6 | // Copyright (c) 2015 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "TODocumentsDataSource.h" 10 | #import "TODocumentPickerConstants.h" 11 | #import "TODocumentPickerItem.h" 12 | 13 | @interface TODocumentsDataSource () 14 | 15 | - (void)createTestData; 16 | - (NSString *)documentsPath; 17 | 18 | @end 19 | 20 | @implementation TODocumentsDataSource 21 | 22 | - (instancetype)init 23 | { 24 | if (self = [super init]) { 25 | [self createTestData]; 26 | } 27 | 28 | return self; 29 | } 30 | 31 | #pragma mark - Data Source Methods - 32 | - (NSString *)documentPickerViewController:(TODocumentPickerViewController *)documentPicker titleForFilePath:(NSString *)filePath 33 | { 34 | if (filePath.length == 0|| [filePath isEqualToString:@"/"]) { 35 | return @"Documents"; 36 | } 37 | 38 | return filePath.lastPathComponent; 39 | } 40 | 41 | - (void)documentPickerViewController:(TODocumentPickerViewController *)documentPicker 42 | requestItemsForFilePath:(NSString *)filePath 43 | completionHandler:(void (^)(NSArray * _Nullable))completionHandler 44 | { 45 | NSString *fullFilePath = [self.documentsPath stringByAppendingPathComponent:filePath]; 46 | NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fullFilePath error:nil]; 47 | 48 | NSMutableArray *items = [NSMutableArray array]; 49 | for (NSString *file in files) { 50 | NSString *path = [fullFilePath stringByAppendingPathComponent:file]; 51 | 52 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; 53 | TODocumentPickerItem *item = [TODocumentPickerItem new]; 54 | item.fileName = file; 55 | item.isFolder = (attributes.fileType == NSFileTypeDirectory); 56 | item.fileSize = item.isFolder ? 0 : (NSUInteger)attributes.fileSize; 57 | item.lastModifiedDate = attributes.fileModificationDate; 58 | [items addObject:item]; 59 | } 60 | 61 | //Perform after a 1 second delay to simulate a web request 62 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 63 | completionHandler(items); 64 | }); 65 | } 66 | 67 | #pragma mark - Class Setup / Management - 68 | - (void)createTestData 69 | { 70 | NSString *documentsFilePath = [self documentsPath]; 71 | 72 | NSArray *folders = @[@"Apps", 73 | @"Archive", 74 | @"Books", 75 | @"Comics", 76 | @"Documents", 77 | @"Examples", 78 | @"Music", 79 | @"Photos", 80 | @"Pictures", 81 | @"Programs", 82 | @"Public", 83 | @"Save Files", 84 | @"Shared", 85 | @"Sites", 86 | @"Writing", 87 | @"Apps/iComics", 88 | @"Apps/Dropbox", 89 | @"Archive/Design Docs", 90 | @"Comics/Adventures in Space", 91 | @"Documents/Invoices", 92 | @"Examples/PSDs",]; 93 | 94 | for (NSString *folder in folders) { 95 | NSString *filePath = [documentsFilePath stringByAppendingPathComponent:folder]; 96 | [[NSFileManager defaultManager] createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil]; 97 | } 98 | 99 | NSArray *textFiles = @[@"DesignPlan.txt", 100 | @"HelloWorld.txt", 101 | @"Blog Posts.txt", 102 | @"Test Document.txt", 103 | @"Upcoming Projects.txt" 104 | ]; 105 | 106 | for (NSString *file in textFiles) { 107 | NSString *filePath = [documentsFilePath stringByAppendingPathComponent:file]; 108 | [file writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 109 | } 110 | } 111 | 112 | - (NSString *)documentsPath 113 | { 114 | static NSString *sharedDocumentsDirectoryPath = nil; 115 | static dispatch_once_t token; 116 | dispatch_once(&token, ^{ 117 | sharedDocumentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 118 | }); 119 | return sharedDocumentsDirectoryPath; 120 | } 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Views/TODocumentPickerTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerTableViewCell.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerTableViewCell.h" 24 | 25 | static const CGFloat kTODocumentPickerTableViewCellImagePadding = 15.0f; 26 | 27 | @interface TODocumentPickerTableViewCell () 28 | 29 | @property (nonatomic, assign) CGFloat textFontHeight; 30 | @property (nonatomic, assign) CGFloat detailTextFontHeight; 31 | 32 | @end 33 | 34 | @implementation TODocumentPickerTableViewCell 35 | 36 | @synthesize imageView = __imageView; 37 | @synthesize textLabel = __textLabel; 38 | @synthesize detailTextLabel = __detailTextLabel; 39 | 40 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 41 | { 42 | if (self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]) { 43 | [self commonInit]; 44 | } 45 | 46 | return self; 47 | } 48 | 49 | - (void)commonInit 50 | { 51 | __textLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 52 | __textLabel.font = [UIFont systemFontOfSize:18.0f weight:UIFontWeightRegular]; 53 | __textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; 54 | [self.contentView addSubview:__textLabel]; 55 | 56 | __detailTextLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 57 | __detailTextLabel.font = [UIFont systemFontOfSize:13.0f weight:UIFontWeightRegular]; 58 | __detailTextLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; 59 | [self.contentView addSubview:__detailTextLabel]; 60 | 61 | __imageView = [[UIImageView alloc] initWithImage:nil]; 62 | __imageView.contentMode = UIViewContentModeScaleAspectFit; 63 | [self.contentView addSubview:__imageView]; 64 | 65 | _textFontHeight = __textLabel.font.ascender - __textLabel.font.descender; 66 | _detailTextFontHeight = __detailTextLabel.font.ascender - __detailTextLabel.font.descender; 67 | } 68 | 69 | - (void)configureCellForStyle:(TODocumentPickerViewControllerStyle)style 70 | { 71 | BOOL darkMode = (style == TODocumentPickerViewControllerStyleDark); 72 | 73 | self.textLabel.textColor = darkMode ? [UIColor whiteColor] : [UIColor blackColor]; 74 | self.detailTextLabel.textColor = darkMode ? [UIColor colorWithWhite:0.6f alpha:1.0f] : [UIColor colorWithWhite:0.5f alpha:1.0f]; 75 | } 76 | 77 | - (void)layoutSubviews 78 | { 79 | [super layoutSubviews]; 80 | 81 | CGRect contentFrame = CGRectZero; 82 | contentFrame.origin.x = self.layoutMargins.left; 83 | contentFrame.origin.y = self.layoutMargins.top; 84 | contentFrame.size.width = CGRectGetWidth(self.contentView.frame) - (self.layoutMargins.left + self.layoutMargins.right); 85 | contentFrame.size.height = CGRectGetHeight(self.contentView.frame) - (self.layoutMargins.top + self.layoutMargins.bottom); 86 | 87 | CGRect frame = CGRectZero; 88 | frame.origin.x = contentFrame.origin.x; 89 | frame.origin.y = contentFrame.origin.y; 90 | frame.size.height = contentFrame.size.height; 91 | frame.size.width = contentFrame.size.height; 92 | self.imageView.frame = CGRectIntegral(frame); 93 | 94 | CGFloat midY = CGRectGetMidY(contentFrame); 95 | frame.origin.x = CGRectGetMaxX(self.imageView.frame) + kTODocumentPickerTableViewCellImagePadding; 96 | frame.origin.y = ceilf(midY - self.textFontHeight); 97 | frame.size.width = contentFrame.size.width - frame.origin.x; 98 | frame.size.height = self.textFontHeight; 99 | self.textLabel.frame = CGRectIntegral(frame); 100 | 101 | frame.origin.x = CGRectGetMaxX(self.imageView.frame) + kTODocumentPickerTableViewCellImagePadding; 102 | frame.origin.y = ceilf(midY); 103 | frame.size.width = contentFrame.size.width - frame.origin.x; 104 | frame.size.height = self.detailTextFontHeight; 105 | self.detailTextLabel.frame = CGRectIntegral(frame); 106 | } 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerDefines.h 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | @class TODocumentPickerViewController; 27 | @class TODocumentPickerItem; 28 | 29 | /* 30 | The default content styles that the controller implements. 31 | */ 32 | typedef NS_ENUM(NSInteger, TODocumentPickerViewControllerStyle) { 33 | TODocumentPickerViewControllerStyleLight, 34 | TODocumentPickerViewControllerStyleDark 35 | }; 36 | 37 | /* 38 | The various sorting orders in which the picker can display the files. 39 | */ 40 | typedef NS_ENUM(NSInteger, TODocumentPickerSortType) { 41 | TODocumentPickerSortTypeNameAscending=0, 42 | TODocumentPickerSortTypeNameDescending, 43 | TODocumentPickerSortTypeDateAscending, 44 | TODocumentPickerSortTypeDateDescending, 45 | TODocumentPickerSortTypeSizeAscending, 46 | TODocumentPickerSortTypeSizeDescending 47 | }; 48 | 49 | //------------------------------------------------------------------------- 50 | // The data source protocol for objects charged with loading the file information to display 51 | 52 | @protocol TODocumentPickerViewControllerDataSource 53 | 54 | @required 55 | 56 | /** 57 | Called by the view controller when it wants to obtain a list of items for the folder at the end of the file path. 58 | Whether the data source subsequently hands off this request asynchronously or not, when completed, the data source 59 | must call the 'updateItemsForFilePath' block. 60 | 61 | @param filePath The file path with which to download file information 62 | */ 63 | - (void)documentPickerViewController:(nonnull TODocumentPickerViewController *)documentPicker 64 | requestItemsForFilePath:(nonnull NSString *)filePath 65 | completionHandler:(nonnull void (^)(NSArray * _Nullable items))completionHandler; 66 | 67 | @optional 68 | 69 | /** 70 | If an asynchronous request for files is currently in progress and the representing view controller is canceled, 71 | (eg, if the user hits 'back' before it completes), this method will be called to give the request a chance to cancel. 72 | 73 | @param filePath The file path with which to cancel loading 74 | */ 75 | - (void)documentPickerViewController:(nonnull TODocumentPickerViewController *)documentPicker cancelRequestForFilePath:(nonnull NSString *)filePath; 76 | 77 | /** 78 | After a table cell has been configured for display, this method allows for additional custom configuration of the cell after the fact. 79 | */ 80 | - (void)documentPickerViewController:(nonnull TODocumentPickerViewController *)documentPicker configureCell:(nonnull UITableViewCell *)cell withItem:(nonnull TODocumentPickerItem *)item; 81 | 82 | /** 83 | The title that will appear in the navigation bar for the folder at this file path. 84 | If not implemented, defautl behaviour is to return simply the folder name from the filePath string 85 | */ 86 | - (nullable NSString *)documentPickerViewController:(nonnull TODocumentPickerViewController *)documentPicker titleForFilePath:(nullable NSString *)filePath; 87 | 88 | /** 89 | If desired, this method may be overridden to provide a custom icon for items that represent folders in the file system. 90 | */ 91 | - (nonnull UIImage *)documentPickerViewController:(nonnull TODocumentPickerViewController *)documentPicker folderIconForStyle:(TODocumentPickerViewControllerStyle)style; 92 | 93 | /** 94 | If desired, a custom icon can be provided by default for each type of file that the view controller is displaying. `extension` can be `nil` for unknown file types. 95 | */ 96 | - (nonnull UIImage *)documentPickerViewController:(nonnull TODocumentPickerViewController *)documentPicker fileIconForExtension:(nullable NSString *)extension style:(TODocumentPickerViewControllerStyle)style; 97 | 98 | @end 99 | 100 | //------------------------------------------------------------------------- 101 | 102 | @protocol TODocumentPickerViewControllerDelegate 103 | 104 | @optional 105 | 106 | /* User either tapped a single item, or selected multiple items and hit 'choose' */ 107 | - (void)documentPickerViewController:(nonnull TODocumentPickerViewController *)documentPicker didSelectItems:(nonnull NSArray *)items inFilePath:(nullable NSString *)filePath; 108 | 109 | @end 110 | 111 | //------------------------------------------------------------------------- 112 | // Size of the default file and folder icons 113 | 114 | static const CGSize kTODocumentPickerDefaultFolderIconSize = (CGSize){47,37}; 115 | static const CGSize kTODocumentPickerDefaultFileIconSize = (CGSize){37,49}; 116 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Models/TODocumentPickerItemManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerItemManager.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerItemManager.h" 24 | #import "TODocumentPickerItem.h" 25 | 26 | @interface TODocumentPickerItemManager () 27 | 28 | @property (nonatomic, strong) NSArray *sortedItems; /* A single list of sorted items */ 29 | @property (nonatomic, strong) NSArray *filteredItems; /* A single list of filtered items */ 30 | 31 | @end 32 | 33 | @implementation TODocumentPickerItemManager 34 | 35 | #pragma mark - Item Sorting - 36 | - (void)reloadItems 37 | { 38 | [self rebuildItems]; 39 | } 40 | 41 | - (void)rebuildItems 42 | { 43 | /* Clear out all of the lists */ 44 | self.sortedItems = nil; 45 | self.filteredItems = nil; 46 | 47 | //A search string overrides all other lists 48 | if (self.searchString.length > 0) { 49 | self.filteredItems = [self filteredItemsWithItems:self.items searchString:self.searchString]; 50 | self.filteredItems = [self sortedItemsArrayWithArray:self.filteredItems]; 51 | } 52 | else { 53 | self.sortedItems = [self sortedItemsArrayWithArray:self.items]; 54 | } 55 | 56 | [self updateTableView]; 57 | } 58 | 59 | - (void)updateTableView 60 | { 61 | dispatch_async(dispatch_get_main_queue(), ^{ 62 | [self.tableView reloadData]; 63 | if (self.contentReloadedHandler) { 64 | self.contentReloadedHandler(); 65 | } 66 | }); 67 | } 68 | 69 | - (NSArray *)sortedItemsArrayWithArray:(NSArray *)items 70 | { 71 | BOOL reverse = NO; 72 | NSString *sortKey = nil; 73 | 74 | switch (self.sortingType) { 75 | //File name needs to be explicitly sorted, so additional flags like 76 | //case-insensitive and numeric handling are properly set 77 | case TODocumentPickerSortTypeNameDescending: reverse = YES; 78 | case TODocumentPickerSortTypeNameAscending: 79 | items = [items sortedArrayUsingComparator:^NSComparisonResult(TODocumentPickerItem *first, TODocumentPickerItem *second) { 80 | return [first.fileName compare:second.fileName options:NSCaseInsensitiveSearch|NSNumericSearch|NSDiacriticInsensitiveSearch]; 81 | }]; 82 | if (reverse) 83 | items = [[items reverseObjectEnumerator] allObjects]; 84 | 85 | return items; 86 | 87 | case TODocumentPickerSortTypeDateDescending: reverse = YES; 88 | case TODocumentPickerSortTypeDateAscending: sortKey = @"lastModifiedDate"; 89 | break; 90 | 91 | case TODocumentPickerSortTypeSizeDescending: reverse = YES; 92 | case TODocumentPickerSortTypeSizeAscending: sortKey = @"fileSize"; 93 | break; 94 | } 95 | 96 | //Sort the files 97 | NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:sortKey ascending:!reverse]; 98 | items = [items sortedArrayUsingDescriptors:@[sortDescriptor]]; 99 | return items; 100 | } 101 | 102 | - (NSArray *)filteredItemsWithItems:(NSArray *)items searchString:(NSString *)searchString 103 | { 104 | NSMutableArray *filteredItems = [NSMutableArray array]; 105 | [items enumerateObjectsUsingBlock:^(TODocumentPickerItem *item, NSUInteger i, BOOL *stop) { 106 | if ([item.fileName rangeOfString:searchString options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)].location != NSNotFound) { 107 | [filteredItems addObject:item]; 108 | } 109 | }]; 110 | 111 | return [NSArray arrayWithArray:filteredItems]; 112 | } 113 | 114 | #pragma mark - Item Serving - 115 | - (TODocumentPickerItem *)itemForIndexPath:(NSIndexPath *)indexPath 116 | { 117 | if (self.filteredItems) { 118 | return self.filteredItems[indexPath.row]; 119 | } 120 | 121 | if (self.sortedItems) { 122 | return self.sortedItems[indexPath.row]; 123 | } 124 | 125 | return nil; 126 | } 127 | 128 | #pragma mark - Accessors - 129 | 130 | - (NSInteger)numberOfItems 131 | { 132 | if (self.filteredItems) { 133 | return self.filteredItems.count; 134 | } 135 | 136 | return self.items.count; 137 | } 138 | 139 | - (void)setItems:(NSArray *)items 140 | { 141 | if (items == _items) { return; } 142 | 143 | _items = items; 144 | 145 | if (self.tableView == nil) { 146 | return; 147 | } 148 | 149 | [self rebuildItems]; 150 | } 151 | 152 | - (void)setSortingType:(TODocumentPickerSortType)sortingType 153 | { 154 | if (sortingType == _sortingType) { return; } 155 | 156 | _sortingType = sortingType; 157 | 158 | if (self.tableView == nil) 159 | return; 160 | 161 | [self rebuildItems]; 162 | } 163 | 164 | - (void)setSearchString:(NSString *)searchString 165 | { 166 | if (searchString == _searchString) { return; } 167 | 168 | _searchString = searchString; 169 | 170 | if (self.tableView == nil) 171 | return; 172 | 173 | [self rebuildItems]; 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /TOSearchBar/TOSearchBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOSearchBar.h 3 | // 4 | // Copyright 2015-2016 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | typedef NS_ENUM(NSInteger, TOSearchBarStyle) { 28 | TOSearchBarStyleLight = 0, 29 | TOSearchBarStyleDark = 1 30 | }; 31 | 32 | @class TOSearchBar; 33 | 34 | @protocol TOSearchBarDelegate 35 | 36 | @optional 37 | /** 38 | Called before the text field gains focus, and can optionally reject doing so. 39 | */ 40 | - (BOOL)searchBarShouldBeginEditing:(TOSearchBar *)searchBar; 41 | 42 | /** 43 | Called just after the text field gains focus. 44 | */ 45 | - (void)searchBarDidBeginEditing:(TOSearchBar *)searchBar; 46 | 47 | /** 48 | Called before the text field loses focus, and can optionally reject doing so. 49 | */ 50 | - (BOOL)searchBarShouldEndEditing:(TOSearchBar *)searchBar; 51 | 52 | /** 53 | Called just after the text field loses focus. 54 | */ 55 | - (void)searchBarDidEndEditing:(TOSearchBar *)searchBar; 56 | 57 | /** 58 | Called just after each time the text in the text field is changed. 59 | */ 60 | - (void)searchBar:(TOSearchBar *)searchBar textDidChange:(nullable NSString *)searchText; 61 | 62 | /** 63 | Called during text editing and can be used to optionally constrain the type of characters the field can accept. 64 | */ 65 | - (BOOL)searchBar:(TOSearchBar *)searchBar shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; 66 | 67 | /** 68 | Called when the 'Clear' button is tapped to confirm it may go ahead. 69 | */ 70 | - (void)searchBarClearButtonTapped:(TOSearchBar *)searchBar; 71 | 72 | /** 73 | Called after the user hits the 'Search' button. Can be used to set up actions for that event. 74 | */ 75 | - (void)searchBarSearchButtonTapped:(TOSearchBar *)searchBar; 76 | 77 | @end 78 | 79 | IB_DESIGNABLE 80 | @interface TOSearchBar : UIControl 81 | 82 | /** The color style of this view. Setting this will set a series of default color values to each subview. 83 | The individual views can then further be custom configured if desired. 84 | */ 85 | @property (nonatomic, assign) IBInspectable TOSearchBarStyle style; 86 | 87 | /** The delegate object for responding to events */ 88 | @property (nullable, nonatomic, weak) id delegate; 89 | 90 | /** The search text, entered by the user */ 91 | @property (nullable, nonatomic, copy) IBInspectable NSString *text; 92 | 93 | /** The contents of the placeholder text. Defaults to 'Search' */ 94 | @property (null_resettable, nonatomic, copy) IBInspectable NSString *placeholderText; 95 | 96 | /** How many points on each side the search bar is inset */ 97 | @property (nonatomic, assign) IBInspectable CGFloat horizontalInset; 98 | 99 | /** Whether the text field is currently presenting the keyboard */ 100 | @property (nonatomic, assign) BOOL editing; 101 | 102 | /** Whether the text field contains any text or not */ 103 | @property (nonatomic, readonly) BOOL hasSearchText; 104 | 105 | /** Shows a 'Cancel' button while editing */ 106 | @property (nonatomic, assign) IBInspectable BOOL showsCancelButton; 107 | 108 | /** The tint color of the placeholder content (Both the icon and the text) */ 109 | @property (nullable, nonatomic, strong) IBInspectable UIColor *placeholderTintColor UI_APPEARANCE_SELECTOR; 110 | 111 | /** The tint color of the rounded background rectangle */ 112 | @property (null_resettable, nonatomic, strong) IBInspectable UIColor *barBackgroundTintColor UI_APPEARANCE_SELECTOR; 113 | 114 | /** The tint color of the background when the text field is tapped by the user */ 115 | @property (nullable, nonatomic, strong) IBInspectable UIColor *selectedBarBackgroundTintColor UI_APPEARANCE_SELECTOR; 116 | 117 | /** The tint color of the background when the text field is currently, or has content */ 118 | @property (nullable, nonatomic, strong) IBInspectable UIColor *highlightedBarBackgroundTintColor UI_APPEARANCE_SELECTOR; 119 | 120 | /** The main text field that the user will input search text */ 121 | @property (nonatomic, readonly) UITextField *searchTextField; 122 | 123 | /** The auxiliary icon view to the left of the text field text */ 124 | @property (nullable, nonatomic, readonly) UIImageView *iconView; 125 | 126 | /** The initial 'Search' text label displayed when the search field is empty. */ 127 | @property (nonatomic, strong, readonly) UILabel *placeholderLabel; 128 | 129 | /** If specified, the 'Cancel' button that can cancel out of text input */ 130 | @property (nullable, nonatomic, readonly) UIButton *cancelButton; 131 | 132 | /** The 'clear' button that will clear any text in the text field */ 133 | @property (nonatomic, strong, readonly) UIButton *clearButton; 134 | 135 | /** 136 | Create a new instance of this class, specifying the frame and style. 137 | 138 | @param frame The entire frame for this view (Ideally of a height of 44 points) 139 | @param style The color style of the view (light or dark) 140 | */ 141 | - (instancetype)initWithFrame:(CGRect)frame style:(TOSearchBarStyle)style; 142 | 143 | /** 144 | Create a new instance of this class, specifying the style. 145 | 146 | @param style The color style of the view (light or dark) 147 | */ 148 | - (instancetype)initWithStyle:(TOSearchBarStyle)style; 149 | 150 | @end 151 | 152 | NS_ASSUME_NONNULL_END 153 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Views/TODocumentPickerHeaderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerHeaderView.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerHeaderView.h" 24 | #import "TOSearchBar.h" 25 | 26 | static const CGFloat kTODocumentPickerHeaderViewPadding = 5.0f; 27 | static const CGFloat kTODocumentPickerHeaderMaxWidth = 640.0f; 28 | 29 | @interface TODocumentPickerHeaderView () 30 | 31 | @property (nonatomic, strong, readwrite) UIView *clippingView; 32 | @property (nonatomic, strong, readwrite) UIView *containerView; 33 | @property (nonatomic, strong, readwrite) TOSearchBar *searchBar; 34 | @property (nonatomic, strong, readwrite) TODocumentPickerSegmentedControl *sortControl; 35 | 36 | @end 37 | 38 | @implementation TODocumentPickerHeaderView 39 | 40 | - (instancetype)init 41 | { 42 | if (self = [super initWithFrame:(CGRect){0,0,320,44}]) { 43 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 44 | self.backgroundColor = [UIColor clearColor]; 45 | 46 | [self setupViews]; 47 | } 48 | 49 | return self; 50 | } 51 | 52 | - (void)setupViews 53 | { 54 | self.clippingView = [[UIView alloc] initWithFrame:self.bounds]; 55 | self.clippingView.autoresizingMask = UIViewAutoresizingFlexibleWidth; 56 | self.clippingView.clipsToBounds = YES; 57 | [self addSubview:self.clippingView]; 58 | 59 | self.containerView = [[UIView alloc] initWithFrame:self.bounds]; 60 | self.containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; 61 | [self.clippingView addSubview:self.containerView]; 62 | 63 | self.sortControl = [[TODocumentPickerSegmentedControl alloc] init]; 64 | [self.containerView addSubview:self.sortControl]; 65 | } 66 | 67 | - (void)sizeToFit 68 | { 69 | CGFloat height = self.layoutMargins.top + self.layoutMargins.bottom; 70 | height += self.sortControl.frame.size.height; 71 | 72 | if (self.searchBar) { 73 | height += kTODocumentPickerHeaderViewPadding; 74 | height += self.searchBar.frame.size.height; 75 | } 76 | 77 | CGRect frame = self.frame; 78 | frame.size.height = height; 79 | self.frame = frame; 80 | } 81 | 82 | - (void)didMoveToSuperview 83 | { 84 | [super didMoveToSuperview]; 85 | self.backgroundColor = self.superview.backgroundColor; 86 | [self needsUpdateConstraints]; 87 | } 88 | 89 | - (void)layoutSubviews 90 | { 91 | [super layoutSubviews]; 92 | 93 | CGFloat originalHeight = self.bounds.size.height; 94 | CGFloat newHeight = originalHeight - self.clippingHeight; 95 | CGFloat clampedHeight = MAX(self.clippingHeight, 0); 96 | 97 | //clamp the height 98 | newHeight = MAX(0, newHeight); 99 | newHeight = MIN(originalHeight, newHeight); 100 | 101 | //Set the clipping view to lower 102 | CGRect frame = self.bounds; 103 | frame.size.height = newHeight; 104 | frame.origin.y = clampedHeight; 105 | self.clippingView.frame = frame; 106 | 107 | //At the same time, raise the container view to create the illusion 108 | //that the content isn't being lowered 109 | frame = self.bounds; 110 | frame.origin.y = -clampedHeight; 111 | self.containerView.frame = frame; 112 | 113 | CGFloat midY = floorf(originalHeight * 0.5f); 114 | 115 | // Layout the child views 116 | if (self.searchBar) { 117 | frame = self.searchBar.frame; 118 | frame.size.width = MIN(kTODocumentPickerHeaderMaxWidth, self.bounds.size.width - (self.layoutMargins.left + self.layoutMargins.right)); 119 | frame.size.height = 44.0f; 120 | frame.origin.y = self.layoutMargins.top; 121 | frame.origin.x = CGRectGetMidX(self.bounds) - (frame.size.width * 0.5f); 122 | self.searchBar.frame = CGRectIntegral(frame); 123 | } 124 | 125 | frame = self.sortControl.frame; 126 | frame.size.width = MIN(kTODocumentPickerHeaderMaxWidth, self.bounds.size.width - (self.layoutMargins.left + self.layoutMargins.right)); 127 | frame.origin.x = CGRectGetMidX(self.bounds) - (frame.size.width * 0.5f); 128 | frame.size.height = 33.0f; 129 | if (self.searchBar) { 130 | frame.origin.y = CGRectGetMaxY(self.searchBar.frame) + kTODocumentPickerHeaderViewPadding; 131 | } 132 | else { 133 | frame.origin.y = midY - (frame.size.height * 0.5f); 134 | } 135 | 136 | self.sortControl.frame = CGRectIntegral(frame); 137 | } 138 | 139 | - (void)layoutMarginsDidChange 140 | { 141 | [super layoutMarginsDidChange]; 142 | [self setNeedsLayout]; 143 | } 144 | 145 | #pragma mark - Search Bar Delegate - 146 | - (void)searchBar:(TOSearchBar *)searchBar textDidChange:(NSString *)searchText 147 | { 148 | if (self.searchTextChangedHandler) { 149 | self.searchTextChangedHandler(searchText); 150 | } 151 | } 152 | 153 | - (void)searchBarSearchButtonTapped:(TOSearchBar *)searchBar 154 | { 155 | [self.searchBar resignFirstResponder]; 156 | } 157 | 158 | #pragma mark - External Interactions - 159 | - (void)dismissKeyboard 160 | { 161 | if ([self.searchBar isFirstResponder]) 162 | [self.searchBar resignFirstResponder]; 163 | } 164 | 165 | - (void)setClippingHeight:(CGFloat)clippingHeight 166 | { 167 | //Don't bother doing any layout if the we're beyond the clipping threshold 168 | clippingHeight = MAX(0, clippingHeight); 169 | if (_clippingHeight == clippingHeight) { 170 | return; 171 | } 172 | 173 | _clippingHeight = clippingHeight; 174 | 175 | if (clippingHeight > self.frame.size.height) { 176 | self.hidden = YES; 177 | return; 178 | } 179 | 180 | self.hidden = NO; 181 | [self setNeedsLayout]; 182 | } 183 | 184 | - (void)setShowsSearchBar:(BOOL)showsSearchBar 185 | { 186 | if (_showsSearchBar == showsSearchBar) { 187 | return; 188 | } 189 | 190 | _showsSearchBar = showsSearchBar; 191 | 192 | if (_showsSearchBar) { 193 | self.searchBar = [[TOSearchBar alloc] initWithFrame:(CGRect){0,0,320,44}]; 194 | self.searchBar.delegate = self; 195 | self.searchBar.placeholderText = @"Search"; 196 | [self.containerView addSubview:self.searchBar]; 197 | } 198 | else { 199 | [self.searchBar removeFromSuperview]; 200 | self.searchBar = nil; 201 | } 202 | 203 | [self sizeToFit]; 204 | } 205 | 206 | @end 207 | -------------------------------------------------------------------------------- /TODocumentPickerViewController/Views/TODocumentPickerSegmentedControl.m: -------------------------------------------------------------------------------- 1 | // 2 | // TODocumentPickerSegmentedControl.m 3 | // 4 | // Copyright 2015-2018 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TODocumentPickerSegmentedControl.h" 24 | 25 | #define ARROW_WIDTH 7 26 | #define ARROW_HEIGHT 4 27 | 28 | typedef NS_ENUM(NSInteger, TODocumentPickerSegmentedControlImage) { 29 | TODocumentPickerSegmentedControlImageDefault=0, 30 | TODocumentPickerSegmentedControlImageDescending=1, 31 | TODocumentPickerSegmentedControlImageAscending=2 32 | }; 33 | 34 | @interface TODocumentPickerSegmentedControl () 35 | 36 | @property (nonatomic, readonly) NSArray *segmentedControlImages; 37 | 38 | - (void)segmentedControlTapped:(id)sender; 39 | - (void)updateItemsForCurrentSortType; 40 | 41 | /* Generates an 'off', 'descending' and 'ascending' images of the supplied text */ 42 | + (NSArray *)TO_documentPickerSegmentedControlItemsForTitle:(NSString *)title; 43 | + (void)drawArrowAtPoint:(CGPoint)point ascending:(BOOL)ascending; 44 | 45 | @end 46 | 47 | @implementation TODocumentPickerSegmentedControl 48 | 49 | #pragma mark - Class Creation - 50 | 51 | - (instancetype)init 52 | { 53 | NSArray *items = @[self.segmentedControlImages[0][0], self.segmentedControlImages[1][0], self.segmentedControlImages[2][0]]; 54 | 55 | if (self = [super initWithItems:items]) { 56 | [self addTarget:self action:@selector(segmentedControlTapped:) forControlEvents:UIControlEventValueChanged]; 57 | } 58 | 59 | return self; 60 | } 61 | 62 | - (void)didMoveToSuperview 63 | { 64 | [super didMoveToSuperview]; 65 | 66 | self.selectedSegmentIndex = 0; 67 | [self updateItemsForCurrentSortType]; 68 | } 69 | 70 | #pragma mark - Interaction Detection - 71 | 72 | //Detects when the same segment is tapped twice 73 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 74 | { 75 | NSInteger oldValue = self.selectedSegmentIndex; 76 | [super touchesEnded:touches withEvent:event]; 77 | 78 | if (oldValue == self.selectedSegmentIndex) { 79 | switch (oldValue) { 80 | case 0: 81 | if (self.sortingType == TODocumentPickerSortTypeNameAscending) { 82 | self.sortingType = TODocumentPickerSortTypeNameDescending; 83 | } 84 | else { 85 | self.sortingType = TODocumentPickerSortTypeNameAscending; 86 | } 87 | break; 88 | case 1: 89 | if (self.sortingType == TODocumentPickerSortTypeSizeAscending) { 90 | self.sortingType = TODocumentPickerSortTypeSizeDescending; 91 | } 92 | else { 93 | self.sortingType = TODocumentPickerSortTypeSizeAscending; 94 | } 95 | break; 96 | case 2: 97 | if (self.sortingType == TODocumentPickerSortTypeDateAscending) { 98 | self.sortingType = TODocumentPickerSortTypeDateDescending; 99 | } 100 | else { 101 | self.sortingType = TODocumentPickerSortTypeDateAscending; 102 | } 103 | break; 104 | } 105 | 106 | [self updateItemsForCurrentSortType]; 107 | 108 | if (self.sortTypeChangedHandler) 109 | self.sortTypeChangedHandler(); 110 | } 111 | } 112 | 113 | - (void)segmentedControlTapped:(id)sender 114 | { 115 | switch (self.selectedSegmentIndex) { 116 | case 0: //Name 117 | self.sortingType = TODocumentPickerSortTypeNameAscending; 118 | break; 119 | case 1: //Size 120 | self.sortingType = TODocumentPickerSortTypeSizeAscending; 121 | break; 122 | case 2: //Date 123 | self.sortingType = TODocumentPickerSortTypeDateAscending; 124 | break; 125 | } 126 | 127 | [self updateItemsForCurrentSortType]; 128 | 129 | if (self.sortTypeChangedHandler) 130 | self.sortTypeChangedHandler(); 131 | } 132 | 133 | #pragma mark - Update Items - 134 | - (void)updateItemsForCurrentSortType 135 | { 136 | //Reset all of the items 137 | for (NSInteger i = 0; i < self.segmentedControlImages.count; i++) 138 | [self setImage:[self.segmentedControlImages[i] firstObject] forSegmentAtIndex:i]; 139 | 140 | switch (self.sortingType) { 141 | case TODocumentPickerSortTypeNameAscending: [self setImage:self.segmentedControlImages[0][TODocumentPickerSegmentedControlImageAscending] forSegmentAtIndex:0]; break; 142 | case TODocumentPickerSortTypeNameDescending: [self setImage:self.segmentedControlImages[0][TODocumentPickerSegmentedControlImageDescending] forSegmentAtIndex:0]; break; 143 | case TODocumentPickerSortTypeSizeAscending: [self setImage:self.segmentedControlImages[1][TODocumentPickerSegmentedControlImageAscending] forSegmentAtIndex:1]; break; 144 | case TODocumentPickerSortTypeSizeDescending: [self setImage:self.segmentedControlImages[1][TODocumentPickerSegmentedControlImageDescending] forSegmentAtIndex:1]; break; 145 | case TODocumentPickerSortTypeDateAscending: [self setImage:self.segmentedControlImages[2][TODocumentPickerSegmentedControlImageAscending] forSegmentAtIndex:2]; break; 146 | case TODocumentPickerSortTypeDateDescending: [self setImage:self.segmentedControlImages[2][TODocumentPickerSegmentedControlImageDescending] forSegmentAtIndex:2]; break; 147 | } 148 | } 149 | 150 | #pragma mark - Accessors - 151 | - (NSArray *)segmentedControlImages 152 | { 153 | static NSArray *_segmentedControlImages; 154 | static dispatch_once_t onceToken; 155 | dispatch_once(&onceToken, ^{ 156 | NSMutableArray *segmentedControlImages = [NSMutableArray array]; 157 | [segmentedControlImages addObject:[TODocumentPickerSegmentedControl TO_documentPickerSegmentedControlItemsForTitle:NSLocalizedString(@"Name", @"Segmented Control Name")]]; 158 | [segmentedControlImages addObject:[TODocumentPickerSegmentedControl TO_documentPickerSegmentedControlItemsForTitle:NSLocalizedString(@"Size", @"Segmented Control Size")]]; 159 | [segmentedControlImages addObject:[TODocumentPickerSegmentedControl TO_documentPickerSegmentedControlItemsForTitle:NSLocalizedString(@"Date", @"Segmented Control Date")]]; 160 | _segmentedControlImages = [NSArray arrayWithArray:segmentedControlImages]; 161 | }); 162 | 163 | return _segmentedControlImages; 164 | } 165 | 166 | #pragma mark - Segmented Control Image generation - 167 | 168 | //Annoyingly, [drawInRect:withAttributes] has no easy 'center' option. Sticking to the old way for now 169 | #pragma GCC diagnostic push 170 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 171 | 172 | + (NSArray *)TO_documentPickerSegmentedControlItemsForTitle:(NSString *)title 173 | { 174 | UIFont *font = [UIFont systemFontOfSize:14.0f]; 175 | NSMutableArray *images = [NSMutableArray array]; 176 | 177 | //Work out the size of all of the images 178 | CGSize imageSize = [title sizeWithAttributes:@{NSFontAttributeName:font}]; 179 | imageSize.width += (ARROW_WIDTH + 3) * 2; //Add enough padding on either side so the content is still center 180 | 181 | CGRect frame = (CGRect){CGPointZero, imageSize}; 182 | 183 | for (NSInteger i = 0; i <= 2; i++) { 184 | UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0f); 185 | { 186 | //draw the text 187 | [[UIColor blackColor] set]; 188 | [title drawInRect:frame withFont:font lineBreakMode:NSLineBreakByClipping alignment:NSTextAlignmentCenter]; 189 | 190 | //depending on the iteration, draw the arrow 191 | if (i > 0) { 192 | CGPoint arrowPoint = CGPointZero; 193 | arrowPoint.x = CGRectGetMaxX(frame) - ARROW_WIDTH; 194 | arrowPoint.y = ceil((CGRectGetHeight(frame) - ARROW_HEIGHT) * 0.5f); 195 | 196 | [TODocumentPickerSegmentedControl drawArrowAtPoint:arrowPoint ascending:(i==2)]; 197 | } 198 | 199 | [images addObject:UIGraphicsGetImageFromCurrentImageContext()]; 200 | } 201 | UIGraphicsEndImageContext(); 202 | } 203 | 204 | return images; 205 | } 206 | 207 | #pragma GCC diagnostic pop 208 | 209 | + (void)drawArrowAtPoint:(CGPoint)point ascending:(BOOL)ascending 210 | { 211 | CGRect frame = {(CGPoint)point, {ARROW_WIDTH,ARROW_HEIGHT}}; 212 | 213 | UIBezierPath* arrowPath = UIBezierPath.bezierPath; 214 | if (!ascending) { 215 | [arrowPath moveToPoint: CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame))]; 216 | [arrowPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 7, CGRectGetMinY(frame))]; 217 | [arrowPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 3.5, CGRectGetMinY(frame) + 4)]; 218 | [arrowPath addLineToPoint: CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame))]; 219 | } 220 | else { 221 | [arrowPath moveToPoint: CGPointMake(CGRectGetMinX(frame), CGRectGetMaxY(frame))]; 222 | [arrowPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 7, CGRectGetMaxY(frame))]; 223 | [arrowPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 3.5, CGRectGetMinY(frame))]; 224 | [arrowPath addLineToPoint: CGPointMake(CGRectGetMinX(frame), CGRectGetMaxY(frame))]; 225 | } 226 | 227 | [arrowPath closePath]; 228 | [[UIColor blackColor] setFill]; 229 | [arrowPath fill]; 230 | } 231 | 232 | @end 233 | -------------------------------------------------------------------------------- /TOSearchBar/TOSearchBar+Assets.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOSearchBar+ImageAssets.m 3 | // 4 | // Copyright 2015-2016 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOSearchBar+Assets.h" 24 | @import UIKit; 25 | 26 | /* A statically held map table that holds one instance of every image generated. */ 27 | /* Once all images are released, the map table is also cleaned up */ 28 | static NSMapTable *imageTable = nil; 29 | 30 | static NSString * const kSharedBackgroundKey = @"SharedSearchBackground"; 31 | static NSString * const kSharedSearchIconKey = @"SharedSearchIcon"; 32 | static NSString * const kSharedClearIconKey = @"SharedClearIcon"; 33 | 34 | @implementation TOSearchBar (ImageAssets) 35 | 36 | + (void)setSharedImage:(UIImage *)image forKey:(NSString *)key 37 | { 38 | if (imageTable == nil) { 39 | imageTable = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsWeakMemory capacity:3]; 40 | } 41 | 42 | [imageTable setObject:image forKey:key]; 43 | } 44 | 45 | + (void)cleanUpSharedAssets 46 | { 47 | if (imageTable.count > 0) { 48 | return; 49 | } 50 | 51 | imageTable = nil; 52 | } 53 | 54 | + (UIImage *)sharedSearchBarBackground 55 | { 56 | UIImage *image = [imageTable objectForKey:kSharedBackgroundKey]; 57 | if (image) { 58 | return image; 59 | } 60 | 61 | CGFloat cornerRadius = 4.5f; 62 | 63 | if (@available(iOS 11.0, *)) { 64 | cornerRadius = 15.0f; 65 | } 66 | 67 | CGRect frame = (CGRect){0, 0, (cornerRadius*2.0f) + 1, (cornerRadius*2.0f) + 1}; 68 | UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0f); 69 | UIBezierPath *path = [TOSearchBar bezierPathWithIOS7RoundedRect:frame cornerRadius:cornerRadius]; 70 | [[UIColor blackColor] set]; 71 | [path fill]; 72 | image = UIGraphicsGetImageFromCurrentImageContext(); 73 | UIGraphicsEndImageContext(); 74 | 75 | image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 76 | image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius)]; 77 | 78 | [TOSearchBar setSharedImage:image forKey:kSharedBackgroundKey]; 79 | 80 | return image; 81 | } 82 | 83 | + (UIImage *)sharedSearchIcon 84 | { 85 | UIImage *image = [imageTable objectForKey:kSharedSearchIconKey]; 86 | if (image) { 87 | return image; 88 | } 89 | 90 | if (@available(iOS 11.0, *)) { 91 | UIGraphicsBeginImageContextWithOptions((CGSize){14,14}, NO, 0.0f); 92 | 93 | CGContextRef context = UIGraphicsGetCurrentContext(); 94 | 95 | //// Circle Drawing 96 | UIBezierPath* circlePath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(0.55, 0.55, 9.95, 9.95)]; 97 | CGContextSaveGState(context); 98 | [circlePath addClip]; 99 | CGContextTranslateCTM(context, 1, 86); 100 | CGContextScaleCTM(context, 1, -1); 101 | CGContextTranslateCTM(context, 0, -image.size.height); 102 | CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage); 103 | CGContextRestoreGState(context); 104 | [UIColor.blackColor setStroke]; 105 | circlePath.lineWidth = 1.1; 106 | [circlePath stroke]; 107 | 108 | 109 | //// Handle Drawing 110 | UIBezierPath* handlePath = [UIBezierPath bezierPath]; 111 | [handlePath moveToPoint: CGPointMake(13.21, 12.2)]; 112 | [handlePath addLineToPoint: CGPointMake(13.21, 12.2)]; 113 | [handlePath addCurveToPoint: CGPointMake(13.74, 12.73) controlPoint1: CGPointMake(13.74, 12.73) controlPoint2: CGPointMake(13.74, 12.73)]; 114 | [handlePath addLineToPoint: CGPointMake(13.74, 12.73)]; 115 | [handlePath addCurveToPoint: CGPointMake(13.74, 13.78) controlPoint1: CGPointMake(14.03, 13.02) controlPoint2: CGPointMake(14.03, 13.49)]; 116 | [handlePath addLineToPoint: CGPointMake(13.74, 13.78)]; 117 | [handlePath addLineToPoint: CGPointMake(13.74, 13.78)]; 118 | [handlePath addLineToPoint: CGPointMake(13.74, 13.78)]; 119 | [handlePath addLineToPoint: CGPointMake(13.74, 13.78)]; 120 | [handlePath addLineToPoint: CGPointMake(13.74, 13.78)]; 121 | [handlePath addCurveToPoint: CGPointMake(12.68, 13.78) controlPoint1: CGPointMake(13.45, 14.07) controlPoint2: CGPointMake(12.97, 14.07)]; 122 | [handlePath addLineToPoint: CGPointMake(12.68, 13.78)]; 123 | [handlePath addLineToPoint: CGPointMake(12.68, 13.78)]; 124 | [handlePath addLineToPoint: CGPointMake(8.33, 9.46)]; 125 | [handlePath addLineToPoint: CGPointMake(9.38, 8.41)]; 126 | [handlePath addLineToPoint: CGPointMake(13.21, 12.2)]; 127 | [handlePath closePath]; 128 | [UIColor.blackColor setFill]; 129 | [handlePath fill]; 130 | 131 | image = UIGraphicsGetImageFromCurrentImageContext(); 132 | UIGraphicsEndImageContext(); 133 | } 134 | else { // iOS 10 and down 135 | UIGraphicsBeginImageContextWithOptions((CGSize){15,15}, NO, 0.0f); 136 | 137 | //// General Declarations 138 | CGContextRef context = UIGraphicsGetCurrentContext(); 139 | 140 | //// Oval Drawing 141 | UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(1, 1, 10, 10)]; 142 | [UIColor.grayColor setStroke]; 143 | ovalPath.lineWidth = 1.3; 144 | [ovalPath stroke]; 145 | 146 | 147 | //// Rectangle Drawing 148 | CGContextSaveGState(context); 149 | CGContextTranslateCTM(context, 8.8, 9.87); 150 | CGContextRotateCTM(context, -45 * M_PI / 180); 151 | 152 | UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(0, 0, 1.4, 6.55)]; 153 | [UIColor.grayColor setFill]; 154 | [rectanglePath fill]; 155 | 156 | CGContextRestoreGState(context); 157 | 158 | image = UIGraphicsGetImageFromCurrentImageContext(); 159 | UIGraphicsEndImageContext(); 160 | } 161 | 162 | 163 | image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 164 | 165 | return image; 166 | } 167 | 168 | + (UIImage *)sharedClearIcon 169 | { 170 | UIImage *image = [imageTable objectForKey:kSharedClearIconKey]; 171 | if (image) { 172 | return image; 173 | } 174 | 175 | if (@available(iOS 11.0, *)) { 176 | UIGraphicsBeginImageContextWithOptions((CGSize){16,16}, NO, 0.0f); 177 | 178 | //// Bezier 2 Drawing 179 | UIBezierPath* deletePath = [UIBezierPath bezierPath]; 180 | [deletePath moveToPoint: CGPointMake(10.96, 4.31)]; 181 | [deletePath addCurveToPoint: CGPointMake(10.47, 4.53) controlPoint1: CGPointMake(10.76, 4.33) controlPoint2: CGPointMake(10.6, 4.4)]; 182 | [deletePath addCurveToPoint: CGPointMake(8, 7) controlPoint1: CGPointMake(10.47, 4.53) controlPoint2: CGPointMake(10.47, 4.53)]; 183 | [deletePath addCurveToPoint: CGPointMake(5.53, 4.53) controlPoint1: CGPointMake(7.15, 6.15) controlPoint2: CGPointMake(5.53, 4.53)]; 184 | [deletePath addCurveToPoint: CGPointMake(4.89, 4.32) controlPoint1: CGPointMake(5.35, 4.35) controlPoint2: CGPointMake(5.11, 4.28)]; 185 | [deletePath addCurveToPoint: CGPointMake(4.47, 4.53) controlPoint1: CGPointMake(4.73, 4.34) controlPoint2: CGPointMake(4.59, 4.41)]; 186 | [deletePath addCurveToPoint: CGPointMake(4.47, 5.59) controlPoint1: CGPointMake(4.18, 4.82) controlPoint2: CGPointMake(4.18, 5.3)]; 187 | [deletePath addCurveToPoint: CGPointMake(6.94, 8.06) controlPoint1: CGPointMake(4.47, 5.59) controlPoint2: CGPointMake(4.47, 5.59)]; 188 | [deletePath addCurveToPoint: CGPointMake(5.85, 9.15) controlPoint1: CGPointMake(6.63, 8.37) controlPoint2: CGPointMake(6.23, 8.77)]; 189 | [deletePath addCurveToPoint: CGPointMake(4.53, 10.47) controlPoint1: CGPointMake(5.17, 9.83) controlPoint2: CGPointMake(4.53, 10.47)]; 190 | [deletePath addCurveToPoint: CGPointMake(4.53, 11.53) controlPoint1: CGPointMake(4.24, 10.76) controlPoint2: CGPointMake(4.24, 11.24)]; 191 | [deletePath addCurveToPoint: CGPointMake(5.59, 11.53) controlPoint1: CGPointMake(4.82, 11.82) controlPoint2: CGPointMake(5.3, 11.82)]; 192 | [deletePath addCurveToPoint: CGPointMake(8, 9.12) controlPoint1: CGPointMake(5.59, 11.53) controlPoint2: CGPointMake(5.59, 11.53)]; 193 | [deletePath addCurveToPoint: CGPointMake(8.43, 9.55) controlPoint1: CGPointMake(8.13, 9.25) controlPoint2: CGPointMake(8.28, 9.4)]; 194 | [deletePath addCurveToPoint: CGPointMake(10.41, 11.53) controlPoint1: CGPointMake(9.29, 10.41) controlPoint2: CGPointMake(10.41, 11.53)]; 195 | [deletePath addCurveToPoint: CGPointMake(11.47, 11.53) controlPoint1: CGPointMake(10.7, 11.82) controlPoint2: CGPointMake(11.18, 11.82)]; 196 | [deletePath addCurveToPoint: CGPointMake(11.47, 10.47) controlPoint1: CGPointMake(11.76, 11.24) controlPoint2: CGPointMake(11.76, 10.76)]; 197 | [deletePath addCurveToPoint: CGPointMake(9.06, 8.06) controlPoint1: CGPointMake(11.47, 10.47) controlPoint2: CGPointMake(11.47, 10.47)]; 198 | [deletePath addCurveToPoint: CGPointMake(11.53, 5.59) controlPoint1: CGPointMake(9.91, 7.21) controlPoint2: CGPointMake(11.53, 5.59)]; 199 | [deletePath addCurveToPoint: CGPointMake(11.53, 4.53) controlPoint1: CGPointMake(11.81, 5.31) controlPoint2: CGPointMake(11.82, 4.85)]; 200 | [deletePath addCurveToPoint: CGPointMake(10.92, 4.31) controlPoint1: CGPointMake(11.34, 4.36) controlPoint2: CGPointMake(11.13, 4.29)]; 201 | [deletePath addLineToPoint: CGPointMake(10.96, 4.31)]; 202 | [deletePath closePath]; 203 | [deletePath moveToPoint: CGPointMake(16, 8)]; 204 | [deletePath addCurveToPoint: CGPointMake(8, 16) controlPoint1: CGPointMake(16, 12.42) controlPoint2: CGPointMake(12.42, 16)]; 205 | [deletePath addCurveToPoint: CGPointMake(0, 8) controlPoint1: CGPointMake(3.58, 16) controlPoint2: CGPointMake(0, 12.42)]; 206 | [deletePath addCurveToPoint: CGPointMake(3.13, 1.65) controlPoint1: CGPointMake(0, 5.41) controlPoint2: CGPointMake(1.23, 3.11)]; 207 | [deletePath addCurveToPoint: CGPointMake(8, 0) controlPoint1: CGPointMake(4.48, 0.61) controlPoint2: CGPointMake(6.17, 0)]; 208 | [deletePath addCurveToPoint: CGPointMake(16, 8) controlPoint1: CGPointMake(12.42, 0) controlPoint2: CGPointMake(16, 3.58)]; 209 | [deletePath closePath]; 210 | [UIColor.blackColor setFill]; 211 | [deletePath fill]; 212 | 213 | image = UIGraphicsGetImageFromCurrentImageContext(); 214 | UIGraphicsEndImageContext(); 215 | 216 | } 217 | else { 218 | UIGraphicsBeginImageContextWithOptions((CGSize){14,14}, NO, 0.0f); 219 | 220 | //// ClearIcon Drawing 221 | UIBezierPath* clearIconPath = [UIBezierPath bezierPath]; 222 | [clearIconPath moveToPoint: CGPointMake(4.13, 4.12)]; 223 | [clearIconPath addCurveToPoint: CGPointMake(4.08, 5.23) controlPoint1: CGPointMake(3.79, 4.46) controlPoint2: CGPointMake(3.79, 4.94)]; 224 | [clearIconPath addCurveToPoint: CGPointMake(5.85, 7) controlPoint1: CGPointMake(4.08, 5.23) controlPoint2: CGPointMake(4.08, 5.23)]; 225 | [clearIconPath addLineToPoint: CGPointMake(4.08, 8.77)]; 226 | [clearIconPath addCurveToPoint: CGPointMake(4.08, 9.83) controlPoint1: CGPointMake(3.79, 9.06) controlPoint2: CGPointMake(3.79, 9.54)]; 227 | [clearIconPath addCurveToPoint: CGPointMake(5.14, 9.83) controlPoint1: CGPointMake(4.37, 10.12) controlPoint2: CGPointMake(4.85, 10.12)]; 228 | [clearIconPath addLineToPoint: CGPointMake(6.91, 8.06)]; 229 | [clearIconPath addCurveToPoint: CGPointMake(8.67, 9.83) controlPoint1: CGPointMake(7.59, 8.74) controlPoint2: CGPointMake(8.67, 9.83)]; 230 | [clearIconPath addCurveToPoint: CGPointMake(9.74, 9.83) controlPoint1: CGPointMake(8.97, 10.12) controlPoint2: CGPointMake(9.44, 10.12)]; 231 | [clearIconPath addCurveToPoint: CGPointMake(9.74, 8.77) controlPoint1: CGPointMake(10.03, 9.54) controlPoint2: CGPointMake(10.03, 9.06)]; 232 | [clearIconPath addCurveToPoint: CGPointMake(7.97, 7) controlPoint1: CGPointMake(9.74, 8.77) controlPoint2: CGPointMake(9.74, 8.77)]; 233 | [clearIconPath addLineToPoint: CGPointMake(9.74, 5.23)]; 234 | [clearIconPath addCurveToPoint: CGPointMake(9.74, 4.17) controlPoint1: CGPointMake(10.03, 4.94) controlPoint2: CGPointMake(10.03, 4.46)]; 235 | [clearIconPath addCurveToPoint: CGPointMake(8.67, 4.17) controlPoint1: CGPointMake(9.44, 3.88) controlPoint2: CGPointMake(8.97, 3.88)]; 236 | [clearIconPath addLineToPoint: CGPointMake(6.91, 5.94)]; 237 | [clearIconPath addCurveToPoint: CGPointMake(5.14, 4.17) controlPoint1: CGPointMake(6.23, 5.26) controlPoint2: CGPointMake(5.14, 4.17)]; 238 | [clearIconPath addCurveToPoint: CGPointMake(4.11, 4.14) controlPoint1: CGPointMake(4.85, 3.88) controlPoint2: CGPointMake(4.37, 3.88)]; 239 | [clearIconPath addLineToPoint: CGPointMake(4.13, 4.12)]; 240 | [clearIconPath closePath]; 241 | [clearIconPath moveToPoint: CGPointMake(14, 7)]; 242 | [clearIconPath addCurveToPoint: CGPointMake(7, 14) controlPoint1: CGPointMake(14, 10.87) controlPoint2: CGPointMake(10.87, 14)]; 243 | [clearIconPath addCurveToPoint: CGPointMake(0, 7) controlPoint1: CGPointMake(3.13, 14) controlPoint2: CGPointMake(0, 10.87)]; 244 | [clearIconPath addCurveToPoint: CGPointMake(2.74, 1.44) controlPoint1: CGPointMake(0, 4.74) controlPoint2: CGPointMake(1.08, 2.72)]; 245 | [clearIconPath addCurveToPoint: CGPointMake(7, 0) controlPoint1: CGPointMake(3.92, 0.54) controlPoint2: CGPointMake(5.4, -0)]; 246 | [clearIconPath addCurveToPoint: CGPointMake(14, 7) controlPoint1: CGPointMake(10.87, 0) controlPoint2: CGPointMake(14, 3.13)]; 247 | [clearIconPath closePath]; 248 | [UIColor.grayColor setFill]; 249 | [clearIconPath fill]; 250 | 251 | image = UIGraphicsGetImageFromCurrentImageContext(); 252 | UIGraphicsEndImageContext(); 253 | } 254 | 255 | image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 256 | 257 | return image; 258 | } 259 | 260 | // https://www.paintcodeapp.com/blogpost/code-for-ios-7-rounded-rectangles 261 | 262 | #define TOP_LEFT(X, Y)\ 263 | CGPointMake(rect.origin.x + X * limitedRadius,\ 264 | rect.origin.y + Y * limitedRadius) 265 | #define TOP_RIGHT(X, Y)\ 266 | CGPointMake(rect.origin.x + rect.size.width - X * limitedRadius,\ 267 | rect.origin.y + Y * limitedRadius) 268 | #define BOTTOM_RIGHT(X, Y)\ 269 | CGPointMake(rect.origin.x + rect.size.width - X * limitedRadius,\ 270 | rect.origin.y + rect.size.height - Y * limitedRadius) 271 | #define BOTTOM_LEFT(X, Y)\ 272 | CGPointMake(rect.origin.x + X * limitedRadius,\ 273 | rect.origin.y + rect.size.height - Y * limitedRadius) 274 | 275 | 276 | + (UIBezierPath*)bezierPathWithIOS7RoundedRect: (CGRect)rect 277 | cornerRadius: (CGFloat)radius 278 | { 279 | UIBezierPath* path = UIBezierPath.bezierPath; 280 | CGFloat limit = MIN(rect.size.width, rect.size.height) / 2 / 1.52866483; 281 | CGFloat limitedRadius = MIN(radius, limit); 282 | 283 | [path moveToPoint: TOP_LEFT(1.52866483, 0.00000000)]; 284 | [path addLineToPoint: TOP_RIGHT(1.52866471, 0.00000000)]; 285 | [path addCurveToPoint: TOP_RIGHT(0.66993427, 0.06549600) 286 | controlPoint1: TOP_RIGHT(1.08849323, 0.00000000) 287 | controlPoint2: TOP_RIGHT(0.86840689, 0.00000000)]; 288 | [path addLineToPoint: TOP_RIGHT(0.63149399, 0.07491100)]; 289 | [path addCurveToPoint: TOP_RIGHT(0.07491176, 0.63149399) 290 | controlPoint1: TOP_RIGHT(0.37282392, 0.16905899) 291 | controlPoint2: TOP_RIGHT(0.16906013, 0.37282401)]; 292 | [path addCurveToPoint: TOP_RIGHT(0.00000000, 1.52866483) 293 | controlPoint1: TOP_RIGHT(0.00000000, 0.86840701) 294 | controlPoint2: TOP_RIGHT(0.00000000, 1.08849299)]; 295 | [path addLineToPoint: BOTTOM_RIGHT(0.00000000, 1.52866471)]; 296 | [path addCurveToPoint: BOTTOM_RIGHT(0.06549569, 0.66993493) 297 | controlPoint1: BOTTOM_RIGHT(0.00000000, 1.08849323) 298 | controlPoint2: BOTTOM_RIGHT(0.00000000, 0.86840689)]; 299 | [path addLineToPoint: BOTTOM_RIGHT(0.07491111, 0.63149399)]; 300 | [path addCurveToPoint: BOTTOM_RIGHT(0.63149399, 0.07491111) 301 | controlPoint1: BOTTOM_RIGHT(0.16905883, 0.37282392) 302 | controlPoint2: BOTTOM_RIGHT(0.37282392, 0.16905883)]; 303 | [path addCurveToPoint: BOTTOM_RIGHT(1.52866471, 0.00000000) 304 | controlPoint1: BOTTOM_RIGHT(0.86840689, 0.00000000) 305 | controlPoint2: BOTTOM_RIGHT(1.08849323, 0.00000000)]; 306 | [path addLineToPoint: BOTTOM_LEFT(1.52866483, 0.00000000)]; 307 | [path addCurveToPoint: BOTTOM_LEFT(0.66993397, 0.06549569) 308 | controlPoint1: BOTTOM_LEFT(1.08849299, 0.00000000) 309 | controlPoint2: BOTTOM_LEFT(0.86840701, 0.00000000)]; 310 | [path addLineToPoint: BOTTOM_LEFT(0.63149399, 0.07491111)]; 311 | [path addCurveToPoint: BOTTOM_LEFT(0.07491100, 0.63149399) 312 | controlPoint1: BOTTOM_LEFT(0.37282401, 0.16905883) 313 | controlPoint2: BOTTOM_LEFT(0.16906001, 0.37282392)]; 314 | [path addCurveToPoint: BOTTOM_LEFT(0.00000000, 1.52866471) 315 | controlPoint1: BOTTOM_LEFT(0.00000000, 0.86840689) 316 | controlPoint2: BOTTOM_LEFT(0.00000000, 1.08849323)]; 317 | [path addLineToPoint: TOP_LEFT(0.00000000, 1.52866483)]; 318 | [path addCurveToPoint: TOP_LEFT(0.06549600, 0.66993397) 319 | controlPoint1: TOP_LEFT(0.00000000, 1.08849299) 320 | controlPoint2: TOP_LEFT(0.00000000, 0.86840701)]; 321 | [path addLineToPoint: TOP_LEFT(0.07491100, 0.63149399)]; 322 | [path addCurveToPoint: TOP_LEFT(0.63149399, 0.07491100) 323 | controlPoint1: TOP_LEFT(0.16906001, 0.37282401) 324 | controlPoint2: TOP_LEFT(0.37282401, 0.16906001)]; 325 | [path addCurveToPoint: TOP_LEFT(1.52866483, 0.00000000) 326 | controlPoint1: TOP_LEFT(0.86840701, 0.00000000) 327 | controlPoint2: TOP_LEFT(1.08849299, 0.00000000)]; 328 | [path closePath]; 329 | return path; 330 | } 331 | 332 | @end 333 | -------------------------------------------------------------------------------- /TOSearchBar/TOSearchBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOSearchBar.m 3 | // 4 | // Copyright 2015-2016 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOSearchBar.h" 24 | #import "TOSearchBar+Assets.h" 25 | 26 | static const CGFloat kTOSearchBarInset = 8.0f; // inset from inside the bar 27 | 28 | // iOS 10 and down 29 | static const CGFloat kTOSearchBarIconMarginClassic = 5.0f; // spacing between icon and placeholder 30 | static const CGFloat kTOSearchBarFontSizeClassic = 15.0f; 31 | static const CGFloat kTOSearchBarBackgroundHeightClassic = 28.0f; 32 | 33 | // iOS 11 style constants 34 | static const CGFloat kTOSearchBarIconMarginModern = 7.0f; 35 | static const CGFloat kTOSearchBarFontSizeModern = 17.0f; 36 | static const CGFloat kTOSearchBarBackgroundHeightModern = 36.0f; 37 | 38 | @interface TOSearchBar () 39 | 40 | // UI components 41 | @property (nonatomic, strong, readwrite) UIImageView *barBackgroundView; 42 | @property (nonatomic, strong, readwrite) UIView *containerView; 43 | @property (nonatomic, strong, readwrite) UILabel *placeholderLabel; 44 | @property (nonatomic, strong, readwrite) UITextField *searchTextField; 45 | @property (nonatomic, strong, readwrite) UIButton *cancelButton; 46 | @property (nonatomic, strong, readwrite) UIButton *clearButton; 47 | @property (nonatomic, strong, readwrite) UIImageView *iconView; 48 | 49 | // State 50 | @property (nonatomic, readonly) BOOL centerTextLabel; 51 | 52 | @end 53 | 54 | @implementation TOSearchBar 55 | 56 | @synthesize barBackgroundTintColor = _barBackgroundTintColor; 57 | 58 | - (instancetype)initWithFrame:(CGRect)frame style:(TOSearchBarStyle)style 59 | { 60 | if (self = [super initWithFrame:frame]) { 61 | _style = style; 62 | } 63 | 64 | return self; 65 | } 66 | 67 | - (instancetype)initWithStyle:(TOSearchBarStyle)style 68 | { 69 | if (self = [super initWithFrame:CGRectZero]) { 70 | _style = style; 71 | } 72 | 73 | return self; 74 | } 75 | 76 | - (void)dealloc 77 | { 78 | [TOSearchBar cleanUpSharedAssets]; 79 | } 80 | 81 | #pragma mark - View Lifecycle - 82 | - (void)didMoveToSuperview 83 | { 84 | [super didMoveToSuperview]; 85 | if (self.superview != nil) { 86 | [self setUpViews]; 87 | } 88 | } 89 | 90 | #pragma mark - View Set-up - 91 | - (void)setUpViews 92 | { 93 | self.clipsToBounds = YES; 94 | self.userInteractionEnabled = YES; 95 | 96 | [self setUpBackgroundViews]; 97 | [self setUpPlaceholderViews]; 98 | [self setUpButtons]; 99 | [self setUpTextField]; 100 | [self setUpGestureRecognizers]; 101 | 102 | [self configureThemeForCurrentStyle]; 103 | } 104 | 105 | - (void)setUpBackgroundViews 106 | { 107 | if (self.barBackgroundView == nil) { 108 | self.barBackgroundView = [[UIImageView alloc] initWithImage:[TOSearchBar sharedSearchBarBackground]]; 109 | } 110 | self.barBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | 111 | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth; 112 | self.barBackgroundView.tintColor = self.barBackgroundTintColor; 113 | self.barBackgroundView.userInteractionEnabled = NO; 114 | [self addSubview:self.barBackgroundView]; 115 | 116 | if (self.containerView == nil) { 117 | self.containerView = [[UIView alloc] initWithFrame:self.barBackgroundView.frame]; 118 | } 119 | self.containerView.autoresizingMask = self.barBackgroundView.autoresizingMask; 120 | [self addSubview:self.containerView]; 121 | } 122 | 123 | - (void)setUpPlaceholderViews 124 | { 125 | // Set up the placeholder label view 126 | if (self.placeholderLabel == nil) { 127 | self.placeholderLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 128 | } 129 | 130 | CGFloat fontSize = kTOSearchBarFontSizeClassic; 131 | if (@available(iOS 11.0, *)) { 132 | fontSize = kTOSearchBarFontSizeModern; 133 | } 134 | self.placeholderLabel.font = [UIFont systemFontOfSize:fontSize]; 135 | self.placeholderLabel.text = NSLocalizedStringFromTableInBundle(@"Search", 136 | @"TOSearchBarLocalizable", 137 | [TOSearchBar bundle], 138 | nil); 139 | [self.placeholderLabel sizeToFit]; 140 | [self.containerView addSubview:self.placeholderLabel]; 141 | 142 | // Set up the accessory icon view 143 | if (self.iconView == nil) { 144 | self.iconView = [[UIImageView alloc] initWithImage:[TOSearchBar sharedSearchIcon]]; 145 | } 146 | [self.containerView addSubview:self.iconView]; 147 | } 148 | 149 | - (void)setUpTextField 150 | { 151 | if (self.searchTextField == nil) { 152 | self.searchTextField = [[UITextField alloc] initWithFrame:self.containerView.bounds]; 153 | } 154 | 155 | CGFloat fontSize = kTOSearchBarFontSizeClassic; 156 | if (@available(iOS 11.0, *)) { 157 | fontSize = kTOSearchBarFontSizeModern; 158 | } 159 | self.searchTextField.userInteractionEnabled = YES; 160 | self.searchTextField.backgroundColor = [UIColor clearColor]; 161 | self.searchTextField.font = [UIFont systemFontOfSize:fontSize]; 162 | self.searchTextField.delegate = self; 163 | self.searchTextField.returnKeyType = UIReturnKeySearch; 164 | [self.searchTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; 165 | [self.containerView addSubview:self.searchTextField]; 166 | } 167 | 168 | - (void)setUpButtons 169 | { 170 | CGFloat iconMargin = kTOSearchBarIconMarginClassic; 171 | if (@available(iOS 11.0, *)) { 172 | iconMargin = kTOSearchBarIconMarginModern; 173 | } 174 | 175 | if (self.showsCancelButton && self.cancelButton == nil) { 176 | self.cancelButton = [UIButton buttonWithType:UIButtonTypeSystem]; 177 | } 178 | [self.cancelButton setTitle:NSLocalizedStringFromTableInBundle(@"Cancel", 179 | @"TOSearchBarLocalizable", 180 | [TOSearchBar bundle], 181 | nil) forState:UIControlStateNormal]; 182 | self.cancelButton.titleLabel.font = [UIFont systemFontOfSize:17.0f]; 183 | self.cancelButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 184 | [self.cancelButton addTarget:self action:@selector(cancelButttonTapped:) forControlEvents:UIControlEventTouchUpInside]; 185 | [self.cancelButton sizeToFit]; 186 | self.cancelButton.frame = CGRectInset(self.cancelButton.frame, -iconMargin, 0.0f); 187 | [self addSubview:self.cancelButton]; 188 | 189 | if (self.clearButton) { 190 | return; 191 | } 192 | 193 | UIImage *clearButtonImage = [TOSearchBar sharedClearIcon]; 194 | self.clearButton = [UIButton buttonWithType:UIButtonTypeSystem]; 195 | [self.clearButton setImage:clearButtonImage forState:UIControlStateNormal]; 196 | self.clearButton.frame = (CGRect){CGPointZero, {44.0f, 44.0f}}; 197 | self.clearButton.enabled = NO; 198 | self.clearButton.hidden = YES; 199 | [self.clearButton addTarget:self action:@selector(clearButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; 200 | [self.containerView addSubview:self.clearButton]; 201 | } 202 | 203 | - (void)setUpGestureRecognizers 204 | { 205 | // Add recognizers to the text field itself (as it captures exclusive access to the view if tapped) 206 | [self.searchTextField addTarget:self action:@selector(didTapEnter:) forControlEvents:UIControlEventTouchDown]; 207 | [self.searchTextField addTarget:self action:@selector(didTapUp:) forControlEvents:UIControlEventTouchUpInside]; 208 | [self.searchTextField addTarget:self action:@selector(didTapExit:) forControlEvents:UIControlEventTouchCancel]; 209 | [self.searchTextField addTarget:self action:@selector(didTapExit:) forControlEvents:UIControlEventTouchDragExit]; 210 | [self.searchTextField addTarget:self action:@selector(didTapEnter:) forControlEvents:UIControlEventTouchDragEnter]; 211 | } 212 | 213 | #pragma mark - View Management - 214 | - (void)layoutSubviews 215 | { 216 | CGRect frame; 217 | CGSize clearImageSize = self.clearButton.imageView.image.size; 218 | 219 | CGFloat iconMargin = kTOSearchBarIconMarginClassic; 220 | if (@available(iOS 11.0, *)) { 221 | iconMargin = kTOSearchBarIconMarginModern; 222 | } 223 | 224 | if (self.cancelButton) { 225 | self.cancelButton.alpha = self.editing ? 1.0f : 0.0f; 226 | frame = self.cancelButton.frame; 227 | frame.origin.y = (CGRectGetHeight(self.frame) - frame.size.height) * 0.5f; 228 | if (self.editing) { 229 | frame.origin.x = (CGRectGetWidth(self.frame) - (frame.size.width + self.horizontalInset)); 230 | } 231 | else { 232 | frame.origin.x = CGRectGetWidth(self.frame); 233 | } 234 | self.cancelButton.frame = frame; 235 | } 236 | 237 | // Layout the background view (and content container) 238 | frame = self.barBackgroundView.frame; 239 | frame.size.width = (self.frame.size.width) - (self.horizontalInset * 2.0f); 240 | frame.size.height = kTOSearchBarBackgroundHeightClassic; 241 | if (@available(iOS 11.0, *)) { 242 | frame.size.height = kTOSearchBarBackgroundHeightModern; 243 | } 244 | 245 | if (self.editing && self.cancelButton) { frame.size.width -= self.cancelButton.frame.size.width; } 246 | frame.origin.x = self.horizontalInset; 247 | frame.origin.y = floorf((self.frame.size.height - frame.size.height) * 0.5f); 248 | self.barBackgroundView.frame = frame; 249 | self.containerView.frame = frame; 250 | 251 | // layout the place holder label 252 | frame = self.placeholderLabel.frame; 253 | if (self.centerTextLabel == NO) { 254 | frame.origin.x = (kTOSearchBarInset) + self.iconView.frame.size.width; 255 | if (@available(iOS 11.0, *)) { 256 | frame.origin.x += kTOSearchBarIconMarginModern; 257 | } 258 | else { 259 | frame.origin.x += kTOSearchBarIconMarginClassic; 260 | } 261 | } 262 | else { 263 | frame.origin.x = floorf((CGRectGetWidth(self.containerView.frame) - CGRectGetWidth(frame)) * 0.5f); 264 | } 265 | frame.origin.y = floorf((CGRectGetHeight(self.containerView.frame) - CGRectGetHeight(frame)) * 0.5f); 266 | self.placeholderLabel.frame = frame; 267 | self.placeholderLabel.hidden = self.hasSearchText; 268 | 269 | // layout the icon 270 | frame = self.iconView.frame; 271 | if (self.editing || self.hasSearchText) { 272 | frame.origin.x = kTOSearchBarInset; 273 | } 274 | else { 275 | frame.origin.x = CGRectGetMinX(self.placeholderLabel.frame) - (CGRectGetWidth(self.iconView.frame) + iconMargin); 276 | } 277 | frame.origin.y = floorf(CGRectGetMidY(self.placeholderLabel.frame) - (CGRectGetHeight(self.iconView.frame) * 0.5f)); 278 | self.iconView.frame = frame; 279 | 280 | // lay out the text field 281 | frame = self.searchTextField.frame; 282 | frame.size = self.containerView.frame.size; 283 | frame.size.width = CGRectGetWidth(frame) - (self.placeholderLabel.frame.origin.x + iconMargin + clearImageSize.width); 284 | frame.origin.x = self.placeholderLabel.frame.origin.x; 285 | frame.origin.y = 0.0f; 286 | self.searchTextField.frame = frame; 287 | 288 | // layout the clear button 289 | CGPoint center = CGPointZero; 290 | center.x = (CGRectGetWidth(self.containerView.frame) - (kTOSearchBarInset + (clearImageSize.width * 0.5f))); 291 | center.y = (CGRectGetHeight(self.containerView.frame) * 0.5f); 292 | self.clearButton.center = center; 293 | } 294 | 295 | #pragma mark - Theme Management - 296 | - (void)configureThemeForCurrentStyle 297 | { 298 | BOOL darkMode = (self.style == TOSearchBarStyleDark); 299 | 300 | if (darkMode) { 301 | self.barBackgroundTintColor = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.065f]; 302 | self.selectedBarBackgroundTintColor = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.15f]; 303 | self.placeholderTintColor = [UIColor colorWithWhite:0.4f alpha:1.0f]; 304 | self.searchTextField.textColor = [UIColor whiteColor]; 305 | self.searchTextField.text = self.searchTextField.text; // Force the color to update 306 | self.clearButton.tintColor = [UIColor colorWithWhite:0.45f alpha:1.0f]; 307 | self.searchTextField.keyboardAppearance = UIKeyboardAppearanceDark; 308 | return; 309 | } 310 | 311 | self.searchTextField.textColor = [UIColor blackColor]; 312 | self.searchTextField.text = self.searchTextField.text; // Force the color to update 313 | 314 | self.placeholderTintColor = [UIColor colorWithRed:0.556863f green:0.556863f blue:0.576471f alpha:1.0f]; 315 | self.clearButton.tintColor = [UIColor colorWithRed:0.556863f green:0.556863f blue:0.576471f alpha:1.0f]; 316 | 317 | if (@available(iOS 11.0, *)) { 318 | self.barBackgroundTintColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.03922f alpha:0.055f]; 319 | self.selectedBarBackgroundTintColor = [UIColor colorWithRed:0.04313f green:0.0392f blue:0.11765f alpha:0.2f]; 320 | } 321 | else { 322 | self.barBackgroundTintColor = [UIColor colorWithRed:0.0f green:0.05f blue:0.13f alpha:0.083f]; 323 | } 324 | self.searchTextField.keyboardAppearance = UIKeyboardAppearanceLight; 325 | } 326 | 327 | #pragma mark - Event Handling - 328 | 329 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 330 | { 331 | return !(self.searchTextField.isFirstResponder); 332 | } 333 | 334 | - (void)clearButtonTapped:(id)sender 335 | { 336 | self.text = nil; 337 | self.clearButton.enabled = NO; 338 | self.placeholderLabel.hidden = NO; 339 | [self setClearButtonHidden:YES animated:YES]; 340 | [self becomeFirstResponder]; 341 | 342 | if ([self.delegate respondsToSelector:@selector(searchBarClearButtonTapped:)]) { 343 | [self.delegate searchBarClearButtonTapped:self]; 344 | } 345 | 346 | if ([self.delegate respondsToSelector:@selector(searchBar:textDidChange:)]) { 347 | [self.delegate searchBar:self textDidChange:self.text]; 348 | } 349 | } 350 | 351 | - (void)cancelButttonTapped:(id)sender 352 | { 353 | [self resignFirstResponder]; 354 | } 355 | 356 | - (BOOL)becomeFirstResponder 357 | { 358 | [self setEditing:YES animated:YES]; 359 | return [self.searchTextField becomeFirstResponder]; 360 | } 361 | 362 | - (BOOL)isFirstResponder 363 | { 364 | return self.searchTextField.isFirstResponder; 365 | } 366 | 367 | - (BOOL)resignFirstResponder 368 | { 369 | [self.searchTextField resignFirstResponder]; 370 | [self setEditing:NO animated:YES]; 371 | return [super resignFirstResponder]; 372 | } 373 | 374 | - (void)didTapEnter:(id)sender 375 | { 376 | if (self.searchTextField.isFirstResponder) { return; } 377 | [self setSelected:YES animated:YES]; 378 | } 379 | 380 | - (void)didTapUp:(id)sender 381 | { 382 | if (self.searchTextField.isFirstResponder) { return; } 383 | [self.searchTextField becomeFirstResponder]; 384 | } 385 | 386 | - (void)didTapExit:(id)sender 387 | { 388 | if (self.searchTextField.isFirstResponder) { return; } 389 | [self setSelected:NO animated:YES]; 390 | } 391 | 392 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 393 | 394 | // Make sure the 'clear' button receives priority touch events 395 | UIView *targetView = self.clearButton; 396 | CGPoint pointForTargetView = [targetView convertPoint:point fromView:self]; 397 | if (targetView.hidden == NO && CGRectContainsPoint(targetView.bounds, pointForTargetView)) { 398 | return [targetView hitTest:pointForTargetView withEvent:event]; 399 | } 400 | 401 | // If we touch anywhere in the rounded rectangle, assume we were aiming for the search field 402 | if (CGRectContainsPoint(self.barBackgroundView.frame, point)) { 403 | return self.searchTextField; 404 | } 405 | 406 | return [super hitTest:point withEvent:event]; 407 | } 408 | 409 | #pragma mark - Editing - 410 | - (void)setEditing:(BOOL)editing animated:(BOOL)animated 411 | { 412 | self.editing = editing; 413 | 414 | if (animated == NO) { 415 | return; 416 | } 417 | 418 | [UIView animateWithDuration:0.4f 419 | delay:0.0f 420 | usingSpringWithDamping:1.0f 421 | initialSpringVelocity:0.1f 422 | options:UIViewAnimationOptionBeginFromCurrentState 423 | animations: 424 | ^{ 425 | [self layoutIfNeeded]; 426 | } completion:nil]; 427 | } 428 | 429 | - (void)setClearButtonHidden:(BOOL)hidden animated:(BOOL)animated 430 | { 431 | if (self.clearButton.hidden == hidden) { 432 | return; 433 | } 434 | 435 | if (animated == NO) { 436 | self.clearButton.hidden = hidden; 437 | return; 438 | } 439 | 440 | self.clearButton.hidden = NO; 441 | 442 | CGAffineTransform visibleTransform = CGAffineTransformIdentity; 443 | CGAffineTransform hiddenTransform = CGAffineTransformScale(CGAffineTransformIdentity, 0.01f, 0.01f); 444 | 445 | CGFloat visibleAlpha = 1.0f; 446 | CGFloat hiddenAlpha = 0.0f; 447 | 448 | self.clearButton.transform = hidden ? visibleTransform : hiddenTransform; 449 | self.clearButton.alpha = hidden ? visibleAlpha : hiddenAlpha; 450 | 451 | [self.clearButton.layer removeAllAnimations]; 452 | [UIView animateWithDuration:0.25f 453 | delay:0.0f 454 | usingSpringWithDamping:1.0f 455 | initialSpringVelocity:0.1f 456 | options:0 457 | animations: 458 | ^{ 459 | self.clearButton.alpha = hidden ? hiddenAlpha : visibleAlpha; 460 | self.clearButton.transform = hidden ? hiddenTransform : visibleTransform; 461 | } completion:^(BOOL complete) { 462 | self.clearButton.hidden = hidden; 463 | }]; 464 | } 465 | 466 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 467 | { 468 | if (super.selected == selected) { return; } 469 | 470 | super.selected = selected; 471 | 472 | // The bar didn't change colour in iOS 10 and down 473 | if (@available(iOS 11.0, *)) { } 474 | else { return; } 475 | 476 | void (^selectedBlock)(void) = ^{ 477 | self.barBackgroundView.tintColor = selected ? self.selectedBarBackgroundTintColor : self.barBackgroundTintColor; 478 | }; 479 | 480 | if (!animated) { 481 | selectedBlock(); 482 | return; 483 | } 484 | 485 | [UIView animateWithDuration:0.3f 486 | delay:0.0f 487 | options:0 488 | animations:selectedBlock 489 | completion:nil]; 490 | } 491 | 492 | #pragma mark - Text Field Delegate - 493 | - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField 494 | { 495 | BOOL result = YES; 496 | 497 | // Allow the delegate to override this value 498 | if ([self.delegate respondsToSelector:@selector(searchBarShouldBeginEditing:)]) { 499 | result = [self.delegate searchBarShouldBeginEditing:self]; 500 | } 501 | 502 | // Kickstart the editing transition now. Any later creates strange stretch effects 503 | if (result) { 504 | [self setEditing:YES animated:YES]; 505 | } 506 | 507 | return result; 508 | } 509 | 510 | - (void)textFieldDidBeginEditing:(UITextField *)textField 511 | { 512 | if ([self.delegate respondsToSelector:@selector(searchBarDidBeginEditing:)]) { 513 | [self.delegate searchBarDidBeginEditing:self]; 514 | } 515 | 516 | [self setSelected:NO animated:YES]; 517 | } 518 | 519 | - (BOOL)textFieldShouldEndEditing:(UITextField *)textField 520 | { 521 | BOOL result = YES; 522 | 523 | // Allow the delegate to override this value 524 | if ([self.delegate respondsToSelector:@selector(searchBarShouldEndEditing:)]) { 525 | result = [self.delegate searchBarShouldEndEditing:self]; 526 | } 527 | 528 | // Kickstart the editing transition now. Any later creates strange stretch effects 529 | if (result) { 530 | [self setEditing:YES animated:YES]; 531 | } 532 | 533 | return result; 534 | } 535 | 536 | - (void)textFieldDidEndEditing:(UITextField *)textField 537 | { 538 | [textField layoutIfNeeded]; // Necessary to stop bouncing animation glitch 539 | [self setEditing:NO animated:YES]; 540 | 541 | if ([self.delegate respondsToSelector:@selector(searchBarDidEndEditing:)]) { 542 | [self.delegate searchBarDidEndEditing:self]; 543 | } 544 | } 545 | 546 | - (void)textFieldDidChange:(UITextField *)textField 547 | { 548 | self.placeholderLabel.hidden = self.hasSearchText; 549 | self.clearButton.enabled = self.hasSearchText; 550 | 551 | [self setClearButtonHidden:!self.hasSearchText animated:YES]; 552 | 553 | if ([self.delegate respondsToSelector:@selector(searchBar:textDidChange:)]) { 554 | [self.delegate searchBar:self textDidChange:self.text]; 555 | } 556 | } 557 | 558 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 559 | { 560 | if ([self.delegate respondsToSelector:@selector(searchBar:shouldChangeTextInRange:replacementText:)]) { 561 | return [self.delegate searchBar:self shouldChangeCharactersInRange:range replacementString:string]; 562 | } 563 | 564 | return YES; 565 | } 566 | 567 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 568 | { 569 | if ([self.delegate respondsToSelector:@selector(searchBarSearchButtonTapped:)]) { 570 | [self.delegate searchBarSearchButtonTapped:self]; 571 | } 572 | 573 | return YES; 574 | } 575 | 576 | #pragma mark - Accessors - 577 | - (void)setEditing:(BOOL)editing 578 | { 579 | _editing = editing; 580 | [self setNeedsLayout]; 581 | } 582 | 583 | - (BOOL)hasSearchText 584 | { 585 | return self.searchTextField.text.length > 0; 586 | } 587 | 588 | - (void)setText:(NSString *)text 589 | { 590 | self.searchTextField.text = text; 591 | } 592 | 593 | - (NSString *)text 594 | { 595 | return self.searchTextField.text; 596 | } 597 | 598 | - (void)setPlaceholderText:(NSString *)placeholderText 599 | { 600 | self.placeholderLabel.text = placeholderText ?: NSLocalizedStringFromTableInBundle(@"Search", 601 | @"TOSearchBarLocalizable", 602 | [TOSearchBar bundle], 603 | nil); 604 | [self.placeholderLabel sizeToFit]; 605 | [self setNeedsLayout]; 606 | } 607 | 608 | - (NSString *)placeholderText 609 | { 610 | return self.placeholderLabel.text; 611 | } 612 | 613 | - (UIColor *)barBackgroundTintColor 614 | { 615 | return _barBackgroundTintColor; 616 | } 617 | 618 | - (void)setBarBackgroundTintColor:(UIColor *)barBackgroundTintColor 619 | { 620 | _barBackgroundTintColor = barBackgroundTintColor; 621 | self.barBackgroundView.tintColor = _barBackgroundTintColor; 622 | } 623 | 624 | - (void)setHorizontalInset:(CGFloat)horizontalInset 625 | { 626 | if (_horizontalInset == horizontalInset) { 627 | return; 628 | } 629 | 630 | _horizontalInset = horizontalInset; 631 | [self setNeedsLayout]; 632 | } 633 | 634 | - (void)setPlaceholderTintColor:(UIColor *)placeholderTintColor 635 | { 636 | _placeholderTintColor = placeholderTintColor; 637 | self.placeholderLabel.textColor = placeholderTintColor; 638 | self.iconView.tintColor = placeholderTintColor; 639 | } 640 | 641 | - (void)setStyle:(TOSearchBarStyle)style 642 | { 643 | if (style == _style) { 644 | return; 645 | } 646 | 647 | _style = style; 648 | [self configureThemeForCurrentStyle]; 649 | } 650 | 651 | - (void)setShowsCancelButton:(BOOL)showsCancelButton 652 | { 653 | _showsCancelButton = showsCancelButton; 654 | 655 | if (_showsCancelButton == NO) { 656 | [self.cancelButton removeFromSuperview]; 657 | self.cancelButton = nil; 658 | } 659 | else { 660 | [self setUpButtons]; 661 | } 662 | 663 | [self setNeedsLayout]; 664 | } 665 | 666 | - (BOOL)centerTextLabel 667 | { 668 | if (@available(iOS 11.0, *)) { 669 | return NO; 670 | } 671 | 672 | return !self.editing && !self.hasSearchText; 673 | } 674 | 675 | + (NSBundle *)bundle 676 | { 677 | // Whether installed manually or via CocoaPods, this will configure the bundle paths properly 678 | NSBundle *resourceBundle = nil; 679 | NSBundle *classBundle = [NSBundle bundleForClass:[self class]]; 680 | NSURL *resourceBundleURL = [classBundle URLForResource:@"TOSearchBarBundle" withExtension:@"bundle"]; 681 | if (resourceBundleURL) { 682 | resourceBundle = [[NSBundle alloc] initWithURL:resourceBundleURL]; 683 | } 684 | else { 685 | resourceBundle = classBundle; 686 | } 687 | 688 | return resourceBundle; 689 | } 690 | 691 | @end 692 | -------------------------------------------------------------------------------- /TODocumentPickerViewControllerExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2205C43E1EBF0B1600B20229 /* TODocumentsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2205C43D1EBF0B1600B20229 /* TODocumentsDataSource.m */; }; 11 | 220D33011D33F0440004E4BE /* TODocumentPickerConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 220D33001D33F0440004E4BE /* TODocumentPickerConfiguration.m */; }; 12 | 225C38F91A7E37DC004DA4D9 /* TODocumentPickerItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 225C38F81A7E37DC004DA4D9 /* TODocumentPickerItemManager.m */; }; 13 | 22707A811A53BDDF008E66CC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 22707A801A53BDDF008E66CC /* main.m */; }; 14 | 22707A841A53BDDF008E66CC /* TOAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 22707A831A53BDDF008E66CC /* TOAppDelegate.m */; }; 15 | 22707A871A53BDDF008E66CC /* TOViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 22707A861A53BDDF008E66CC /* TOViewController.m */; }; 16 | 22707A8A1A53BDDF008E66CC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 22707A881A53BDDF008E66CC /* Main.storyboard */; }; 17 | 22707A8C1A53BDDF008E66CC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 22707A8B1A53BDDF008E66CC /* Images.xcassets */; }; 18 | 22707A8F1A53BDDF008E66CC /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 22707A8D1A53BDDF008E66CC /* LaunchScreen.xib */; }; 19 | 22A3E19D1A56857700C4839E /* TODocumentPickerItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A3E1991A56857700C4839E /* TODocumentPickerItem.m */; }; 20 | 22A3E1A01A5685A000C4839E /* TODocumentPickerHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A3E19F1A5685A000C4839E /* TODocumentPickerHeaderView.m */; }; 21 | 22A3E1A31A56880C00C4839E /* TODocumentPickerSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A3E1A21A56880C00C4839E /* TODocumentPickerSegmentedControl.m */; }; 22 | 22A5E42C1A7B17F8000CD302 /* TODocumentPickerTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A5E42B1A7B17F8000CD302 /* TODocumentPickerTableView.m */; }; 23 | 22AF79871D2C01550066965F /* TODocumentPickerConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 22AF79861D2C01550066965F /* TODocumentPickerConstants.m */; }; 24 | 22B177011FF60F6700F974BB /* TODocumentPickerTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 22B177001FF60F6700F974BB /* TODocumentPickerTheme.m */; }; 25 | 22B177041FF6242600F974BB /* TODocumentPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 22B177031FF6242600F974BB /* TODocumentPickerViewController.m */; }; 26 | 22B177071FF630E200F974BB /* TODocumentPickerTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 22B177061FF630E200F974BB /* TODocumentPickerTableViewCell.m */; }; 27 | 22DF14A61A8104F8002E9DE4 /* UIImage+TODocumentPickerIcons.m in Sources */ = {isa = PBXBuildFile; fileRef = 22DF14A51A8104F8002E9DE4 /* UIImage+TODocumentPickerIcons.m */; }; 28 | 22F806D71ECAC5EB0053C94E /* TODocumentPickerLocalDiskDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 22F806D61ECAC5EB0053C94E /* TODocumentPickerLocalDiskDataSource.m */; }; 29 | 22FC4C611FF748A50055FDF5 /* TOSearchBarLocalizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 22FC4C4E1FF748A50055FDF5 /* TOSearchBarLocalizable.strings */; }; 30 | 22FC4C621FF748A50055FDF5 /* TOSearchBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 22FC4C5D1FF748A50055FDF5 /* TOSearchBar.m */; }; 31 | 22FC4C631FF748A50055FDF5 /* TOSearchBar+Assets.m in Sources */ = {isa = PBXBuildFile; fileRef = 22FC4C601FF748A50055FDF5 /* TOSearchBar+Assets.m */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 2205C43C1EBF0B1600B20229 /* TODocumentsDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentsDataSource.h; sourceTree = ""; }; 36 | 2205C43D1EBF0B1600B20229 /* TODocumentsDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentsDataSource.m; sourceTree = ""; }; 37 | 220D32FF1D33F0440004E4BE /* TODocumentPickerConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerConfiguration.h; sourceTree = ""; }; 38 | 220D33001D33F0440004E4BE /* TODocumentPickerConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerConfiguration.m; sourceTree = ""; }; 39 | 2211A3CF1A5B80900089106B /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = ""; }; 40 | 2211A3D01A5B80900089106B /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/LaunchScreen.strings; sourceTree = ""; }; 41 | 225C38F71A7E37DC004DA4D9 /* TODocumentPickerItemManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerItemManager.h; sourceTree = ""; }; 42 | 225C38F81A7E37DC004DA4D9 /* TODocumentPickerItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerItemManager.m; sourceTree = ""; }; 43 | 225C38FA1A7E3AF0004DA4D9 /* TODocumentPickerConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerConstants.h; sourceTree = ""; }; 44 | 22707A7B1A53BDDF008E66CC /* TODocumentPickerViewControllerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TODocumentPickerViewControllerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 22707A7F1A53BDDF008E66CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 22707A801A53BDDF008E66CC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 47 | 22707A821A53BDDF008E66CC /* TOAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOAppDelegate.h; sourceTree = ""; }; 48 | 22707A831A53BDDF008E66CC /* TOAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOAppDelegate.m; sourceTree = ""; }; 49 | 22707A851A53BDDF008E66CC /* TOViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOViewController.h; sourceTree = ""; }; 50 | 22707A861A53BDDF008E66CC /* TOViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOViewController.m; sourceTree = ""; }; 51 | 22707A891A53BDDF008E66CC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 52 | 22707A8B1A53BDDF008E66CC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 53 | 22707A8E1A53BDDF008E66CC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 54 | 22A3E1981A56857700C4839E /* TODocumentPickerItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerItem.h; sourceTree = ""; }; 55 | 22A3E1991A56857700C4839E /* TODocumentPickerItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerItem.m; sourceTree = ""; }; 56 | 22A3E19E1A5685A000C4839E /* TODocumentPickerHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerHeaderView.h; sourceTree = ""; }; 57 | 22A3E19F1A5685A000C4839E /* TODocumentPickerHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerHeaderView.m; sourceTree = ""; }; 58 | 22A3E1A11A56880C00C4839E /* TODocumentPickerSegmentedControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerSegmentedControl.h; sourceTree = ""; }; 59 | 22A3E1A21A56880C00C4839E /* TODocumentPickerSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerSegmentedControl.m; sourceTree = ""; }; 60 | 22A5E42A1A7B17F8000CD302 /* TODocumentPickerTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerTableView.h; sourceTree = ""; }; 61 | 22A5E42B1A7B17F8000CD302 /* TODocumentPickerTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerTableView.m; sourceTree = ""; }; 62 | 22AF79861D2C01550066965F /* TODocumentPickerConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerConstants.m; sourceTree = ""; }; 63 | 22B176FF1FF60F6700F974BB /* TODocumentPickerTheme.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerTheme.h; sourceTree = ""; }; 64 | 22B177001FF60F6700F974BB /* TODocumentPickerTheme.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerTheme.m; sourceTree = ""; }; 65 | 22B177021FF6242600F974BB /* TODocumentPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TODocumentPickerViewController.h; path = TODocumentPickerViewController/TODocumentPickerViewController.h; sourceTree = ""; }; 66 | 22B177031FF6242600F974BB /* TODocumentPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TODocumentPickerViewController.m; path = TODocumentPickerViewController/TODocumentPickerViewController.m; sourceTree = ""; }; 67 | 22B177051FF630E200F974BB /* TODocumentPickerTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerTableViewCell.h; sourceTree = ""; }; 68 | 22B177061FF630E200F974BB /* TODocumentPickerTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerTableViewCell.m; sourceTree = ""; }; 69 | 22DF14A41A8104F8002E9DE4 /* UIImage+TODocumentPickerIcons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+TODocumentPickerIcons.h"; path = "TODocumentPickerViewController/Categories/UIImage+TODocumentPickerIcons.h"; sourceTree = ""; }; 70 | 22DF14A51A8104F8002E9DE4 /* UIImage+TODocumentPickerIcons.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+TODocumentPickerIcons.m"; path = "TODocumentPickerViewController/Categories/UIImage+TODocumentPickerIcons.m"; sourceTree = ""; }; 71 | 22F806D51ECAC5EB0053C94E /* TODocumentPickerLocalDiskDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TODocumentPickerLocalDiskDataSource.h; sourceTree = ""; }; 72 | 22F806D61ECAC5EB0053C94E /* TODocumentPickerLocalDiskDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TODocumentPickerLocalDiskDataSource.m; sourceTree = ""; }; 73 | 22FC4C4F1FF748A50055FDF5 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 74 | 22FC4C501FF748A50055FDF5 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/TOSearchBarLocalizable.strings"; sourceTree = ""; }; 75 | 22FC4C511FF748A50055FDF5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 76 | 22FC4C521FF748A50055FDF5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 77 | 22FC4C531FF748A50055FDF5 /* TOSearchBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TOSearchBar.h; sourceTree = ""; }; 78 | 22FC4C541FF748A50055FDF5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 79 | 22FC4C551FF748A50055FDF5 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 80 | 22FC4C561FF748A50055FDF5 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 81 | 22FC4C571FF748A50055FDF5 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 82 | 22FC4C581FF748A50055FDF5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 83 | 22FC4C591FF748A50055FDF5 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/TOSearchBarLocalizable.strings"; sourceTree = ""; }; 84 | 22FC4C5A1FF748A50055FDF5 /* TOSearchBar+Assets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TOSearchBar+Assets.h"; sourceTree = ""; }; 85 | 22FC4C5B1FF748A50055FDF5 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 86 | 22FC4C5C1FF748A50055FDF5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 87 | 22FC4C5D1FF748A50055FDF5 /* TOSearchBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TOSearchBar.m; sourceTree = ""; }; 88 | 22FC4C5E1FF748A50055FDF5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 89 | 22FC4C5F1FF748A50055FDF5 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/TOSearchBarLocalizable.strings; sourceTree = ""; }; 90 | 22FC4C601FF748A50055FDF5 /* TOSearchBar+Assets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "TOSearchBar+Assets.m"; sourceTree = ""; }; 91 | /* End PBXFileReference section */ 92 | 93 | /* Begin PBXFrameworksBuildPhase section */ 94 | 22707A781A53BDDF008E66CC /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | 225C38F31A7DFB7A004DA4D9 /* Categories */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 22DF14A41A8104F8002E9DE4 /* UIImage+TODocumentPickerIcons.h */, 108 | 22DF14A51A8104F8002E9DE4 /* UIImage+TODocumentPickerIcons.m */, 109 | ); 110 | name = Categories; 111 | sourceTree = ""; 112 | }; 113 | 22707A721A53BDDF008E66CC = { 114 | isa = PBXGroup; 115 | children = ( 116 | 22707AA41A53BE85008E66CC /* TODocumentPickerViewController */, 117 | 22FC4C4D1FF748A50055FDF5 /* TOSearchBar */, 118 | 22707A7D1A53BDDF008E66CC /* TODocumentPickerViewControllerExample */, 119 | 22707A7C1A53BDDF008E66CC /* Products */, 120 | ); 121 | sourceTree = ""; 122 | }; 123 | 22707A7C1A53BDDF008E66CC /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 22707A7B1A53BDDF008E66CC /* TODocumentPickerViewControllerExample.app */, 127 | ); 128 | name = Products; 129 | sourceTree = ""; 130 | }; 131 | 22707A7D1A53BDDF008E66CC /* TODocumentPickerViewControllerExample */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 2205C43C1EBF0B1600B20229 /* TODocumentsDataSource.h */, 135 | 2205C43D1EBF0B1600B20229 /* TODocumentsDataSource.m */, 136 | 22707A821A53BDDF008E66CC /* TOAppDelegate.h */, 137 | 22707A831A53BDDF008E66CC /* TOAppDelegate.m */, 138 | 22707A851A53BDDF008E66CC /* TOViewController.h */, 139 | 22707A861A53BDDF008E66CC /* TOViewController.m */, 140 | 22707A881A53BDDF008E66CC /* Main.storyboard */, 141 | 22707A8B1A53BDDF008E66CC /* Images.xcassets */, 142 | 22707A8D1A53BDDF008E66CC /* LaunchScreen.xib */, 143 | 22707A7E1A53BDDF008E66CC /* Supporting Files */, 144 | ); 145 | path = TODocumentPickerViewControllerExample; 146 | sourceTree = ""; 147 | }; 148 | 22707A7E1A53BDDF008E66CC /* Supporting Files */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 22707A7F1A53BDDF008E66CC /* Info.plist */, 152 | 22707A801A53BDDF008E66CC /* main.m */, 153 | ); 154 | name = "Supporting Files"; 155 | sourceTree = ""; 156 | }; 157 | 22707AA41A53BE85008E66CC /* TODocumentPickerViewController */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 22B177021FF6242600F974BB /* TODocumentPickerViewController.h */, 161 | 22B177031FF6242600F974BB /* TODocumentPickerViewController.m */, 162 | 225C38F31A7DFB7A004DA4D9 /* Categories */, 163 | 22A3E1971A56857700C4839E /* Models */, 164 | 22A3E19A1A56857700C4839E /* Views */, 165 | ); 166 | name = TODocumentPickerViewController; 167 | sourceTree = ""; 168 | }; 169 | 22A3E1971A56857700C4839E /* Models */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 225C38FA1A7E3AF0004DA4D9 /* TODocumentPickerConstants.h */, 173 | 22AF79861D2C01550066965F /* TODocumentPickerConstants.m */, 174 | 22F806D51ECAC5EB0053C94E /* TODocumentPickerLocalDiskDataSource.h */, 175 | 22F806D61ECAC5EB0053C94E /* TODocumentPickerLocalDiskDataSource.m */, 176 | 220D32FF1D33F0440004E4BE /* TODocumentPickerConfiguration.h */, 177 | 220D33001D33F0440004E4BE /* TODocumentPickerConfiguration.m */, 178 | 22B176FF1FF60F6700F974BB /* TODocumentPickerTheme.h */, 179 | 22B177001FF60F6700F974BB /* TODocumentPickerTheme.m */, 180 | 225C38F71A7E37DC004DA4D9 /* TODocumentPickerItemManager.h */, 181 | 225C38F81A7E37DC004DA4D9 /* TODocumentPickerItemManager.m */, 182 | 22A3E1981A56857700C4839E /* TODocumentPickerItem.h */, 183 | 22A3E1991A56857700C4839E /* TODocumentPickerItem.m */, 184 | ); 185 | name = Models; 186 | path = TODocumentPickerViewController/Models; 187 | sourceTree = ""; 188 | }; 189 | 22A3E19A1A56857700C4839E /* Views */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 22A3E19E1A5685A000C4839E /* TODocumentPickerHeaderView.h */, 193 | 22A3E19F1A5685A000C4839E /* TODocumentPickerHeaderView.m */, 194 | 22A3E1A11A56880C00C4839E /* TODocumentPickerSegmentedControl.h */, 195 | 22A3E1A21A56880C00C4839E /* TODocumentPickerSegmentedControl.m */, 196 | 22A5E42A1A7B17F8000CD302 /* TODocumentPickerTableView.h */, 197 | 22A5E42B1A7B17F8000CD302 /* TODocumentPickerTableView.m */, 198 | 22B177051FF630E200F974BB /* TODocumentPickerTableViewCell.h */, 199 | 22B177061FF630E200F974BB /* TODocumentPickerTableViewCell.m */, 200 | ); 201 | name = Views; 202 | path = TODocumentPickerViewController/Views; 203 | sourceTree = ""; 204 | }; 205 | 22FC4C4D1FF748A50055FDF5 /* TOSearchBar */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 22FC4C4E1FF748A50055FDF5 /* TOSearchBarLocalizable.strings */, 209 | 22FC4C531FF748A50055FDF5 /* TOSearchBar.h */, 210 | 22FC4C5A1FF748A50055FDF5 /* TOSearchBar+Assets.h */, 211 | 22FC4C5D1FF748A50055FDF5 /* TOSearchBar.m */, 212 | 22FC4C601FF748A50055FDF5 /* TOSearchBar+Assets.m */, 213 | ); 214 | path = TOSearchBar; 215 | sourceTree = ""; 216 | }; 217 | /* End PBXGroup section */ 218 | 219 | /* Begin PBXNativeTarget section */ 220 | 22707A7A1A53BDDF008E66CC /* TODocumentPickerViewControllerExample */ = { 221 | isa = PBXNativeTarget; 222 | buildConfigurationList = 22707A9E1A53BDDF008E66CC /* Build configuration list for PBXNativeTarget "TODocumentPickerViewControllerExample" */; 223 | buildPhases = ( 224 | 22707A771A53BDDF008E66CC /* Sources */, 225 | 22707A781A53BDDF008E66CC /* Frameworks */, 226 | 22707A791A53BDDF008E66CC /* Resources */, 227 | ); 228 | buildRules = ( 229 | ); 230 | dependencies = ( 231 | ); 232 | name = TODocumentPickerViewControllerExample; 233 | productName = TODocumentPickerViewControllerExample; 234 | productReference = 22707A7B1A53BDDF008E66CC /* TODocumentPickerViewControllerExample.app */; 235 | productType = "com.apple.product-type.application"; 236 | }; 237 | /* End PBXNativeTarget section */ 238 | 239 | /* Begin PBXProject section */ 240 | 22707A731A53BDDF008E66CC /* Project object */ = { 241 | isa = PBXProject; 242 | attributes = { 243 | LastUpgradeCheck = 0900; 244 | ORGANIZATIONNAME = "Tim Oliver"; 245 | TargetAttributes = { 246 | 22707A7A1A53BDDF008E66CC = { 247 | CreatedOnToolsVersion = 6.1.1; 248 | DevelopmentTeam = 6LF3GMKZAB; 249 | }; 250 | }; 251 | }; 252 | buildConfigurationList = 22707A761A53BDDF008E66CC /* Build configuration list for PBXProject "TODocumentPickerViewControllerExample" */; 253 | compatibilityVersion = "Xcode 3.2"; 254 | developmentRegion = English; 255 | hasScannedForEncodings = 0; 256 | knownRegions = ( 257 | en, 258 | Base, 259 | de, 260 | "zh-Hans", 261 | ja, 262 | es, 263 | da, 264 | it, 265 | ko, 266 | "zh-Hant", 267 | pl, 268 | ru, 269 | fr, 270 | nl, 271 | ); 272 | mainGroup = 22707A721A53BDDF008E66CC; 273 | productRefGroup = 22707A7C1A53BDDF008E66CC /* Products */; 274 | projectDirPath = ""; 275 | projectRoot = ""; 276 | targets = ( 277 | 22707A7A1A53BDDF008E66CC /* TODocumentPickerViewControllerExample */, 278 | ); 279 | }; 280 | /* End PBXProject section */ 281 | 282 | /* Begin PBXResourcesBuildPhase section */ 283 | 22707A791A53BDDF008E66CC /* Resources */ = { 284 | isa = PBXResourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 22707A8A1A53BDDF008E66CC /* Main.storyboard in Resources */, 288 | 22707A8F1A53BDDF008E66CC /* LaunchScreen.xib in Resources */, 289 | 22FC4C611FF748A50055FDF5 /* TOSearchBarLocalizable.strings in Resources */, 290 | 22707A8C1A53BDDF008E66CC /* Images.xcassets in Resources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | /* End PBXResourcesBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 22707A771A53BDDF008E66CC /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 22B177041FF6242600F974BB /* TODocumentPickerViewController.m in Sources */, 302 | 2205C43E1EBF0B1600B20229 /* TODocumentsDataSource.m in Sources */, 303 | 22F806D71ECAC5EB0053C94E /* TODocumentPickerLocalDiskDataSource.m in Sources */, 304 | 22DF14A61A8104F8002E9DE4 /* UIImage+TODocumentPickerIcons.m in Sources */, 305 | 220D33011D33F0440004E4BE /* TODocumentPickerConfiguration.m in Sources */, 306 | 22FC4C621FF748A50055FDF5 /* TOSearchBar.m in Sources */, 307 | 22707A871A53BDDF008E66CC /* TOViewController.m in Sources */, 308 | 22FC4C631FF748A50055FDF5 /* TOSearchBar+Assets.m in Sources */, 309 | 22A3E19D1A56857700C4839E /* TODocumentPickerItem.m in Sources */, 310 | 22B177011FF60F6700F974BB /* TODocumentPickerTheme.m in Sources */, 311 | 22A3E1A01A5685A000C4839E /* TODocumentPickerHeaderView.m in Sources */, 312 | 22AF79871D2C01550066965F /* TODocumentPickerConstants.m in Sources */, 313 | 22A3E1A31A56880C00C4839E /* TODocumentPickerSegmentedControl.m in Sources */, 314 | 22A5E42C1A7B17F8000CD302 /* TODocumentPickerTableView.m in Sources */, 315 | 22707A841A53BDDF008E66CC /* TOAppDelegate.m in Sources */, 316 | 22B177071FF630E200F974BB /* TODocumentPickerTableViewCell.m in Sources */, 317 | 225C38F91A7E37DC004DA4D9 /* TODocumentPickerItemManager.m in Sources */, 318 | 22707A811A53BDDF008E66CC /* main.m in Sources */, 319 | ); 320 | runOnlyForDeploymentPostprocessing = 0; 321 | }; 322 | /* End PBXSourcesBuildPhase section */ 323 | 324 | /* Begin PBXVariantGroup section */ 325 | 22707A881A53BDDF008E66CC /* Main.storyboard */ = { 326 | isa = PBXVariantGroup; 327 | children = ( 328 | 22707A891A53BDDF008E66CC /* Base */, 329 | 2211A3CF1A5B80900089106B /* ja */, 330 | ); 331 | name = Main.storyboard; 332 | sourceTree = ""; 333 | }; 334 | 22707A8D1A53BDDF008E66CC /* LaunchScreen.xib */ = { 335 | isa = PBXVariantGroup; 336 | children = ( 337 | 22707A8E1A53BDDF008E66CC /* Base */, 338 | 2211A3D01A5B80900089106B /* ja */, 339 | ); 340 | name = LaunchScreen.xib; 341 | sourceTree = ""; 342 | }; 343 | 22FC4C4E1FF748A50055FDF5 /* TOSearchBarLocalizable.strings */ = { 344 | isa = PBXVariantGroup; 345 | children = ( 346 | 22FC4C4F1FF748A50055FDF5 /* de */, 347 | 22FC4C501FF748A50055FDF5 /* zh-Hans */, 348 | 22FC4C511FF748A50055FDF5 /* ja */, 349 | 22FC4C521FF748A50055FDF5 /* en */, 350 | 22FC4C541FF748A50055FDF5 /* es */, 351 | 22FC4C551FF748A50055FDF5 /* da */, 352 | 22FC4C561FF748A50055FDF5 /* it */, 353 | 22FC4C571FF748A50055FDF5 /* ko */, 354 | 22FC4C581FF748A50055FDF5 /* Base */, 355 | 22FC4C591FF748A50055FDF5 /* zh-Hant */, 356 | 22FC4C5B1FF748A50055FDF5 /* pl */, 357 | 22FC4C5C1FF748A50055FDF5 /* ru */, 358 | 22FC4C5E1FF748A50055FDF5 /* fr */, 359 | 22FC4C5F1FF748A50055FDF5 /* nl */, 360 | ); 361 | name = TOSearchBarLocalizable.strings; 362 | sourceTree = ""; 363 | }; 364 | /* End PBXVariantGroup section */ 365 | 366 | /* Begin XCBuildConfiguration section */ 367 | 22707A9C1A53BDDF008E66CC /* Debug */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ALWAYS_SEARCH_USER_PATHS = NO; 371 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 372 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 373 | CLANG_CXX_LIBRARY = "libc++"; 374 | CLANG_ENABLE_MODULES = YES; 375 | CLANG_ENABLE_OBJC_ARC = YES; 376 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 377 | CLANG_WARN_BOOL_CONVERSION = YES; 378 | CLANG_WARN_COMMA = YES; 379 | CLANG_WARN_CONSTANT_CONVERSION = YES; 380 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 381 | CLANG_WARN_EMPTY_BODY = YES; 382 | CLANG_WARN_ENUM_CONVERSION = YES; 383 | CLANG_WARN_INFINITE_RECURSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 387 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 388 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 389 | CLANG_WARN_STRICT_PROTOTYPES = YES; 390 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 391 | CLANG_WARN_UNREACHABLE_CODE = YES; 392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 393 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 394 | COPY_PHASE_STRIP = NO; 395 | ENABLE_STRICT_OBJC_MSGSEND = YES; 396 | ENABLE_TESTABILITY = YES; 397 | GCC_C_LANGUAGE_STANDARD = gnu99; 398 | GCC_DYNAMIC_NO_PIC = NO; 399 | GCC_NO_COMMON_BLOCKS = YES; 400 | GCC_OPTIMIZATION_LEVEL = 0; 401 | GCC_PREPROCESSOR_DEFINITIONS = ( 402 | "DEBUG=1", 403 | "$(inherited)", 404 | ); 405 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 406 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 407 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 408 | GCC_WARN_UNDECLARED_SELECTOR = YES; 409 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 410 | GCC_WARN_UNUSED_FUNCTION = YES; 411 | GCC_WARN_UNUSED_VARIABLE = YES; 412 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 413 | MTL_ENABLE_DEBUG_INFO = YES; 414 | ONLY_ACTIVE_ARCH = YES; 415 | SDKROOT = iphoneos; 416 | TARGETED_DEVICE_FAMILY = "1,2"; 417 | }; 418 | name = Debug; 419 | }; 420 | 22707A9D1A53BDDF008E66CC /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | ALWAYS_SEARCH_USER_PATHS = NO; 424 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_ENABLE_OBJC_ARC = YES; 429 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 430 | CLANG_WARN_BOOL_CONVERSION = YES; 431 | CLANG_WARN_COMMA = YES; 432 | CLANG_WARN_CONSTANT_CONVERSION = YES; 433 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 434 | CLANG_WARN_EMPTY_BODY = YES; 435 | CLANG_WARN_ENUM_CONVERSION = YES; 436 | CLANG_WARN_INFINITE_RECURSION = YES; 437 | CLANG_WARN_INT_CONVERSION = YES; 438 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 439 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 440 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 441 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 442 | CLANG_WARN_STRICT_PROTOTYPES = YES; 443 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 444 | CLANG_WARN_UNREACHABLE_CODE = YES; 445 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 446 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 447 | COPY_PHASE_STRIP = YES; 448 | ENABLE_NS_ASSERTIONS = NO; 449 | ENABLE_STRICT_OBJC_MSGSEND = YES; 450 | GCC_C_LANGUAGE_STANDARD = gnu99; 451 | GCC_NO_COMMON_BLOCKS = YES; 452 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 453 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 454 | GCC_WARN_UNDECLARED_SELECTOR = YES; 455 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 456 | GCC_WARN_UNUSED_FUNCTION = YES; 457 | GCC_WARN_UNUSED_VARIABLE = YES; 458 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 459 | MTL_ENABLE_DEBUG_INFO = NO; 460 | SDKROOT = iphoneos; 461 | TARGETED_DEVICE_FAMILY = "1,2"; 462 | VALIDATE_PRODUCT = YES; 463 | }; 464 | name = Release; 465 | }; 466 | 22707A9F1A53BDDF008E66CC /* Debug */ = { 467 | isa = XCBuildConfiguration; 468 | buildSettings = { 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | DEVELOPMENT_TEAM = 6LF3GMKZAB; 471 | INFOPLIST_FILE = TODocumentPickerViewControllerExample/Info.plist; 472 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 474 | PRODUCT_BUNDLE_IDENTIFIER = "com.timoliver.$(PRODUCT_NAME:rfc1034identifier)"; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | }; 477 | name = Debug; 478 | }; 479 | 22707AA01A53BDDF008E66CC /* Release */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 483 | DEVELOPMENT_TEAM = 6LF3GMKZAB; 484 | INFOPLIST_FILE = TODocumentPickerViewControllerExample/Info.plist; 485 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 486 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 487 | PRODUCT_BUNDLE_IDENTIFIER = "com.timoliver.$(PRODUCT_NAME:rfc1034identifier)"; 488 | PRODUCT_NAME = "$(TARGET_NAME)"; 489 | }; 490 | name = Release; 491 | }; 492 | /* End XCBuildConfiguration section */ 493 | 494 | /* Begin XCConfigurationList section */ 495 | 22707A761A53BDDF008E66CC /* Build configuration list for PBXProject "TODocumentPickerViewControllerExample" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 22707A9C1A53BDDF008E66CC /* Debug */, 499 | 22707A9D1A53BDDF008E66CC /* Release */, 500 | ); 501 | defaultConfigurationIsVisible = 0; 502 | defaultConfigurationName = Release; 503 | }; 504 | 22707A9E1A53BDDF008E66CC /* Build configuration list for PBXNativeTarget "TODocumentPickerViewControllerExample" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | 22707A9F1A53BDDF008E66CC /* Debug */, 508 | 22707AA01A53BDDF008E66CC /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | /* End XCConfigurationList section */ 514 | }; 515 | rootObject = 22707A731A53BDDF008E66CC /* Project object */; 516 | } 517 | --------------------------------------------------------------------------------