├── .gitignore ├── ATAssistiveTools.podspec ├── ATAssistiveTools ├── ATAssistiveTools.h ├── ATAssistiveTools.m ├── ATCustomViewProtocol.h ├── ATExpandInfoView.h ├── ATExpandInfoView.m ├── ATExpandInfoViewCell.h ├── ATExpandInfoViewCell.m ├── ATRootViewController.h ├── ATRootViewController.m ├── ATShrinkInfoView.h └── ATShrinkInfoView.m ├── ATAssistiveToolsDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── ATAssistiveToolsDemo.xcworkspace └── contents.xcworkspacedata ├── ATAssistiveToolsDemo ├── ATAssistiveTools │ ├── ATAssistiveTools.h │ ├── ATAssistiveTools.m │ ├── ATContainerWindow.h │ ├── ATContainerWindow.m │ ├── ATCustomViewProtocol.h │ ├── ATCustomizeViews │ │ ├── ATDeviceLogsView │ │ │ ├── ATDeviceLogsView.h │ │ │ └── ATDeviceLogsView.m │ │ ├── ATFakeLocationView │ │ │ ├── ATFakeLocationView.h │ │ │ └── ATFakeLocationView.m │ │ ├── ATGPSEmulatorView │ │ │ ├── AMapLogFileManager.h │ │ │ ├── AMapLogFileManager.m │ │ │ ├── ATGPSEmulator.h │ │ │ ├── ATGPSEmulator.m │ │ │ ├── ATGPSEmulatorView.h │ │ │ ├── ATGPSEmulatorView.m │ │ │ ├── GPSSimulatorEngine.h │ │ │ ├── GPSSimulatorEngine.m │ │ │ ├── OnlineGPSSimulator.h │ │ │ └── OnlineGPSSimulator.m │ │ └── ATSandboxViewerView │ │ │ ├── ATSandboxViewerView.h │ │ │ └── ATSandboxViewerView.m │ ├── ATExpandInfoView.h │ ├── ATExpandInfoView.m │ ├── ATExpandInfoViewCell.h │ ├── ATExpandInfoViewCell.m │ ├── ATRootViewController.h │ ├── ATRootViewController.m │ ├── ATShrinkInfoView.h │ └── ATShrinkInfoView.m ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m ├── ATCustomizeViews ├── ATDeviceLogsView │ ├── ATDeviceLogsView.h │ └── ATDeviceLogsView.m ├── ATFakeLocationView │ ├── ATFakeLocationView.h │ └── ATFakeLocationView.m ├── ATGPSEmulatorView │ ├── ATGPSEmulator.h │ └── ATGPSEmulator.m └── ATSandboxViewerView │ ├── ATSandboxViewerView.h │ └── ATSandboxViewerView.m ├── LICENSE ├── Podfile ├── Podfile.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | 3 | ## Build generated 4 | build/ 5 | DerivedData 6 | 7 | ## Various settings 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | 18 | ## Other 19 | *.xccheckout 20 | *.moved-aside 21 | *.xcuserstate 22 | *.xcscmblueprint 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | 28 | # Mac OS X 29 | *.DS_Store 30 | 31 | # iOS framework 32 | lib/ 33 | Products/ 34 | lastbuild 35 | 36 | # CocoaPods 37 | Pods/ 38 | -------------------------------------------------------------------------------- /ATAssistiveTools.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = 'ATAssistiveTools' 4 | s.version = '0.1.0' 5 | s.author = { 'devliubo' => 'vipliubo@vip.qq.com' } 6 | s.license = { :type => 'MIT', :file => 'LICENSE' } 7 | s.homepage = 'https://github.com/devliubo/ATAssistiveTools' 8 | s.summary = 'ATAssistiveTools' 9 | 10 | s.source = { :git => 'https://github.com/devliubo/ATAssistiveTools.git', 11 | :tag => "v#{s.version}" } 12 | s.ios.deployment_target = '8.0' 13 | s.requires_arc = true 14 | s.user_target_xcconfig = { 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES' } 15 | 16 | s.default_subspec = 'Core' 17 | 18 | s.subspec 'Core' do |cs| 19 | cs.ios.source_files = 'ATAssistiveTools/**/*.{h,m}' 20 | cs.ios.public_header_files = 'ATAssistiveTools/**/*.h' 21 | cs.requires_arc = true 22 | end 23 | 24 | s.subspec 'CustomizeViews' do |cs| 25 | cs.dependency 'ATAssistiveTools/Core' 26 | cs.dependency 'GCDWebServer/WebUploader' 27 | cs.ios.source_files = 'ATCustomizeViews/**/*.{h,m}' 28 | cs.ios.public_header_files = 'ATCustomizeViews/**/*.h' 29 | cs.requires_arc = true 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATAssistiveTools.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATAssistiveTools.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ATCustomViewProtocol.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface ATAssistiveTools : NSObject 15 | 16 | + (instancetype)sharedInstance; 17 | 18 | /** 19 | * Call ths method to show the assistive tool window. 20 | */ 21 | - (void)show; 22 | 23 | /** 24 | * The window that used for assistive tool. Can be used for present view controller or anyother operations that must via a window. 25 | */ 26 | @property (nonatomic, readonly) UIWindow *mainWindow; 27 | 28 | @end 29 | 30 | @interface ATAssistiveTools () 31 | 32 | /** 33 | * Get all titles had been added. 34 | */ 35 | @property (nonatomic, readonly) NSArray *currentTitles; 36 | 37 | /** 38 | * add a custom view and corresponding title to assistive tool. 39 | * 40 | * @param aView the view to be added. This view must be adopt the ATCustomViewProtocol protocol. 41 | * @param aTitle the title for the view to be added 42 | */ 43 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle; 44 | 45 | /** 46 | * remove a custom view via it's corresponding title. 47 | * 48 | * @param aTitle the title for the view to be moved 49 | */ 50 | - (void)removeCustiomViewForTitle:(NSString *)aTitle; 51 | 52 | /** 53 | * remove all custom views from assistive tool. 54 | */ 55 | - (void)removeAllCustomViews; 56 | 57 | @end 58 | 59 | NS_ASSUME_NONNULL_END 60 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATAssistiveTools.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATAssistiveTools.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATAssistiveTools.h" 10 | #import "ATRootViewController.h" 11 | 12 | @interface ATAssistiveTools () 13 | 14 | @property (nonatomic, strong) UIWindow *assistiveWindow; 15 | @property (nonatomic, strong) ATRootViewController *rootViewController; 16 | 17 | @end 18 | 19 | @implementation ATAssistiveTools 20 | 21 | #pragma mark - Private: Life Cycle 22 | 23 | + (instancetype)sharedInstance 24 | { 25 | static ATAssistiveTools *sharedInstance; 26 | static dispatch_once_t onceToken; 27 | dispatch_once(&onceToken, ^{ 28 | sharedInstance = [[ATAssistiveTools alloc] init]; 29 | }); 30 | return sharedInstance; 31 | } 32 | 33 | - (instancetype)init 34 | { 35 | if (self = [super init]) 36 | { 37 | [self createAssistiveTools]; 38 | } 39 | return self; 40 | } 41 | 42 | - (void)createAssistiveTools 43 | { 44 | [self initProperties]; 45 | 46 | [self initAssisticeWindowAndController]; 47 | } 48 | 49 | #pragma mark - Private: Initialization 50 | 51 | - (void)initProperties 52 | { 53 | 54 | } 55 | 56 | - (void)initAssisticeWindowAndController 57 | { 58 | // ATRootViewController 59 | _rootViewController = [[ATRootViewController alloc] init]; 60 | _rootViewController.delegate = self; 61 | _rootViewController.autorotateEnabled = YES; 62 | 63 | // ATContainerWindow 64 | _assistiveWindow = [[UIWindow alloc] initWithFrame:_rootViewController.shrinkedWindowFrame]; 65 | _assistiveWindow.windowLevel = CGFLOAT_MAX; 66 | _assistiveWindow.layer.masksToBounds = YES; 67 | _assistiveWindow.backgroundColor = [UIColor clearColor]; 68 | 69 | self.rootViewController.assistiveWindow = self.assistiveWindow; 70 | self.assistiveWindow.rootViewController = self.rootViewController; 71 | } 72 | 73 | #pragma mark - Public: Interface 74 | 75 | - (void)show 76 | { 77 | [self makeWindowVisible:self.assistiveWindow]; 78 | } 79 | 80 | - (void)makeWindowVisible:(UIWindow *)window 81 | { 82 | UIWindow *currentKeyWindow = [[UIApplication sharedApplication] keyWindow]; 83 | if (currentKeyWindow == window) 84 | { 85 | [currentKeyWindow makeKeyAndVisible]; 86 | } 87 | else 88 | { 89 | [window makeKeyAndVisible]; 90 | [currentKeyWindow makeKeyWindow]; 91 | } 92 | } 93 | 94 | - (UIWindow *)mainWindow 95 | { 96 | return self.assistiveWindow; 97 | } 98 | 99 | - (NSArray *)currentTitles 100 | { 101 | return self.rootViewController.currentTitles; 102 | } 103 | 104 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle 105 | { 106 | [self.rootViewController addCustomView:aView forTitle:aTitle]; 107 | } 108 | 109 | - (void)removeCustiomViewForTitle:(NSString *)aTitle 110 | { 111 | [self.rootViewController removeCustiomViewForTitle:aTitle]; 112 | } 113 | 114 | - (void)removeAllCustomViews 115 | { 116 | [self.rootViewController removeAllCustomViews]; 117 | } 118 | 119 | //#pragma mark - Private: ATContainerWindowDelegate 120 | // 121 | //- (BOOL)customPointInside:(CGPoint)point withEvent:(UIEvent *)event 122 | //{ 123 | // BOOL inShrink = [self.shrinkInfoView pointInside:[self.assistiveWindow convertPoint:point toView:self.shrinkInfoView] withEvent:event]; 124 | // BOOL inExpand = [self.expandInfoView pointInside:[self.assistiveWindow convertPoint:point toView:self.expandInfoView] withEvent:event]; 125 | // 126 | // BOOL inside = inShrink || inExpand; 127 | // return inside; 128 | //} 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATCustomViewProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATCustomViewProtocol.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol ATCustomViewProtocol 12 | @optional 13 | 14 | /** 15 | * Called when the custom view will appear. 16 | */ 17 | - (void)customViewWillAppear; 18 | 19 | /** 20 | * Called when the custom view did appear. 21 | */ 22 | - (void)customViewDidAppear; 23 | 24 | /** 25 | * Called when the custom view will disappear. 26 | */ 27 | - (void)customViewWillDisappear; 28 | 29 | /** 30 | * Called when the custom view did disappear. 31 | */ 32 | - (void)customViewDidDisappear; 33 | 34 | 35 | /** 36 | * Called when the assistive tool will shrink. 37 | */ 38 | - (void)customViewWillShrink; 39 | 40 | /** 41 | * Called when the assistive tool did shrink. 42 | */ 43 | - (void)customViewDidShrink; 44 | 45 | /** 46 | * Called when the assistive tool will expand. 47 | */ 48 | - (void)customViewWillExpand; 49 | 50 | /** 51 | * Called when the assistive tool did expand. 52 | */ 53 | - (void)customViewDidExpand; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATExpandInfoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATExpandInfoView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ATCustomViewProtocol.h" 11 | 12 | #define kATExpandViewTopHeight 30.f 13 | #define kATExpandViewDetailHeight 450.f 14 | 15 | #define kATExpandViewWidth 320.f 16 | #define kATExpandViewHeight (kATExpandViewDetailHeight+kATExpandViewTopHeight) 17 | 18 | #define kATExpandViewThemeCloor [UIColor colorWithRed:53/255.0 green:117/255.0 blue:255/255.0 alpha:1] 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | @class ATExpandInfoView; 23 | 24 | @protocol ATExpandInfoViewDelegate 25 | @required 26 | 27 | - (void)expandInfoViewCloseAction:(ATExpandInfoView *)expandView; 28 | 29 | @end 30 | 31 | @interface ATExpandInfoView : UIView 32 | 33 | @property (nonatomic, weak) id delegate; 34 | 35 | @property (nonatomic, readonly) NSArray *currentTitles; 36 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle; 37 | - (void)removeCustiomViewForTitle:(NSString *)aTitle; 38 | - (void)removeAllCustomViews; 39 | 40 | //default [UIFont systemFontOfSize:15] 41 | @property (nonatomic, strong) UIFont *titleFont; 42 | //default [UIColor blackColor] 43 | @property (nonatomic, strong) UIColor *titleNormalColor; 44 | //default kATExpandViewThemeCloor 45 | @property (nonatomic, strong) UIColor *titleSelectColor; 46 | 47 | @end 48 | 49 | @interface ATExpandInfoView () 50 | 51 | - (void)expandInfoViewWillShrink; 52 | - (void)expandInfoViewDidShrink; 53 | - (void)expandInfoViewWillExpand; 54 | - (void)expandInfoViewDidExpand; 55 | 56 | @end 57 | 58 | NS_ASSUME_NONNULL_END 59 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATExpandInfoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATExpandInfoView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATExpandInfoView.h" 10 | #import "ATExpandInfoViewCell.h" 11 | 12 | #define kATExpandViewAlpha 0.95f 13 | #define kATExpandVIewBorderWidth 0.4f 14 | #define kATExpandViewDefaultPadding 10.f 15 | #define kATExpandViewIndicatorHeight 4.f 16 | 17 | #define kATExpandViewDuration 0.25f 18 | 19 | @interface ATExpandInfoView () 20 | 21 | @property (nonatomic, strong) UIView *topContentView; 22 | @property (nonatomic, strong) UIView *detailContentView; 23 | 24 | // top 25 | @property (nonatomic, strong) UIButton *closeButton; 26 | @property (nonatomic, strong) UICollectionView *collectionView; 27 | @property (nonatomic, strong) UIView *indicatorView; 28 | @property (nonatomic, readonly) NSDictionary *titleAttributes; 29 | 30 | // detail 31 | @property (nonatomic, readwrite) NSInteger selectedItemIndex; 32 | @property (nonatomic, strong) NSMutableArray *allTitles; 33 | @property (nonatomic, strong) NSMapTable *> *allContents; 34 | 35 | @end 36 | 37 | @implementation ATExpandInfoView 38 | 39 | #pragma mark - Life Cycle 40 | 41 | - (instancetype)initWithFrame:(CGRect)frame 42 | { 43 | if (self = [super initWithFrame:frame]) 44 | { 45 | [self buildExpandInfoView]; 46 | } 47 | return self; 48 | } 49 | 50 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 51 | { 52 | if (self = [super initWithCoder:aDecoder]) 53 | { 54 | [self buildExpandInfoView]; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)buildExpandInfoView 60 | { 61 | self.layer.borderColor = [UIColor blackColor].CGColor; 62 | self.layer.borderWidth = 0.5f; 63 | self.layer.cornerRadius = 4.f; 64 | self.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:kATExpandViewAlpha]; 65 | self.clipsToBounds = YES; 66 | 67 | [self initProperties]; 68 | 69 | [self createContentView]; 70 | 71 | [self buildTopContentView]; 72 | } 73 | 74 | - (void)initProperties 75 | { 76 | _titleFont = [UIFont systemFontOfSize:15]; 77 | _titleNormalColor = [UIColor blackColor]; 78 | _titleSelectColor = kATExpandViewThemeCloor; 79 | 80 | _selectedItemIndex = -1; 81 | _allTitles = [[NSMutableArray alloc] init]; 82 | _allContents = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsCopyIn valueOptions:NSPointerFunctionsStrongMemory capacity:0]; 83 | } 84 | 85 | - (void)createContentView 86 | { 87 | //top content view 88 | _topContentView = [[UIView alloc] initWithFrame:self.bounds]; 89 | _topContentView.translatesAutoresizingMaskIntoConstraints = NO; 90 | _topContentView.backgroundColor = [UIColor clearColor]; 91 | _topContentView.layer.borderColor = kATExpandViewThemeCloor.CGColor; 92 | _topContentView.layer.borderWidth = kATExpandVIewBorderWidth; 93 | 94 | //detail content view 95 | _detailContentView = [[UIView alloc] initWithFrame:self.bounds]; 96 | _detailContentView.translatesAutoresizingMaskIntoConstraints = NO; 97 | _detailContentView.backgroundColor = [UIColor clearColor]; 98 | 99 | [self addSubview:_topContentView]; 100 | [self addSubview:_detailContentView]; 101 | 102 | NSDictionary *views = NSDictionaryOfVariableBindings(_topContentView,_detailContentView); 103 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_topContentView]|" options:0 metrics:nil views:views]]; 104 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_detailContentView]|" options:0 metrics:nil views:views]]; 105 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_topContentView(==height)][_detailContentView]|" options:0 metrics:@{@"height":@(kATExpandViewTopHeight)} views:views]]; 106 | } 107 | 108 | - (void)buildTopContentView 109 | { 110 | // close button 111 | _closeButton = [[UIButton alloc] init]; 112 | _closeButton.translatesAutoresizingMaskIntoConstraints = NO; 113 | _closeButton.backgroundColor = [UIColor clearColor]; 114 | _closeButton.layer.borderColor = [UIColor blackColor].CGColor; 115 | _closeButton.layer.borderWidth = kATExpandVIewBorderWidth; 116 | 117 | [_closeButton addTarget:self action:@selector(closeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; 118 | [_closeButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 119 | [_closeButton setTitle:@"X" forState:UIControlStateNormal]; 120 | 121 | // collection view 122 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 123 | layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 124 | _collectionView = [[UICollectionView alloc] initWithFrame:self.topContentView.bounds collectionViewLayout:layout]; 125 | _collectionView.translatesAutoresizingMaskIntoConstraints = NO; 126 | _collectionView.backgroundColor = [UIColor clearColor]; 127 | 128 | [_collectionView registerClass:[ATExpandInfoViewCell class] forCellWithReuseIdentifier:NSStringFromClass([ATExpandInfoViewCell class])]; 129 | _collectionView.dataSource = self; 130 | _collectionView.delegate = self; 131 | _collectionView.scrollEnabled = YES; 132 | _collectionView.showsHorizontalScrollIndicator = NO; 133 | _collectionView.contentInset = UIEdgeInsetsZero; 134 | 135 | // indicator view 136 | _indicatorView = [[UIView alloc] init]; 137 | _indicatorView.userInteractionEnabled = NO; 138 | _indicatorView.backgroundColor = kATExpandViewThemeCloor; 139 | [_collectionView addSubview:_indicatorView]; 140 | 141 | [self.topContentView addSubview:_closeButton]; 142 | [self.topContentView addSubview:_collectionView]; 143 | 144 | NSDictionary *views = NSDictionaryOfVariableBindings(_closeButton,_collectionView); 145 | [self.topContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_closeButton]|" options:0 metrics:nil views:views]]; 146 | [self.topContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_collectionView]|" options:0 metrics:nil views:views]]; 147 | [self.topContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_closeButton(==width)][_collectionView]|" options:0 metrics:@{@"width":@(kATExpandViewTopHeight)} views:views]]; 148 | } 149 | 150 | - (NSDictionary *)titleAttributes 151 | { 152 | return @{NSFontAttributeName:[self.titleFont copy], 153 | NSForegroundColorAttributeName:[self.titleNormalColor copy], 154 | NSBackgroundColorAttributeName:[self.titleSelectColor copy]}; 155 | } 156 | 157 | #pragma mark - Private: Close Action 158 | 159 | - (void)closeButtonAction:(UIButton *)button 160 | { 161 | if (self.delegate) 162 | { 163 | [self.delegate expandInfoViewCloseAction:self]; 164 | } 165 | } 166 | 167 | #pragma mark - Model 168 | 169 | - (void)addTitle:(NSString *)title relateView:(UIView *)view 170 | { 171 | @synchronized (self.allTitles) { 172 | if ([self.allContents objectForKey:title]) 173 | { 174 | [self.allTitles removeObject:title]; 175 | [self.allContents removeObjectForKey:title]; 176 | } 177 | 178 | if (view != nil) 179 | { 180 | [self.allTitles addObject:title]; 181 | [self.allContents setObject:view forKey:title]; 182 | } 183 | 184 | [self reloadExpandInfoViewData]; 185 | } 186 | } 187 | 188 | - (void)deleteTitle:(NSString *)title 189 | { 190 | @synchronized (self.allTitles) { 191 | if ([self.allContents objectForKey:title]) 192 | { 193 | [self.allTitles removeObject:title]; 194 | [self.allContents removeObjectForKey:title]; 195 | } 196 | 197 | [self reloadExpandInfoViewData]; 198 | } 199 | } 200 | 201 | - (void)deleteAllTitles 202 | { 203 | @synchronized (self.allTitles) { 204 | [self.allTitles removeAllObjects]; 205 | [self.allContents removeAllObjects]; 206 | 207 | [self reloadExpandInfoViewData]; 208 | } 209 | } 210 | 211 | - (void)reloadExpandInfoViewData 212 | { 213 | [self.collectionView reloadData]; 214 | } 215 | 216 | #pragma mark - Public: Interface 217 | 218 | - (NSArray *)currentTitles 219 | { 220 | @synchronized (self.allTitles) { 221 | return [self.allTitles copy]; 222 | } 223 | } 224 | 225 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle 226 | { 227 | if (aTitle == nil || aTitle.length <= 0) 228 | { 229 | return; 230 | } 231 | 232 | [self addTitle:aTitle relateView:aView]; 233 | } 234 | 235 | - (void)removeCustiomViewForTitle:(NSString *)aTitle 236 | { 237 | if (aTitle == nil || aTitle.length <= 0) 238 | { 239 | return; 240 | } 241 | 242 | [self deleteTitle:aTitle]; 243 | } 244 | 245 | - (void)removeAllCustomViews 246 | { 247 | [self deleteAllTitles]; 248 | } 249 | 250 | #pragma mark - Private: Methods 251 | 252 | - (void)manualSelectedItemWithIndex:(NSInteger)index 253 | { 254 | if (index >= self.allTitles.count || index < 0 || index == self.selectedItemIndex) 255 | { 256 | return; 257 | } 258 | 259 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; 260 | 261 | [self.collectionView selectItemAtIndexPath:indexPath 262 | animated:YES 263 | scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; 264 | 265 | [self collectionView:self.collectionView didSelectItemAtIndexPath:indexPath]; 266 | } 267 | 268 | #pragma mark - UICollectionView Delegate 269 | 270 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 271 | { 272 | if (indexPath.row == self.selectedItemIndex) 273 | { 274 | return; 275 | } 276 | 277 | self.selectedItemIndex = indexPath.row; 278 | 279 | ATExpandInfoViewCell *aCell = (ATExpandInfoViewCell *)[collectionView cellForItemAtIndexPath:indexPath]; 280 | 281 | // indicator view frame && collection view scroll to item 282 | CGRect newFrame = CGRectMake(aCell.frame.origin.x, aCell.frame.size.height - kATExpandViewIndicatorHeight, aCell.frame.size.width, kATExpandViewIndicatorHeight); 283 | [UIView animateWithDuration:kATExpandViewDuration animations:^{ 284 | self.indicatorView.frame = newFrame; 285 | [collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; 286 | }]; 287 | 288 | // update detail content view 289 | UIView *viewForCell = [self.allContents objectForKey:aCell.title]; 290 | 291 | // remove old views 292 | NSArray *allSubviews = self.detailContentView.subviews; 293 | for (UIView *aView in allSubviews) 294 | { 295 | // will disappear 296 | if ([aView respondsToSelector:@selector(customViewWillDisappear)]) 297 | { 298 | [aView customViewWillDisappear]; 299 | } 300 | 301 | [aView removeFromSuperview]; 302 | 303 | // did disappear 304 | if ([aView respondsToSelector:@selector(customViewDidDisappear)]) 305 | { 306 | [aView customViewDidDisappear]; 307 | } 308 | } 309 | 310 | // add new view 311 | if (viewForCell != nil) 312 | { 313 | viewForCell.frame = self.detailContentView.bounds; 314 | viewForCell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 315 | 316 | // will appear 317 | if ([viewForCell respondsToSelector:@selector(customViewWillAppear)]) 318 | { 319 | [viewForCell customViewWillAppear]; 320 | } 321 | 322 | [self.detailContentView addSubview:viewForCell]; 323 | 324 | // did appear 325 | if ([viewForCell respondsToSelector:@selector(customViewDidAppear)]) 326 | { 327 | [viewForCell customViewDidAppear]; 328 | } 329 | } 330 | } 331 | 332 | - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath 333 | { 334 | // do nothing 335 | } 336 | 337 | #pragma mark - UICollectionView DataSource 338 | 339 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 340 | { 341 | return [self.allTitles count]; 342 | } 343 | 344 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 345 | { 346 | ATExpandInfoViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([ATExpandInfoViewCell class]) forIndexPath:indexPath]; 347 | 348 | //set cell properties 349 | cell.title = self.allTitles[indexPath.row]; 350 | cell.titleFont = self.titleFont; 351 | cell.titleNormalColor = self.titleNormalColor; 352 | cell.titleSelectColor = self.titleSelectColor; 353 | 354 | return cell; 355 | } 356 | 357 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 358 | { 359 | // size of cell 360 | CGSize size = [[self.allTitles objectAtIndex:indexPath.row] sizeWithAttributes:self.titleAttributes]; 361 | CGSize resultSize = CGSizeMake(size.width + kATExpandViewDefaultPadding, collectionView.bounds.size.height); 362 | 363 | // if it's the first time to show indicator view, calculate view's frame from the first cell's size 364 | if (CGRectIsEmpty(self.indicatorView.frame) && indexPath.row == 0) 365 | { 366 | CGRect newFrame = CGRectMake(0, collectionView.bounds.size.height - kATExpandViewIndicatorHeight, 0, kATExpandViewIndicatorHeight); 367 | self.indicatorView.frame = newFrame; 368 | } 369 | 370 | return resultSize; 371 | } 372 | 373 | //- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section 374 | //{ 375 | // return UIEdgeInsetsZero; 376 | //} 377 | // 378 | //- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section 379 | //{ 380 | // return kATExpandViewDefaultPadding; 381 | //} 382 | // 383 | //- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section 384 | //{ 385 | // return kATExpandViewDefaultPadding; 386 | //} 387 | 388 | #pragma mark - Shrink && Expand 389 | 390 | - (void)expandInfoViewWillShrink 391 | { 392 | NSArray *allSubviews = self.detailContentView.subviews; 393 | for (UIView *aView in allSubviews) 394 | { 395 | if ([aView respondsToSelector:@selector(customViewWillShrink)]) 396 | { 397 | [aView customViewWillShrink]; 398 | } 399 | } 400 | } 401 | 402 | - (void)expandInfoViewDidShrink 403 | { 404 | NSArray *allSubviews = self.detailContentView.subviews; 405 | for (UIView *aView in allSubviews) 406 | { 407 | if ([aView respondsToSelector:@selector(customViewDidShrink)]) 408 | { 409 | [aView customViewDidShrink]; 410 | } 411 | } 412 | } 413 | 414 | - (void)expandInfoViewWillExpand 415 | { 416 | NSArray *allSubviews = self.detailContentView.subviews; 417 | for (UIView *aView in allSubviews) 418 | { 419 | if ([aView respondsToSelector:@selector(customViewWillExpand)]) 420 | { 421 | [aView customViewWillExpand]; 422 | } 423 | } 424 | } 425 | 426 | - (void)expandInfoViewDidExpand 427 | { 428 | NSArray *allSubviews = self.detailContentView.subviews; 429 | for (UIView *aView in allSubviews) 430 | { 431 | if ([aView respondsToSelector:@selector(customViewDidExpand)]) 432 | { 433 | [aView customViewDidExpand]; 434 | } 435 | } 436 | 437 | // default select the first item 438 | if (self.selectedItemIndex < 0) 439 | { 440 | [self manualSelectedItemWithIndex:0]; 441 | } 442 | } 443 | 444 | @end 445 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATExpandInfoViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATExpandInfoViewCell.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/31. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ATExpandInfoViewCell : UICollectionViewCell 14 | 15 | @property (nonatomic, strong) NSString *title; 16 | 17 | @property (nonatomic, strong) UIFont *titleFont; 18 | @property (nonatomic, strong) UIColor *titleNormalColor; 19 | @property (nonatomic, strong) UIColor *titleSelectColor; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATExpandInfoViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATExpandInfoViewCell.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/31. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATExpandInfoViewCell.h" 10 | 11 | @interface ATExpandInfoViewCell () 12 | 13 | @property (nonatomic, strong) UILabel *titleLabel; 14 | 15 | @end 16 | 17 | @implementation ATExpandInfoViewCell 18 | 19 | #pragma mark - Life Cycle 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame 22 | { 23 | if (self = [super initWithFrame:frame]) 24 | { 25 | [self buildExpandInfoViewCell]; 26 | } 27 | return self; 28 | } 29 | 30 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 31 | { 32 | if (self = [super initWithCoder:aDecoder]) 33 | { 34 | [self buildExpandInfoViewCell]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)buildExpandInfoViewCell 40 | { 41 | self.backgroundColor = [UIColor clearColor]; 42 | 43 | [self initProperties]; 44 | 45 | [self initSubviews]; 46 | } 47 | 48 | - (void)initProperties 49 | { 50 | _titleNormalColor = [UIColor blackColor]; 51 | _titleFont = [UIFont systemFontOfSize:15]; 52 | } 53 | 54 | - (void)initSubviews 55 | { 56 | _titleLabel = [[UILabel alloc] init]; 57 | _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 58 | _titleLabel.textAlignment = NSTextAlignmentCenter; 59 | _titleLabel.numberOfLines = 1; 60 | _titleLabel.textColor = _titleNormalColor; 61 | _titleLabel.font = _titleFont; 62 | 63 | [self addSubview:_titleLabel]; 64 | 65 | NSDictionary *views = NSDictionaryOfVariableBindings(_titleLabel); 66 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|" options:0 metrics:nil views:views]]; 67 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]|" options:0 metrics:nil views:views]]; 68 | } 69 | 70 | #pragma mark - Override 71 | 72 | - (void)prepareForReuse 73 | { 74 | [super prepareForReuse]; 75 | 76 | self.title = @""; 77 | } 78 | 79 | - (void)setSelected:(BOOL)selected 80 | { 81 | [super setSelected:selected]; 82 | 83 | self.titleLabel.textColor = selected ? _titleSelectColor : _titleNormalColor; 84 | } 85 | 86 | - (void)setTitle:(NSString *)title 87 | { 88 | _title = title; 89 | self.titleLabel.text = _title; 90 | } 91 | 92 | - (void)setTitleFont:(UIFont *)titleFont 93 | { 94 | _titleFont = titleFont; 95 | self.titleLabel.font = _titleFont; 96 | } 97 | 98 | - (void)setTitleNormalColor:(UIColor *)titleNormalColor 99 | { 100 | _titleNormalColor = titleNormalColor; 101 | if (!self.selected) 102 | { 103 | self.titleLabel.textColor = _titleNormalColor; 104 | } 105 | } 106 | 107 | - (void)setTitleSelectColor:(UIColor *)titleSelectColor 108 | { 109 | _titleSelectColor = titleSelectColor; 110 | if (self.selected) 111 | { 112 | self.titleLabel.textColor = _titleSelectColor; 113 | } 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATRootViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATRootViewController.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ATCustomViewProtocol.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | #pragma mark - ATRootViewController 15 | 16 | @protocol ATRootViewControllerDelegate 17 | 18 | @end 19 | 20 | @interface ATRootViewController : UIViewController 21 | 22 | @property (nonatomic, weak) id delegate; 23 | 24 | // default YES 25 | @property (nonatomic, assign) BOOL autorotateEnabled; 26 | 27 | @property (nonatomic, readonly) NSArray *currentTitles; 28 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle; 29 | - (void)removeCustiomViewForTitle:(NSString *)aTitle; 30 | - (void)removeAllCustomViews; 31 | 32 | // used for ATAssistiveTools 33 | @property (nonatomic, weak) UIWindow *assistiveWindow; 34 | @property (nonatomic, readonly) CGRect shrinkedWindowFrame; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATRootViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATRootViewController.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATRootViewController.h" 10 | #import "ATShrinkInfoView.h" 11 | #import "ATExpandInfoView.h" 12 | 13 | #define kATAnimationDuration 0.2f 14 | 15 | typedef NS_ENUM(NSUInteger, ATRootViewControllerStatus) { 16 | ATRootViewControllerStatusShrink = 1, 17 | ATRootViewControllerStatusExpand = 2, 18 | }; 19 | 20 | #pragma mark - ATRootViewController 21 | 22 | @interface ATRootViewController () 23 | 24 | @property (nonatomic, strong) ATShrinkInfoView *shrinkInfoView; 25 | @property (nonatomic, strong) ATExpandInfoView *expandInfoView; 26 | 27 | @property (nonatomic, assign) CGRect curScreenBounds; 28 | @property (nonatomic, assign) CGRect expandedWindowFrame; 29 | @property (nonatomic, assign) CGRect shrinkedWindowFrame; 30 | 31 | @property (nonatomic, assign) ATRootViewControllerStatus status; 32 | @property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer; 33 | 34 | @end 35 | 36 | @implementation ATRootViewController 37 | 38 | #pragma mark - Private: Life Cycle 39 | 40 | - (instancetype)init 41 | { 42 | if (self = [super init]) 43 | { 44 | [self initProperties]; 45 | } 46 | return self; 47 | } 48 | 49 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 50 | { 51 | if (self = [super initWithCoder:aDecoder]) 52 | { 53 | [self initProperties]; 54 | } 55 | return self; 56 | } 57 | 58 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 59 | { 60 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) 61 | { 62 | [self initProperties]; 63 | } 64 | return self; 65 | } 66 | 67 | - (void)dealloc 68 | { 69 | self.shrinkInfoView.status = ATShrinkInfoViewStatusNone; 70 | } 71 | 72 | - (void)viewDidLoad 73 | { 74 | [super viewDidLoad]; 75 | 76 | [self buildRootViewController]; 77 | } 78 | 79 | - (void)viewWillAppear:(BOOL)animated 80 | { 81 | [super viewWillAppear:animated]; 82 | } 83 | 84 | - (void)viewDidAppear:(BOOL)animated 85 | { 86 | [super viewDidAppear:animated]; 87 | } 88 | 89 | - (void)viewWillDisappear:(BOOL)animated 90 | { 91 | [super viewWillDisappear:animated]; 92 | } 93 | 94 | #pragma mark - Private: Initilization 95 | 96 | - (void)buildRootViewController 97 | { 98 | [self initShrinkInfoView]; 99 | 100 | [self initExpandInfoView]; 101 | } 102 | 103 | - (void)initProperties 104 | { 105 | _curScreenBounds = [[UIScreen mainScreen] bounds]; 106 | _expandedWindowFrame = CGRectMake((CGRectGetWidth(_curScreenBounds)-kATExpandViewWidth)/2.0, (CGRectGetHeight(_curScreenBounds)-kATExpandViewHeight)/2.0, kATExpandViewWidth, kATExpandViewHeight); 107 | _shrinkedWindowFrame = [self normalizdFrameToScreenSide:CGRectMake(0, CGRectGetMidY(_curScreenBounds), kATShrinkViewWidth, kATShrinkViewWidth)]; 108 | 109 | _status = ATRootViewControllerStatusShrink; 110 | _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognizerAction:)]; 111 | 112 | _autorotateEnabled = YES; 113 | } 114 | 115 | - (void)initShrinkInfoView 116 | { 117 | _shrinkInfoView = [[ATShrinkInfoView alloc] initWithFrame:CGRectMake(0, 0, kATShrinkViewWidth, kATShrinkViewWidth)]; 118 | _shrinkInfoView.delegate = self; 119 | _shrinkInfoView.hidden = NO; 120 | 121 | [self.shrinkInfoView addGestureRecognizer:self.panGestureRecognizer]; 122 | [self.view addSubview:self.shrinkInfoView]; 123 | } 124 | 125 | - (void)initExpandInfoView 126 | { 127 | _expandInfoView = [[ATExpandInfoView alloc] initWithFrame:CGRectMake(0, 0, kATExpandViewWidth, kATExpandViewHeight)]; 128 | _expandInfoView.delegate = self; 129 | _expandInfoView.hidden = YES; 130 | 131 | [self.view addSubview:self.expandInfoView]; 132 | } 133 | 134 | #pragma mark - Private: Interface Orientations 135 | 136 | - (BOOL)prefersStatusBarHidden 137 | { 138 | return NO; 139 | } 140 | 141 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations 142 | { 143 | return self.autorotateEnabled ? UIInterfaceOrientationMaskAll : UIInterfaceOrientationMaskPortrait; 144 | } 145 | 146 | - (BOOL)shouldAutorotate 147 | { 148 | return self.autorotateEnabled; 149 | } 150 | 151 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator 152 | { 153 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 154 | 155 | [coordinator animateAlongsideTransition:^(id context) { 156 | 157 | if (!self.autorotateEnabled) 158 | { 159 | return; 160 | } 161 | 162 | CGRect preScreenBounds = self.curScreenBounds; 163 | CGRect curScreenBounds = [[UIScreen mainScreen] bounds]; 164 | 165 | if (CGRectEqualToRect(preScreenBounds, curScreenBounds)) 166 | { 167 | return; 168 | } 169 | 170 | double xRate = curScreenBounds.size.width / preScreenBounds.size.width; 171 | double yRate = curScreenBounds.size.height / preScreenBounds.size.height; 172 | self.curScreenBounds = curScreenBounds; 173 | 174 | // calculate shrinked window frame 175 | CGRect extendFrame = CGRectInset(self.shrinkedWindowFrame, -kATShrinkViewMargin, -kATShrinkViewMargin); 176 | 177 | CGPoint resOrigin = CGPointMake(extendFrame.origin.x * xRate, extendFrame.origin.y * yRate); 178 | extendFrame.origin.x = resOrigin.x; 179 | extendFrame.origin.y = resOrigin.y; 180 | 181 | CGRect resFrame = CGRectInset(extendFrame, kATShrinkViewMargin, kATShrinkViewMargin); 182 | self.shrinkedWindowFrame = [self normalizdFrameToScreenSide:resFrame]; 183 | 184 | // calculate expended window frame 185 | CGPoint newExpandOrigin = CGPointMake(self.expandedWindowFrame.origin.x * xRate, self.expandedWindowFrame.origin.y * yRate); 186 | CGRect newExpandFrame = CGRectMake(newExpandOrigin.x, newExpandOrigin.y, self.expandedWindowFrame.size.width, self.expandedWindowFrame.size.height); 187 | self.expandedWindowFrame = newExpandFrame; 188 | 189 | if (self.status == ATRootViewControllerStatusShrink) 190 | { 191 | self.assistiveWindow.frame = self.shrinkedWindowFrame; 192 | } 193 | else if (self.status == ATRootViewControllerStatusExpand) 194 | { 195 | self.assistiveWindow.frame = self.expandedWindowFrame; 196 | } 197 | else 198 | { 199 | 200 | } 201 | 202 | } completion:^(id context) { 203 | 204 | }]; 205 | } 206 | 207 | #pragma mark - Private: Gesture Recognizer Action 208 | 209 | - (void)panGestureRecognizerAction:(UIPanGestureRecognizer *)panGesture 210 | { 211 | if (panGesture.state == UIGestureRecognizerStateBegan) 212 | { 213 | // expand assistive window so that shrink info view can move to everywhere 214 | self.assistiveWindow.frame = [[UIScreen mainScreen] bounds]; 215 | 216 | // move shrinkInfoView to touchPoint 217 | CGPoint touchPoint = [panGesture locationInView:self.assistiveWindow]; 218 | self.shrinkInfoView.center = touchPoint; 219 | 220 | // change shrinkInfoView status 221 | self.shrinkInfoView.status = ATShrinkInfoViewStatusActive; 222 | } 223 | else if (panGesture.state == UIGestureRecognizerStateChanged) 224 | { 225 | // move shrink info view 226 | CGPoint touchPoint = [panGesture locationInView:self.assistiveWindow]; 227 | self.shrinkInfoView.center = touchPoint; 228 | } 229 | else if (panGesture.state == UIGestureRecognizerStateEnded 230 | || panGesture.state == UIGestureRecognizerStateFailed 231 | || panGesture.state == UIGestureRecognizerStateCancelled) 232 | { 233 | self.assistiveWindow.frame = CGRectMake(self.shrinkInfoView.frame.origin.x, self.shrinkInfoView.frame.origin.y, kATShrinkViewWidth, kATShrinkViewWidth); 234 | self.shrinkInfoView.frame = CGRectMake(0, 0, kATShrinkViewWidth, kATShrinkViewWidth); 235 | 236 | [UIView animateWithDuration:kATAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 237 | 238 | // strict to screen side 239 | self.assistiveWindow.frame = [self normalizdFrameToScreenSide:self.assistiveWindow.frame]; 240 | 241 | } completion:^(BOOL finished) { 242 | 243 | // save frame for shrink amination 244 | self.shrinkedWindowFrame = self.assistiveWindow.frame; 245 | 246 | // change shrinkInfoView status 247 | self.shrinkInfoView.status = ATShrinkInfoViewStatusCountdown; 248 | }]; 249 | } 250 | } 251 | 252 | #pragma mark - Private: ATShrinkInfoViewDelegate 253 | 254 | - (void)shrinkInfoViewTaped:(ATShrinkInfoView *)shrinkView atPoint:(CGPoint)tapPoint 255 | { 256 | self.status = ATRootViewControllerStatusExpand; 257 | 258 | // change shrinkInfoView status 259 | self.shrinkInfoView.status = ATShrinkInfoViewStatusActive; 260 | 261 | [self.expandInfoView expandInfoViewWillExpand]; 262 | 263 | [UIView animateWithDuration:kATAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 264 | self.shrinkInfoView.hidden = YES; 265 | self.expandInfoView.hidden = NO; 266 | 267 | self.assistiveWindow.frame = self.expandedWindowFrame; 268 | self.expandInfoView.frame = CGRectMake(0, 0, kATExpandViewWidth, kATExpandViewHeight); 269 | 270 | } completion:^(BOOL finished) { 271 | 272 | [self.expandInfoView expandInfoViewDidExpand]; 273 | }]; 274 | } 275 | 276 | #pragma mark - Private: ATExpandInfoViewDelegate 277 | 278 | - (void)expandInfoViewCloseAction:(ATExpandInfoView *)expandView 279 | { 280 | self.status = ATRootViewControllerStatusShrink; 281 | 282 | [self.expandInfoView expandInfoViewWillShrink]; 283 | 284 | [UIView animateWithDuration:kATAnimationDuration animations:^{ 285 | 286 | self.assistiveWindow.frame = self.shrinkedWindowFrame; 287 | self.shrinkInfoView.frame = CGRectMake(0, 0, kATShrinkViewWidth, kATShrinkViewWidth); 288 | 289 | } completion:^(BOOL finished) { 290 | self.shrinkInfoView.hidden = NO; 291 | self.expandInfoView.hidden = YES; 292 | 293 | // change shrinkInfoView status 294 | self.shrinkInfoView.status = ATShrinkInfoViewStatusCountdown; 295 | 296 | [self.expandInfoView expandInfoViewDidShrink]; 297 | }]; 298 | } 299 | 300 | #pragma mark - Public: Interface 301 | 302 | - (NSArray *)currentTitles 303 | { 304 | return self.expandInfoView.currentTitles; 305 | } 306 | 307 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle 308 | { 309 | [self.expandInfoView addCustomView:aView forTitle:aTitle]; 310 | } 311 | 312 | - (void)removeCustiomViewForTitle:(NSString *)aTitle 313 | { 314 | [self.expandInfoView removeCustiomViewForTitle:aTitle]; 315 | } 316 | 317 | - (void)removeAllCustomViews 318 | { 319 | [self.expandInfoView removeAllCustomViews]; 320 | } 321 | 322 | #pragma mark - Private: Handle Position 323 | 324 | - (CGRect)normalizdFrameToScreenSide:(CGRect)curFrame 325 | { 326 | CGRect result = curFrame; 327 | 328 | CGSize screenSize = [[UIScreen mainScreen] bounds].size; 329 | CGPoint curCenter = CGPointMake(CGRectGetMidX(curFrame), CGRectGetMidY(curFrame)); 330 | 331 | if (curCenter.y < screenSize.height * 0.15) 332 | { 333 | result.origin.x = MAX(0, MIN(screenSize.width - curFrame.size.width, result.origin.x)); 334 | result.origin.y = kATShrinkViewMargin; 335 | } 336 | else if (curCenter.y > screenSize.height * 0.85) 337 | { 338 | result.origin.x = MAX(0, MIN(screenSize.width - curFrame.size.width, result.origin.x)); 339 | result.origin.y = screenSize.height - curFrame.size.height - kATShrinkViewMargin; 340 | } 341 | else if (curCenter.x < screenSize.width/2.0) 342 | { 343 | result.origin.x = kATShrinkViewMargin; 344 | } 345 | else if (curCenter.x >= screenSize.width/2.0) 346 | { 347 | result.origin.x = screenSize.width - curFrame.size.width - kATShrinkViewMargin; 348 | } 349 | 350 | return result; 351 | } 352 | 353 | @end 354 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATShrinkInfoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATShrinkInfoView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define kATShrinkViewActiveAlpha 0.75f 12 | #define kATShrinkViewDeactiveAlpha 0.25f 13 | #define kATShrinkViewWidth 60.f 14 | #define kATShrinkViewMargin 2.f 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | typedef NS_ENUM(NSUInteger, ATShrinkInfoViewStatus) { 19 | ATShrinkInfoViewStatusNone = 0,//just stop timer 20 | ATShrinkInfoViewStatusActive = 1, 21 | ATShrinkInfoViewStatusCountdown = 2, 22 | ATShrinkInfoViewStatusDeactive = 3, 23 | }; 24 | 25 | @class ATShrinkInfoView; 26 | 27 | @protocol ATShrinkInfoViewDelegate 28 | 29 | - (void)shrinkInfoViewTaped:(ATShrinkInfoView *)shrinkView atPoint:(CGPoint)tapPoint; 30 | 31 | @end 32 | 33 | @interface ATShrinkInfoView : UIView 34 | 35 | @property (nonatomic, weak) id delegate; 36 | 37 | @property (nonatomic, assign) ATShrinkInfoViewStatus status; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /ATAssistiveTools/ATShrinkInfoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATShrinkInfoView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATShrinkInfoView.h" 10 | 11 | #define kATShrinkViewCornerRadius (kATShrinkViewWidth/5.0) 12 | #define kATShrinkViewAnimationDuration 0.2f 13 | 14 | @interface ATShrinkInfoView () 15 | 16 | @property (nonatomic, strong) NSTimer *alphaTimer; 17 | @property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer; 18 | 19 | @property (nonatomic, strong) UIView *contentView; 20 | @property (nonatomic, strong) UILabel *mainTipLabel; 21 | 22 | @end 23 | 24 | @implementation ATShrinkInfoView 25 | 26 | #pragma mark - Life Cycle 27 | 28 | - (instancetype)initWithFrame:(CGRect)frame 29 | { 30 | if (self = [super initWithFrame:frame]) 31 | { 32 | [self buildShrinkInfoView]; 33 | } 34 | return self; 35 | } 36 | 37 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 38 | { 39 | if (self = [super initWithCoder:aDecoder]) 40 | { 41 | [self buildShrinkInfoView]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)dealloc 47 | { 48 | [self stopAlphaTimer]; 49 | } 50 | 51 | - (void)buildShrinkInfoView 52 | { 53 | self.layer.cornerRadius = kATShrinkViewCornerRadius; 54 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:kATShrinkViewDeactiveAlpha]; 55 | 56 | [self initProperties]; 57 | 58 | [self initContentView]; 59 | 60 | [self initMainTipLabel]; 61 | 62 | [self.mainTipLabel setText:@"ATAssistiveTools"]; 63 | } 64 | 65 | - (void)initProperties 66 | { 67 | _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureRecognizerAction:)]; 68 | [_tapGestureRecognizer setNumberOfTapsRequired:1]; 69 | [_tapGestureRecognizer setNumberOfTouchesRequired:1]; 70 | 71 | [self addGestureRecognizer:_tapGestureRecognizer]; 72 | } 73 | 74 | - (void)initContentView 75 | { 76 | _contentView = [[UIView alloc] init]; 77 | [_contentView setTranslatesAutoresizingMaskIntoConstraints:NO]; 78 | 79 | [self addSubview:_contentView]; 80 | 81 | NSDictionary *views = NSDictionaryOfVariableBindings(_contentView); 82 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[_contentView]-right-|" options:0 metrics:@{@"left":@(kATShrinkViewCornerRadius/2.0),@"right":@(kATShrinkViewCornerRadius/2.0)} views:views]]; 83 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[_contentView]-bottom-|" options:0 metrics:@{@"top":@(kATShrinkViewCornerRadius/2.0),@"bottom":@(kATShrinkViewCornerRadius/2.0)} views:views]]; 84 | } 85 | 86 | - (void)initMainTipLabel 87 | { 88 | _mainTipLabel = [[UILabel alloc] init]; 89 | _mainTipLabel.translatesAutoresizingMaskIntoConstraints = NO; 90 | _mainTipLabel.numberOfLines = 4; 91 | _mainTipLabel.textColor = [UIColor whiteColor]; 92 | _mainTipLabel.font = [UIFont systemFontOfSize:8]; 93 | 94 | [self.contentView addSubview:_mainTipLabel]; 95 | 96 | NSDictionary *views = NSDictionaryOfVariableBindings(_mainTipLabel); 97 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_mainTipLabel]|" options:0 metrics:nil views:views]]; 98 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_mainTipLabel]|" options:0 metrics:nil views:views]]; 99 | } 100 | 101 | #pragma mark - Timer Action 102 | 103 | - (void)startAlphaTimer 104 | { 105 | if (self.alphaTimer != nil) 106 | { 107 | return; 108 | } 109 | 110 | self.alphaTimer = [NSTimer timerWithTimeInterval:4.f target:self selector:@selector(timerAction:) userInfo:nil repeats:NO]; 111 | [[NSRunLoop currentRunLoop] addTimer:self.alphaTimer forMode:NSRunLoopCommonModes]; 112 | } 113 | 114 | - (void)stopAlphaTimer 115 | { 116 | if (self.alphaTimer == nil) 117 | { 118 | return; 119 | } 120 | 121 | [self.alphaTimer invalidate]; 122 | self.alphaTimer = nil; 123 | } 124 | 125 | - (void)timerAction:(NSTimer *)timer 126 | { 127 | [UIView animateWithDuration:kATShrinkViewAnimationDuration animations:^{ 128 | self.status = ATShrinkInfoViewStatusDeactive; 129 | }]; 130 | } 131 | 132 | #pragma mark - Interface 133 | 134 | - (void)setStatus:(ATShrinkInfoViewStatus)status 135 | { 136 | [self stopAlphaTimer]; 137 | 138 | _status = status; 139 | if (_status == ATShrinkInfoViewStatusActive) 140 | { 141 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:kATShrinkViewActiveAlpha]; 142 | } 143 | else if (_status == ATShrinkInfoViewStatusCountdown) 144 | { 145 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:kATShrinkViewActiveAlpha]; 146 | 147 | [self startAlphaTimer]; 148 | } 149 | else if (_status == ATShrinkInfoViewStatusDeactive) 150 | { 151 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:kATShrinkViewDeactiveAlpha]; 152 | } 153 | else 154 | { 155 | // do nothing 156 | } 157 | } 158 | 159 | #pragma mark - Gesture Recognizer Action 160 | 161 | - (void)tapGestureRecognizerAction:(UITapGestureRecognizer *)tapGesture 162 | { 163 | if (self.delegate) 164 | { 165 | CGPoint touchPoint = [tapGesture locationInView:self]; 166 | [self.delegate shrinkInfoViewTaped:self atPoint:touchPoint]; 167 | } 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATAssistiveTools.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATAssistiveTools.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ATCustomViewProtocol.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface ATAssistiveTools : NSObject 15 | 16 | + (instancetype)sharedInstance; 17 | 18 | - (void)show; 19 | 20 | @property (nonatomic, readonly) UIWindow *mainWindow; 21 | 22 | @end 23 | 24 | @interface ATAssistiveTools (CustomView) 25 | 26 | 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATAssistiveTools.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATAssistiveTools.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATAssistiveTools.h" 10 | #import "ATContainerWindow.h" 11 | #import "ATRootViewController.h" 12 | 13 | // Custom Views 14 | #import "ATDeviceLogsView.h" 15 | #import "ATFakeLocationView.h" 16 | #import "ATSandboxViewerView.h" 17 | #import "ATGPSEmulatorView.h" 18 | 19 | @interface ATAssistiveTools () 20 | 21 | @property (nonatomic, strong) ATContainerWindow *assistiveWindow; 22 | @property (nonatomic, strong) ATRootViewController *rootViewController; 23 | 24 | @end 25 | 26 | @implementation ATAssistiveTools 27 | 28 | #pragma mark - Private: Life Cycle 29 | 30 | + (instancetype)sharedInstance 31 | { 32 | static ATAssistiveTools *sharedInstance; 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | sharedInstance = [[ATAssistiveTools alloc] init]; 36 | }); 37 | return sharedInstance; 38 | } 39 | 40 | - (instancetype)init 41 | { 42 | if (self = [super init]) 43 | { 44 | [self createAssistiveTools]; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)createAssistiveTools 50 | { 51 | [self initProperties]; 52 | 53 | [self initAssisticeWindowAndController]; 54 | } 55 | 56 | #pragma mark - Private: Initialization 57 | 58 | - (void)initProperties 59 | { 60 | 61 | } 62 | 63 | - (void)initAssisticeWindowAndController 64 | { 65 | // ATRootViewController 66 | _rootViewController = [[ATRootViewController alloc] init]; 67 | _rootViewController.delegate = self; 68 | _rootViewController.autorotateEnabled = YES; 69 | 70 | // ATContainerWindow 71 | _assistiveWindow = [[ATContainerWindow alloc] initWithFrame:_rootViewController.shrinkedWindowFrame]; 72 | _assistiveWindow.containerDelegate = self; 73 | _assistiveWindow.windowLevel = CGFLOAT_MAX; 74 | _assistiveWindow.layer.masksToBounds = YES; 75 | _assistiveWindow.backgroundColor = [UIColor clearColor]; 76 | 77 | self.rootViewController.assistiveWindow = self.assistiveWindow; 78 | self.assistiveWindow.rootViewController = self.rootViewController; 79 | } 80 | 81 | #pragma mark - Private: Load Custom View 82 | 83 | - (void)loadCustomViews 84 | { 85 | ATFakeLocationView *simLocView = [[ATFakeLocationView alloc] init]; 86 | [self addCustomView:simLocView forTitle:@"FakeLocation"]; 87 | 88 | ATSandboxViewerView *sandboxView = [[ATSandboxViewerView alloc] init]; 89 | [self addCustomView:sandboxView forTitle:@"SandboxViewer"]; 90 | 91 | ATGPSEmulatorView *gpsSimView = [[ATGPSEmulatorView alloc] init]; 92 | [self addCustomView:gpsSimView forTitle:@"GPSSimlulator"]; 93 | 94 | ATDeviceLogsView *logsView = [[ATDeviceLogsView alloc] init]; 95 | [self addCustomView:logsView forTitle:@"DeviceLog"]; 96 | } 97 | 98 | #pragma mark - Public: Interface 99 | 100 | - (void)show 101 | { 102 | [self makeWindowVisible:self.assistiveWindow]; 103 | 104 | [self loadCustomViews]; 105 | } 106 | 107 | - (void)makeWindowVisible:(UIWindow *)window 108 | { 109 | UIWindow *currentKeyWindow = [[UIApplication sharedApplication] keyWindow]; 110 | if (currentKeyWindow == window) 111 | { 112 | [currentKeyWindow makeKeyAndVisible]; 113 | } 114 | else 115 | { 116 | [window makeKeyAndVisible]; 117 | [currentKeyWindow makeKeyWindow]; 118 | } 119 | } 120 | 121 | - (UIWindow *)mainWindow 122 | { 123 | return self.assistiveWindow; 124 | } 125 | 126 | - (NSArray *)currentTitles 127 | { 128 | return self.rootViewController.currentTitles; 129 | } 130 | 131 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle 132 | { 133 | [self.rootViewController addCustomView:aView forTitle:aTitle]; 134 | } 135 | 136 | - (void)removeCustiomViewForTitle:(NSString *)aTitle 137 | { 138 | [self.rootViewController removeCustiomViewForTitle:aTitle]; 139 | } 140 | 141 | - (void)removeAllCustomViews 142 | { 143 | [self.rootViewController removeAllCustomViews]; 144 | } 145 | 146 | #pragma mark - Private: ATContainerWindowDelegate 147 | 148 | //- (BOOL)customPointInside:(CGPoint)point withEvent:(UIEvent *)event 149 | //{ 150 | // BOOL inShrink = [self.shrinkInfoView pointInside:[self.assistiveWindow convertPoint:point toView:self.shrinkInfoView] withEvent:event]; 151 | // BOOL inExpand = [self.expandInfoView pointInside:[self.assistiveWindow convertPoint:point toView:self.expandInfoView] withEvent:event]; 152 | // 153 | // BOOL inside = inShrink || inExpand; 154 | // return inside; 155 | //} 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATContainerWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATContainerWindow.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @protocol ATContainerWindowDelegate 14 | 15 | //- (BOOL)customPointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; 16 | 17 | @end 18 | 19 | @interface ATContainerWindow : UIWindow 20 | 21 | @property (nonatomic, weak) id containerDelegate; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATContainerWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATContainerWindow.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATContainerWindow.h" 10 | 11 | @implementation ATContainerWindow 12 | 13 | //- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 14 | //{ 15 | // BOOL inside = [super pointInside:point withEvent:event]; 16 | // 17 | // if (inside && self.containerDelegate) 18 | // { 19 | // return [self.containerDelegate customPointInside:point withEvent:event]; 20 | // } 21 | // 22 | // return NO; 23 | //} 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomViewProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATCustomViewProtocol.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol ATCustomViewProtocol 12 | @optional 13 | 14 | - (void)customViewWillAppear; 15 | - (void)customViewDidAppear; 16 | - (void)customViewWillDisappear; 17 | - (void)customViewDidDisappear; 18 | 19 | - (void)customViewWillShrink; 20 | - (void)customViewDidShrink; 21 | - (void)customViewWillExpand; 22 | - (void)customViewDidExpand; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATDeviceLogsView/ATDeviceLogsView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATDeviceLogsView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/11. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ATCustomViewProtocol.h" 11 | 12 | @interface ATDeviceLogsView : UIView 13 | 14 | + (void)asyncReadDeviceLogsWithCompletionBlock:(void (^)(NSString *logs))completionBlock; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATDeviceLogsView/ATDeviceLogsView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATDeviceLogsView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/11. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATDeviceLogsView.h" 10 | 11 | #import "asl.h" 12 | #import 13 | 14 | @interface ATDeviceLogsView () 15 | 16 | @property (nonatomic, strong) UITextView *textView; 17 | @property (nonatomic, strong) UIButton *refreshButton; 18 | 19 | @end 20 | 21 | @implementation ATDeviceLogsView 22 | 23 | #pragma mark - Life Cycle 24 | 25 | - (instancetype)initWithFrame:(CGRect)frame 26 | { 27 | if (self = [super initWithFrame:frame]) 28 | { 29 | [self buildDeviceLogsView]; 30 | } 31 | return self; 32 | } 33 | 34 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 35 | { 36 | if (self = [super initWithCoder:aDecoder]) 37 | { 38 | [self buildDeviceLogsView]; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)buildDeviceLogsView 44 | { 45 | self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 46 | 47 | [self initProperties]; 48 | 49 | [self initSubviews]; 50 | } 51 | 52 | - (void)initProperties 53 | { 54 | 55 | } 56 | 57 | - (void)initSubviews 58 | { 59 | self.refreshButton = [UIButton buttonWithType:UIButtonTypeSystem]; 60 | self.refreshButton.translatesAutoresizingMaskIntoConstraints = NO; 61 | self.refreshButton.layer.borderColor = [UIColor blueColor].CGColor; 62 | self.refreshButton.layer.borderWidth = 0.5; 63 | self.refreshButton.titleLabel.font = [UIFont systemFontOfSize:15.0]; 64 | [self.refreshButton setTitle:@"Refresh" forState:UIControlStateNormal]; 65 | [self.refreshButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 66 | [self.refreshButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted]; 67 | [self.refreshButton addTarget:self action:@selector(refreshButtonAction:) forControlEvents:UIControlEventTouchUpInside]; 68 | 69 | self.textView = [[UITextView alloc] init]; 70 | self.textView.translatesAutoresizingMaskIntoConstraints = NO; 71 | self.textView.editable = NO; 72 | self.textView.selectable = NO; 73 | 74 | [self addSubview:self.refreshButton]; 75 | [self addSubview:self.textView]; 76 | 77 | NSDictionary *views = NSDictionaryOfVariableBindings(_textView, _refreshButton); 78 | 79 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_textView]|" options:0 metrics:nil views:views]]; 80 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[_refreshButton(==width)]" options:0 metrics:@{@"width":@(80)} views:views]]; 81 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[_refreshButton(==height)]-10-[_textView]|" options:0 metrics:@{@"height":@(20)} views:views]]; 82 | } 83 | 84 | #pragma mark - Button Action 85 | 86 | - (void)refreshButtonAction:(UIButton *)button 87 | { 88 | self.textView.text = @""; 89 | 90 | [self updateDeviceLog]; 91 | } 92 | 93 | #pragma mark - Methods 94 | 95 | - (void)updateDeviceLog 96 | { 97 | if ([[[UIDevice currentDevice] systemVersion] doubleValue] < 10.0) 98 | { 99 | __weak typeof(self) weakSelf = self; 100 | [ATDeviceLogsView asyncReadDeviceLogsWithCompletionBlock:^(NSString *logs) { 101 | weakSelf.textView.text = logs; 102 | [weakSelf.textView scrollRangeToVisible:NSMakeRange(weakSelf.textView.text.length-10, 10)]; 103 | }]; 104 | } 105 | else 106 | { 107 | self.textView.text = @"Onle applicable to system version less than 10.0!"; 108 | } 109 | } 110 | 111 | #pragma mark - ATCustomViewProtocol 112 | 113 | - (void)customViewDidAppear 114 | { 115 | [self updateDeviceLog]; 116 | } 117 | 118 | #pragma mark - Read Device Log 119 | 120 | + (void)asyncReadDeviceLogsWithCompletionBlock:(void (^)(NSString *logs))completionBlock 121 | { 122 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 123 | NSString *logs = [self readDeviceLogs]; 124 | dispatch_async(dispatch_get_main_queue(), ^{ 125 | if (completionBlock) 126 | { 127 | completionBlock(logs); 128 | } 129 | }); 130 | }); 131 | } 132 | 133 | + (NSString *)readDeviceLogs 134 | { 135 | aslmsg q, m; 136 | int i; 137 | const char *key, *val; 138 | NSMutableString *logs = [NSMutableString stringWithString:@""]; 139 | 140 | q = asl_new(ASL_TYPE_QUERY); 141 | 142 | aslresponse r = asl_search(NULL, q); 143 | while (NULL != (m = asl_next(r))) 144 | { 145 | NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary]; 146 | 147 | for (i = 0; (NULL != (key = asl_key(m, i))); i++) 148 | { 149 | NSString *keyString = [NSString stringWithUTF8String:(char *)key]; 150 | 151 | val = asl_get(m, key); 152 | 153 | NSString *string = val != NULL ? [NSString stringWithUTF8String:val] : nil; 154 | [tmpDict setValue:string forKey:keyString]; 155 | } 156 | 157 | NSString *line = [NSString stringWithFormat:@"%@ %@[%@] %@\n", [NSDate dateWithTimeIntervalSince1970:[tmpDict[@"Time"] intValue]], tmpDict[@"Sender"], tmpDict[@"PID"], tmpDict[@"Message"]]; 158 | 159 | [logs appendString:line]; 160 | } 161 | asl_release(r); 162 | 163 | return logs; 164 | } 165 | 166 | @end 167 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATFakeLocationView/ATFakeLocationView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATFakeLocationView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/14. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "ATCustomViewProtocol.h" 12 | 13 | @interface ATFakeLocationView : UIView 14 | 15 | @end 16 | 17 | @interface ATSimlulateCoordinate : NSObject 18 | 19 | + (instancetype)sharedInstance; 20 | 21 | @property (nonatomic, assign) BOOL useCLLocationCoordiante; 22 | @property (nonatomic, assign) BOOL useCLLocationManager; 23 | 24 | @property (nonatomic, assign) CLLocationCoordinate2D externalWGS84Coordinate; 25 | @property (nonatomic, assign) CLLocationCoordinate2D externalGCJ02Coordinate; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/AMapLogFileManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMapLogFileManager.h 3 | // SensorRecoder 4 | // 5 | // Created by liubo on 11/15/16. 6 | // Copyright © 2016 AutoNavi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | #pragma mark - CMAcceleration 14 | 15 | @interface AMapLogFileManager : NSObject 16 | 17 | + (AMapLogFileManager *)logFileManagerWithFileName:(NSString *)logFileName; 18 | 19 | - (BOOL)addLocationLogString:(NSString *)logString; 20 | - (BOOL)addMotionLogString:(NSString *)logString; 21 | 22 | @end 23 | 24 | #pragma mark - CMAcceleration 25 | 26 | @interface AMapLogFileManagerUtility : NSObject 27 | 28 | //Location 29 | + (NSString *)logStringFormCLLocation:(CLLocation *)aLocation; 30 | + (CLLocation *)CLLocationFromLogString:(NSString *)logString; 31 | 32 | //Motion 33 | + (NSString *)logStringFromCMDeviceMotion:(CMDeviceMotion *)deviceMotion; 34 | + (void)attitudeValueFromLogString:(NSString *)logString 35 | roll:(double *)roll 36 | pitch:(double *)pitch 37 | yaw:(double *)yaw 38 | rotationMatrix:(CMRotationMatrix *)martix 39 | quaternion:(CMQuaternion *)quaternion; 40 | + (CMRotationRate)rotationRateFromLogString:(NSString *)logString; 41 | + (CMAcceleration)accelerationFromLogString:(NSString *)logString; 42 | + (CMAcceleration)userAccelerationFromLogString:(NSString *)logString; 43 | + (CMCalibratedMagneticField)magneticFieldFromLogString:(NSString *)logString; 44 | 45 | @end 46 | 47 | #pragma mark - AMapLocationLogPlayback 48 | 49 | typedef void(^AMapLocationLogPlaybackBlock)(CLLocation *location, NSUInteger index, BOOL *stop); 50 | 51 | @interface AMapLocationLogPlayback : NSObject 52 | 53 | @property (nonatomic, readonly) BOOL isPlaying; 54 | 55 | + (AMapLocationLogPlayback *)playbackWithFilePath:(NSString *)filePath; 56 | 57 | - (void)startPlaybackUsingLocationBlock:(AMapLocationLogPlaybackBlock)locationBlock; 58 | - (void)stopPlayback; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/AMapLogFileManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // AMapLogFileManager.m 3 | // SensorRecoder 4 | // 5 | // Created by liubo on 11/15/16. 6 | // Copyright © 2016 AutoNavi. All rights reserved. 7 | // 8 | 9 | #import "AMapLogFileManager.h" 10 | #include 11 | 12 | #define kAMapLogFileManagerDir @"AMapLogFiles" 13 | 14 | #define kAMapLogFileLocation @"logFile_Location.txt" 15 | #define kAMapLogFileMotion @"logFile_Motion.txt" 16 | #define kAMapLogFileInfo @"logFile_Info.txt" 17 | 18 | @interface AMapLogFileManager () 19 | { 20 | NSString *_basePath; 21 | NSString *_fileName; 22 | NSString *_fullFilePath; 23 | NSString *_startTime; 24 | 25 | NSString *_filePathLocation; 26 | NSString *_filePathMotion; 27 | NSString *_filePathInfo; 28 | } 29 | 30 | @end 31 | 32 | @implementation AMapLogFileManager 33 | 34 | #pragma mark - Life Cycle 35 | 36 | - (instancetype)initWithLogFileName:(NSString *)logFileName 37 | { 38 | if (self = [super init]) 39 | { 40 | _fileName = logFileName; 41 | 42 | [self buildLogFileManager]; 43 | } 44 | return self; 45 | } 46 | 47 | - (void)dealloc 48 | { 49 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 50 | formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]; 51 | formatter.dateFormat = @"yyyy_MM_dd_HH_mm_ss"; 52 | 53 | [self addLogString:_startTime forFile:_filePathInfo]; 54 | [self addLogString:[formatter stringFromDate:[NSDate date]] forFile:_filePathInfo]; 55 | } 56 | 57 | - (void)buildLogFileManager 58 | { 59 | NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 60 | _basePath = [documentPath stringByAppendingPathComponent:kAMapLogFileManagerDir]; 61 | 62 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 63 | formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]; 64 | formatter.dateFormat = @"yyyy_MM_dd_HH_mm_ss"; 65 | _startTime = [formatter stringFromDate:[NSDate date]]; 66 | 67 | NSString *currentLogDirectory = _startTime; 68 | if (_fileName != nil || _fileName.length > 0) 69 | { 70 | currentLogDirectory = [NSString stringWithFormat:@"%@_%@", _startTime, _fileName]; 71 | } 72 | _fullFilePath = [_basePath stringByAppendingPathComponent:currentLogDirectory]; 73 | 74 | if ([[NSFileManager defaultManager] fileExistsAtPath:_fullFilePath] == NO) 75 | { 76 | NSError *error = nil; 77 | [[NSFileManager defaultManager] createDirectoryAtPath:_fullFilePath 78 | withIntermediateDirectories:YES 79 | attributes:nil 80 | error:&error]; 81 | 82 | if (error) 83 | { 84 | NSLog(@"createDirectoryError:%@", error); 85 | } 86 | } 87 | 88 | NSLog(@"filePath:%@", _fullFilePath); 89 | 90 | _filePathLocation = [_fullFilePath stringByAppendingPathComponent:kAMapLogFileLocation]; 91 | _filePathMotion = [_fullFilePath stringByAppendingPathComponent:kAMapLogFileMotion]; 92 | _filePathInfo = [_fullFilePath stringByAppendingPathComponent:kAMapLogFileInfo]; 93 | } 94 | 95 | #pragma mark - Interface 96 | 97 | + (AMapLogFileManager *)logFileManagerWithFileName:(NSString *)logFileName; 98 | { 99 | AMapLogFileManager *logFileManager = [[AMapLogFileManager alloc] initWithLogFileName:logFileName]; 100 | return logFileManager; 101 | } 102 | 103 | - (BOOL)addLocationLogString:(NSString *)logString 104 | { 105 | return [self addLogString:logString forFile:_filePathLocation]; 106 | } 107 | 108 | - (BOOL)addMotionLogString:(NSString *)logString 109 | { 110 | return [self addLogString:logString forFile:_filePathMotion]; 111 | } 112 | 113 | - (BOOL)addLogString:(NSString *)logString forFile:(NSString *)filePath 114 | { 115 | FILE *file = fopen([filePath UTF8String], "at+"); 116 | 117 | if (file == NULL) 118 | { 119 | NSLog(@"open file failed: %@", filePath); 120 | return NO; 121 | } 122 | 123 | fprintf(file, "%s\n", logString.UTF8String); 124 | 125 | fclose(file); 126 | file = NULL; 127 | 128 | // NSLog(@"log:%@", logString); 129 | 130 | return YES; 131 | } 132 | 133 | @end 134 | 135 | @implementation AMapLogFileManagerUtility 136 | 137 | + (NSString *)logStringFormCLLocation:(CLLocation *)aLocation 138 | { 139 | if (aLocation == nil) 140 | { 141 | return nil; 142 | } 143 | 144 | NSMutableString *logString = [[NSMutableString alloc] init]; 145 | 146 | [logString appendFormat:@"%f,%f,%f,%f,%f,%f,%f,%f", aLocation.coordinate.latitude, aLocation.coordinate.longitude, aLocation.altitude, aLocation.horizontalAccuracy, aLocation.verticalAccuracy, aLocation.course, aLocation.speed, [aLocation.timestamp timeIntervalSince1970]]; 147 | 148 | return logString; 149 | } 150 | 151 | + (CLLocation *)CLLocationFromLogString:(NSString *)logString 152 | { 153 | if (logString == nil) 154 | { 155 | return nil; 156 | } 157 | 158 | NSArray *components = [logString componentsSeparatedByString:@","]; 159 | 160 | double lat = [[components objectAtIndex:0] doubleValue]; 161 | double lon = [[components objectAtIndex:1] doubleValue]; 162 | CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(lat, lon); 163 | 164 | double altitude = [[components objectAtIndex:2] doubleValue]; 165 | double horAccuracy = [[components objectAtIndex:3] doubleValue]; 166 | double verAccuracy = [[components objectAtIndex:4] doubleValue]; 167 | double course = [[components objectAtIndex:5] doubleValue]; 168 | double speed = [[components objectAtIndex:6] doubleValue]; 169 | NSDate *timestamp = [NSDate dateWithTimeIntervalSince1970:[[components objectAtIndex:7] doubleValue]]; 170 | 171 | CLLocation *location = [[CLLocation alloc] initWithCoordinate:coordinate altitude:altitude horizontalAccuracy:horAccuracy verticalAccuracy:verAccuracy course:course speed:speed timestamp:timestamp]; 172 | 173 | return location; 174 | } 175 | 176 | + (NSString *)logStringFromCMDeviceMotion:(CMDeviceMotion *)deviceMotion 177 | { 178 | NSMutableString *logString = [[NSMutableString alloc] init]; 179 | 180 | //attitude 181 | CMAttitude *aAttitude = deviceMotion.attitude; 182 | [logString appendFormat:@"%f,%f,%f,", aAttitude.roll, aAttitude.pitch, aAttitude.yaw]; 183 | [logString appendFormat:@"%f,%f,%f,%f,%f,%f,%f,%f,%f,", aAttitude.rotationMatrix.m11, aAttitude.rotationMatrix.m12, aAttitude.rotationMatrix.m13, aAttitude.rotationMatrix.m21, aAttitude.rotationMatrix.m22, aAttitude.rotationMatrix.m23, aAttitude.rotationMatrix.m31, aAttitude.rotationMatrix.m32, aAttitude.rotationMatrix.m33]; 184 | [logString appendFormat:@"%f,%f,%f,%f,", aAttitude.quaternion.x, aAttitude.quaternion.y, aAttitude.quaternion.z, aAttitude.quaternion.w]; 185 | 186 | //rotationRate 187 | [logString appendFormat:@"%f,%f,%f,", deviceMotion.rotationRate.x, deviceMotion.rotationRate.y, deviceMotion.rotationRate.z]; 188 | 189 | //gravity 190 | [logString appendFormat:@"%f,%f,%f,", deviceMotion.gravity.x, deviceMotion.gravity.y, deviceMotion.gravity.z]; 191 | 192 | //userAcceleration 193 | [logString appendFormat:@"%f,%f,%f,", deviceMotion.userAcceleration.x, deviceMotion.userAcceleration.y, deviceMotion.userAcceleration.z]; 194 | 195 | //magneticField 196 | [logString appendFormat:@"%f,%f,%f,%d,", deviceMotion.magneticField.field.x, deviceMotion.magneticField.field.y, deviceMotion.magneticField.field.z, deviceMotion.magneticField.accuracy]; 197 | 198 | //timestamp 199 | [logString appendFormat:@"%f,%f", deviceMotion.timestamp, [[NSDate date] timeIntervalSince1970]]; 200 | 201 | return logString; 202 | } 203 | 204 | + (void)attitudeValueFromLogString:(NSString *)logString 205 | roll:(double *)roll 206 | pitch:(double *)pitch 207 | yaw:(double *)yaw 208 | rotationMatrix:(CMRotationMatrix *)martix 209 | quaternion:(CMQuaternion *)quaternion 210 | { 211 | NSArray *components = [logString componentsSeparatedByString:@","]; 212 | 213 | if (roll != NULL) *roll = [[components objectAtIndex:0] doubleValue]; 214 | if (pitch != NULL) *pitch = [[components objectAtIndex:1] doubleValue]; 215 | if (yaw != NULL) *yaw = [[components objectAtIndex:2] doubleValue]; 216 | 217 | if (martix != NULL) 218 | { 219 | (*martix).m11 = [[components objectAtIndex:3] doubleValue]; 220 | (*martix).m12 = [[components objectAtIndex:4] doubleValue]; 221 | (*martix).m13 = [[components objectAtIndex:5] doubleValue]; 222 | (*martix).m21 = [[components objectAtIndex:6] doubleValue]; 223 | (*martix).m22 = [[components objectAtIndex:7] doubleValue]; 224 | (*martix).m23 = [[components objectAtIndex:8] doubleValue]; 225 | (*martix).m31 = [[components objectAtIndex:9] doubleValue]; 226 | (*martix).m32 = [[components objectAtIndex:10] doubleValue]; 227 | (*martix).m33 = [[components objectAtIndex:11] doubleValue]; 228 | } 229 | 230 | if (quaternion != NULL) 231 | { 232 | (*quaternion).x = [[components objectAtIndex:12] doubleValue]; 233 | (*quaternion).y = [[components objectAtIndex:13] doubleValue]; 234 | (*quaternion).z = [[components objectAtIndex:14] doubleValue]; 235 | (*quaternion).w = [[components objectAtIndex:15] doubleValue]; 236 | } 237 | } 238 | 239 | + (CMRotationRate)rotationRateFromLogString:(NSString *)logString 240 | { 241 | NSArray *components = [logString componentsSeparatedByString:@","]; 242 | 243 | double x = [[components objectAtIndex:16] doubleValue]; 244 | double y = [[components objectAtIndex:17] doubleValue]; 245 | double z = [[components objectAtIndex:18] doubleValue]; 246 | 247 | CMRotationRate reVal = {x, y, z}; 248 | 249 | return reVal; 250 | } 251 | 252 | + (CMAcceleration)accelerationFromLogString:(NSString *)logString 253 | { 254 | NSArray *components = [logString componentsSeparatedByString:@","]; 255 | 256 | double x = [[components objectAtIndex:19] doubleValue]; 257 | double y = [[components objectAtIndex:20] doubleValue]; 258 | double z = [[components objectAtIndex:21] doubleValue]; 259 | 260 | CMAcceleration reVal = {x, y, z}; 261 | 262 | return reVal; 263 | } 264 | 265 | + (CMAcceleration)userAccelerationFromLogString:(NSString *)logString 266 | { 267 | NSArray *components = [logString componentsSeparatedByString:@","]; 268 | 269 | double x = [[components objectAtIndex:22] doubleValue]; 270 | double y = [[components objectAtIndex:23] doubleValue]; 271 | double z = [[components objectAtIndex:24] doubleValue]; 272 | 273 | CMAcceleration reVal = {x, y, z}; 274 | 275 | return reVal; 276 | } 277 | 278 | + (CMCalibratedMagneticField)magneticFieldFromLogString:(NSString *)logString 279 | { 280 | NSArray *components = [logString componentsSeparatedByString:@","]; 281 | 282 | double x = [[components objectAtIndex:25] doubleValue]; 283 | double y = [[components objectAtIndex:26] doubleValue]; 284 | double z = [[components objectAtIndex:27] doubleValue]; 285 | int accuracy = [[components objectAtIndex:28] intValue]; 286 | 287 | CMMagneticField field = {x, y, z}; 288 | CMCalibratedMagneticField reVal = {field, accuracy}; 289 | 290 | return reVal; 291 | } 292 | 293 | @end 294 | 295 | @interface AMapLocationLogPlayback () 296 | { 297 | NSString *_filePath; 298 | 299 | NSMutableArray *_locations; 300 | NSThread *_locationsThread; 301 | } 302 | 303 | @property (nonatomic, readwrite) BOOL isPlaying; 304 | 305 | @end 306 | 307 | @implementation AMapLocationLogPlayback 308 | 309 | #pragma mark - Life Cycle 310 | 311 | - (instancetype)initWithFilePath:(NSString *)filePath 312 | { 313 | if (self = [super init]) 314 | { 315 | _filePath = filePath; 316 | _locations = [NSMutableArray array]; 317 | _isPlaying = NO; 318 | 319 | [self parseLocationLogFile]; 320 | } 321 | 322 | return self; 323 | } 324 | 325 | - (void)dealloc 326 | { 327 | if (_locationsThread) 328 | { 329 | [_locationsThread cancel]; 330 | _locationsThread = nil; 331 | } 332 | } 333 | 334 | - (void)parseLocationLogFile 335 | { 336 | if (_filePath == nil) 337 | { 338 | return; 339 | } 340 | 341 | NSError *error = nil; 342 | NSString *logFileString = [NSString stringWithContentsOfFile:_filePath encoding:NSUTF8StringEncoding error:&error]; 343 | if (error) 344 | { 345 | NSLog(@"load location log file failed!!!"); 346 | return; 347 | } 348 | 349 | NSArray *allLogStrings = [logFileString componentsSeparatedByString:@";\n"]; 350 | for (NSString *aLog in allLogStrings) 351 | { 352 | if (aLog.length > 0) 353 | { 354 | CLLocation *aLocation = [AMapLogFileManagerUtility CLLocationFromLogString:aLog]; 355 | [_locations addObject:aLocation]; 356 | } 357 | } 358 | } 359 | 360 | #pragma mark - Helper 361 | 362 | - (void)locationsHandleBlock:(AMapLocationLogPlaybackBlock)block 363 | { 364 | BOOL shouldStop = NO; 365 | NSUInteger index = 0; 366 | CLLocation *location = nil; 367 | NSDate *addedTime = nil; 368 | 369 | while (!shouldStop && index < _locations.count && ![_locationsThread isCancelled]) 370 | { 371 | NSDate *currentTime = [_locations[index] timestamp]; 372 | NSTimeInterval timeInterval = [currentTime timeIntervalSinceDate:addedTime]; 373 | 374 | location = _locations[index]; 375 | addedTime = currentTime; 376 | 377 | [NSThread sleepForTimeInterval:timeInterval]; 378 | 379 | CLLocation *newLocation = [[CLLocation alloc] initWithCoordinate:location.coordinate 380 | altitude:location.altitude 381 | horizontalAccuracy:location.horizontalAccuracy 382 | verticalAccuracy:location.verticalAccuracy 383 | course:location.course 384 | speed:location.speed 385 | timestamp:[NSDate date]]; 386 | 387 | block(newLocation, index, &shouldStop); 388 | 389 | ++index; 390 | } 391 | } 392 | 393 | #pragma mark - Interface 394 | 395 | + (AMapLocationLogPlayback *)playbackWithFilePath:(NSString *)filePath 396 | { 397 | return [[AMapLocationLogPlayback alloc] initWithFilePath:filePath]; 398 | } 399 | 400 | - (void)startPlaybackUsingLocationBlock:(AMapLocationLogPlaybackBlock)locationBlock 401 | { 402 | if (!locationBlock || _isPlaying) 403 | { 404 | return; 405 | } 406 | 407 | if (_locationsThread) 408 | { 409 | [_locationsThread cancel]; 410 | _locationsThread = nil; 411 | } 412 | 413 | _isPlaying = YES; 414 | 415 | if (locationBlock) 416 | { 417 | _locationsThread = [[NSThread alloc] initWithTarget:self selector:@selector(locationsHandleBlock:) object:locationBlock]; 418 | [_locationsThread setName:@"AMapLocationLogPlaybackThread"]; 419 | [_locationsThread start]; 420 | } 421 | } 422 | 423 | - (void)stopPlayback 424 | { 425 | if (_locationsThread) 426 | { 427 | [_locationsThread cancel]; 428 | } 429 | 430 | _isPlaying = NO; 431 | } 432 | 433 | @end 434 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/ATGPSEmulator.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATGPSEmulator.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSUInteger, ATGPSEmulatorType) { 13 | ATGPSEmulatorCoordinate = 1, 14 | ATGPSEmulatorLofFile = 2, 15 | }; 16 | 17 | @class ATGPSEmulator; 18 | 19 | @protocol ATGPSEmulatorDelegate 20 | 21 | - (void)gpsEmulator:(ATGPSEmulator *)emulator updateLocation:(CLLocation *)location; 22 | 23 | @end 24 | 25 | @interface ATGPSEmulator : NSObject 26 | 27 | @property (nonatomic, readonly) ATGPSEmulatorType type; 28 | 29 | @property (nonatomic, readonly) BOOL isSimulating; 30 | 31 | - (void)setCoordinates:(CLLocationCoordinate2D *)coordinates count:(UInt64)count; 32 | 33 | - (void)setLogFilePath:(NSString *)filePath; 34 | 35 | - (void)startEmulatorWithType:(ATGPSEmulatorType)type; 36 | - (void)stopEmulator; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/ATGPSEmulator.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATGPSEmulator.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATGPSEmulator.h" 10 | 11 | @interface ATGPSEmulator () 12 | { 13 | CLLocationCoordinate2D *coordinates; 14 | UInt64 count; 15 | } 16 | 17 | @property (nonatomic, assign) ATGPSEmulatorType type; 18 | @property (nonatomic, assign) BOOL isSimulating; 19 | 20 | @property (nonatomic, copy) NSString *filePath; 21 | 22 | @end 23 | 24 | @implementation ATGPSEmulator 25 | 26 | #pragma mark - Life Cycle 27 | 28 | - (instancetype)init 29 | { 30 | if (self = [super init]) 31 | { 32 | [self buildGpsEmulator]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)dealloc 38 | { 39 | [self stopEmulator]; 40 | } 41 | 42 | - (void)buildGpsEmulator 43 | { 44 | [self initProperties]; 45 | } 46 | 47 | - (void)initProperties 48 | { 49 | _type = 0; 50 | _isSimulating = NO; 51 | } 52 | 53 | #pragma mark - Interface 54 | 55 | - (void)setCoordinates:(CLLocationCoordinate2D *)coordinates count:(UInt64)count 56 | { 57 | 58 | } 59 | 60 | - (void)setLogFilePath:(NSString *)filePath 61 | { 62 | 63 | } 64 | 65 | - (void)startEmulatorWithType:(ATGPSEmulatorType)type 66 | { 67 | 68 | } 69 | 70 | - (void)stopEmulator 71 | { 72 | 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/ATGPSEmulatorView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATGPSEmulatorView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "ATCustomViewProtocol.h" 12 | 13 | @class ATGPSEmulatorView; 14 | 15 | @protocol ATGPSEmulatorViewDelegate 16 | 17 | 18 | 19 | @end 20 | 21 | @interface ATGPSEmulatorView : UIView 22 | 23 | @property (nonatomic, weak) id delegate; 24 | 25 | - (void)setNaviManager:(AMapNaviBaseManager *)naviManager; 26 | 27 | - (void)loadRouteData; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/ATGPSEmulatorView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATGPSEmulatorView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATGPSEmulatorView.h" 10 | 11 | #pragma mark - ATGPSEmulatorView 12 | 13 | @interface ATGPSEmulatorView () 14 | 15 | @property (nonatomic, weak) AMapNaviBaseManager *naviManager; 16 | 17 | @property (nonatomic, strong) NSThread *simlulatorThread; 18 | 19 | @end 20 | 21 | @implementation ATGPSEmulatorView 22 | 23 | #pragma mark - Life Cycle 24 | 25 | - (instancetype)initWithFrame:(CGRect)frame 26 | { 27 | if (self = [super initWithFrame:frame]) 28 | { 29 | [self buildGPSSimulatorLocationView]; 30 | } 31 | return self; 32 | } 33 | 34 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 35 | { 36 | if (self = [super initWithCoder:aDecoder]) 37 | { 38 | [self buildGPSSimulatorLocationView]; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)buildGPSSimulatorLocationView 44 | { 45 | self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 46 | 47 | [self initProperties]; 48 | 49 | [self initSubviews]; 50 | } 51 | 52 | - (void)initProperties 53 | { 54 | 55 | } 56 | 57 | - (void)initSubviews 58 | { 59 | 60 | } 61 | 62 | #pragma mark - ATCustomViewProtocol 63 | 64 | - (void)customViewWillAppear 65 | { 66 | NSLog(@"customViewWillAppear"); 67 | } 68 | 69 | - (void)customViewDidAppear 70 | { 71 | NSLog(@"customViewDidAppear"); 72 | } 73 | 74 | - (void)customViewWillDisappear 75 | { 76 | NSLog(@"customViewWillDisappear"); 77 | } 78 | 79 | - (void)customViewDidDisappear 80 | { 81 | NSLog(@"customViewDidDisappear"); 82 | } 83 | 84 | - (void)customViewWillShrink 85 | { 86 | NSLog(@"customViewWillShrink"); 87 | } 88 | 89 | - (void)customViewDidShrink 90 | { 91 | NSLog(@"customViewDidShrink"); 92 | } 93 | 94 | - (void)customViewWillExpand 95 | { 96 | NSLog(@"customViewWillExpand"); 97 | } 98 | 99 | - (void)customViewDidExpand 100 | { 101 | NSLog(@"customViewDidExpand"); 102 | } 103 | 104 | #pragma mark - Button Action 105 | 106 | #pragma mark - Interface 107 | 108 | - (void)setNaviManager:(AMapNaviBaseManager *)naviManager 109 | { 110 | if (naviManager) 111 | { 112 | self.naviManager = naviManager; 113 | } 114 | else 115 | { 116 | [self stopSimlulator]; 117 | } 118 | } 119 | 120 | - (void)loadRouteData 121 | { 122 | 123 | } 124 | 125 | - (void)startSimlulator 126 | { 127 | 128 | } 129 | 130 | - (void)stopSimlulator 131 | { 132 | 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/GPSSimulatorEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // GPSSimulatorEngine.h 3 | // Pods 4 | // 5 | // Created by wang.fu on 2017/2/24. 6 | // 7 | // 8 | 9 | #ifdef NMONLINEGPSSIMULATOR 10 | #import 11 | 12 | // 获取模拟GPS的时间间隔。用于计算location的速度 13 | #define GPSSIMULATOR_OFFLINE_TIMEINTERVAL 0.25f 14 | 15 | FOUNDATION_EXTERN void GCJtoWGS(double chnlat, double chnlon, double *wgslat, double *wgslon); 16 | FOUNDATION_EXTERN void G20toWGS(int chnx, int chny, double *wgslat, double *wgslon); 17 | 18 | @class CLLocation; 19 | @interface GPSSimulatorNode : NSObject 20 | 21 | @property (nonatomic, assign) ANPoint g20Location; 22 | 23 | @property (nonatomic, weak) GPSSimulatorNode *preNode; 24 | 25 | @property (nonatomic, weak) GPSSimulatorNode *nextNode; 26 | 27 | - (instancetype)initWithPoint:(ANPoint)point nextNode:(GPSSimulatorNode *)nextNode preNode:(GPSSimulatorNode *)preNode; 28 | 29 | - (CGFloat)distanceToNextNode; 30 | 31 | - (CLLocation *)curLocation; 32 | 33 | - (GPSSimulatorNode *)nextNodeWithDistance:(CGFloat)distance; 34 | 35 | @end 36 | 37 | 38 | @interface GPSSimulatorEngine : NSObject 39 | 40 | + (instancetype)engine; 41 | 42 | - (BOOL)fillG20Locations:(NSArray *)g20Locations; 43 | 44 | - (GPSSimulatorNode *)nextNodeWithSpeed:(CGFloat)speed; 45 | 46 | - (GPSSimulatorNode *)fetchNextNodeWithSpeed:(CGFloat)speed; 47 | 48 | - (void)reset; 49 | 50 | - (BOOL)checkData; 51 | 52 | @end 53 | #endif 54 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/GPSSimulatorEngine.m: -------------------------------------------------------------------------------- 1 | // 2 | // GPSSimulatorEngine.m 3 | // Pods 4 | // 5 | // Created by wang.fu on 2017/2/24. 6 | // 7 | // 8 | 9 | #import "GPSSimulatorEngine.h" 10 | #ifdef NMONLINEGPSSIMULATOR 11 | #import 12 | #import "OnlineGPSSimulator.h" 13 | 14 | #define GPSSIMULATOR_OFFLINE_DISTANCE_INVALID -1.f 15 | #define GPSSIMULATOR_OFFLINE_DISTANCE_NAN -10.f 16 | void GCJtoWGS(double chnlat, double chnlon, double *wgslat, double *wgslon) 17 | { 18 | int nLat; 19 | int nLon; 20 | double lon; 21 | double lat; 22 | ANLonlatTo20Pixel(chnlat, chnlon, &nLat, &nLon); 23 | AN20PixelToLonlat(nLat, nLon, &lon, &lat); 24 | *wgslat = chnlat - (lat - chnlat); 25 | *wgslon = chnlon - (lon - chnlon); 26 | } 27 | 28 | void G20toWGS(int chnx, int chny, double *wgslat, double *wgslon) 29 | { 30 | double lon; 31 | double lat; 32 | AN20PixelToLonlat(chnx,chny,&lon,&lat); 33 | GCJtoWGS(lat,lon,wgslat,wgslon); 34 | } 35 | 36 | @interface GPSSimulatorNode() 37 | 38 | @property (nonatomic, strong) CLLocation *curLocation; 39 | 40 | @property (nonatomic, assign) CGFloat distanceToNextNode; 41 | 42 | @end 43 | 44 | @implementation GPSSimulatorNode 45 | 46 | - (instancetype)initWithPoint:(ANPoint)point nextNode:(GPSSimulatorNode *)nextNode preNode:(GPSSimulatorNode *)preNode 47 | { 48 | if (self = [super init]) { 49 | _g20Location = point; 50 | _nextNode = nextNode; 51 | _preNode = preNode; 52 | _preNode.nextNode = self; 53 | _nextNode.preNode = self; 54 | _distanceToNextNode = GPSSIMULATOR_OFFLINE_DISTANCE_NAN; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)dealloc 60 | { 61 | _curLocation = nil; 62 | } 63 | 64 | - (void)reset 65 | { 66 | _curLocation = nil; 67 | _distanceToNextNode = GPSSIMULATOR_OFFLINE_DISTANCE_NAN; 68 | } 69 | 70 | - (void)setNextNode:(GPSSimulatorNode *)nextNode 71 | { 72 | if (_nextNode != nextNode) { 73 | _nextNode = nextNode; 74 | [self reset]; 75 | } 76 | } 77 | 78 | - (void)setPreNode:(GPSSimulatorNode *)preNode 79 | { 80 | if (_preNode != preNode) { 81 | _preNode = preNode; 82 | [self reset]; 83 | } 84 | } 85 | 86 | - (CGFloat)distanceToNextNode 87 | { 88 | if (_distanceToNextNode != GPSSIMULATOR_OFFLINE_DISTANCE_NAN) { 89 | return _distanceToNextNode; 90 | } 91 | 92 | if (!self.nextNode) { 93 | _distanceToNextNode = GPSSIMULATOR_OFFLINE_DISTANCE_INVALID; 94 | } else { 95 | _distanceToNextNode = [ANCoordConvert distanceFromPoint:self.g20Location toPoint:self.nextNode.g20Location]; 96 | } 97 | 98 | return _distanceToNextNode; 99 | } 100 | 101 | 102 | - (CLLocation *)curLocation 103 | { 104 | if (!_curLocation) { 105 | double lat; 106 | double lon; 107 | G20toWGS(self.g20Location.x, self.g20Location.y, &lat, &lon); 108 | CLLocationDirection heading = 0; 109 | if (self.nextNode) { 110 | heading = [GPSSimulatorNode headingFromANPoint1:self.g20Location toANPoint2:self.nextNode.g20Location]; 111 | } else if (self.preNode) { 112 | heading = [GPSSimulatorNode headingFromANPoint1:self.preNode.g20Location toANPoint2:self.g20Location]; 113 | } 114 | 115 | _curLocation = [[CLLocation alloc]initWithCoordinate:CLLocationCoordinate2DMake(lat, lon) 116 | altitude:10. 117 | horizontalAccuracy:5. 118 | verticalAccuracy:5. 119 | course:heading 120 | speed:[[OnlineGPSSimulator shareManager] speed] 121 | timestamp:[NSDate date]]; 122 | } 123 | 124 | return _curLocation; 125 | } 126 | 127 | - (GPSSimulatorNode *)nextNodeWithDistance:(CGFloat)distance 128 | { 129 | if (!self.nextNode) { 130 | return nil; 131 | } 132 | 133 | CGFloat totalDistance = distance; 134 | 135 | GPSSimulatorNode *curNode = self; 136 | 137 | while (totalDistance > 0) { 138 | if (!curNode) { 139 | return nil; 140 | } 141 | 142 | CGFloat distanceToNextNode = [curNode distanceToNextNode]; 143 | if (distanceToNextNode == GPSSIMULATOR_OFFLINE_DISTANCE_INVALID) { 144 | return nil; 145 | } 146 | 147 | if (totalDistance >= distanceToNextNode) { 148 | totalDistance -= distanceToNextNode; 149 | curNode = curNode.nextNode; 150 | } else { 151 | curNode = [GPSSimulatorNode createNodeFromNode:curNode toNode:curNode.nextNode distance:totalDistance]; 152 | totalDistance = 0; 153 | } 154 | } 155 | 156 | return curNode; 157 | } 158 | 159 | #pragma mark - private 160 | // create a node between 'fromNode' and 'toNode' which has 'distance' distance from 'fromNode' 161 | + (GPSSimulatorNode *)createNodeFromNode:(GPSSimulatorNode *)fromNode toNode:(GPSSimulatorNode *)toNode distance:(CGFloat)distance 162 | { 163 | CGFloat totalDistance = [ANCoordConvert distanceFromPoint:fromNode.g20Location toPoint:toNode.g20Location]; 164 | CGFloat percent = distance / totalDistance; 165 | int x = percent * (toNode.g20Location.x - fromNode.g20Location.x) + fromNode.g20Location.x; 166 | int y = percent * (toNode.g20Location.y - fromNode.g20Location.y) + fromNode.g20Location.y; 167 | return [[GPSSimulatorNode alloc] initWithPoint:ANPointMake(x, y) nextNode:toNode preNode:fromNode]; 168 | } 169 | 170 | + (CLLocationDirection)headingFromANPoint1:(ANPoint)point1 toANPoint2:(ANPoint)point2 { 171 | double dx = point2.x - point1.x; 172 | double dy = -(point2.y - point1.y); 173 | double angle = 0; 174 | if (fabs(dx) < 1 && fabs(dy) < 1) { 175 | angle = -1; 176 | } else { 177 | //坐标逆时针旋转90度 使atan2的初始值为y轴 178 | double f = atan2(dx,dy) * 180/M_PI; 179 | if (dx < 0) { 180 | angle = 360 + f; 181 | } else { 182 | angle = f; 183 | } 184 | } 185 | return angle; 186 | } 187 | 188 | @end 189 | 190 | 191 | 192 | @interface GPSSimulatorEngine() 193 | 194 | @property (nonatomic, weak) GPSSimulatorNode *firstNode; 195 | 196 | @property (nonatomic, weak) GPSSimulatorNode *curNode; 197 | 198 | @property (nonatomic, strong) NSMutableSet *nodesSet; 199 | 200 | @property (nonatomic, strong) NSArray *g20Locations; 201 | 202 | @end 203 | 204 | @implementation GPSSimulatorEngine 205 | 206 | + (instancetype)engine 207 | { 208 | static GPSSimulatorEngine *engine = nil; 209 | static dispatch_once_t onceToken; 210 | dispatch_once(&onceToken, ^{ 211 | engine = [[GPSSimulatorEngine alloc] init]; 212 | }); 213 | return engine; 214 | } 215 | 216 | - (instancetype)init 217 | { 218 | if (self = [super init]) { 219 | _nodesSet = [NSMutableSet set]; 220 | } 221 | return self; 222 | } 223 | 224 | - (BOOL)fillG20Locations:(NSArray *)g20Locations 225 | { 226 | self.g20Locations = g20Locations; 227 | [self.nodesSet removeAllObjects]; 228 | self.firstNode = nil; 229 | 230 | GPSSimulatorNode *lastNode = nil; 231 | for (NSValue *value in g20Locations) { 232 | if (!lastNode) { 233 | lastNode = [[GPSSimulatorNode alloc] initWithPoint:[value ANPointValue] nextNode:nil preNode:nil]; 234 | self.firstNode = lastNode; 235 | } else { 236 | lastNode = [[GPSSimulatorNode alloc] initWithPoint:[value ANPointValue] nextNode:nil preNode:lastNode]; 237 | } 238 | [self.nodesSet addObject:lastNode]; 239 | } 240 | 241 | return [self checkData]; 242 | } 243 | 244 | - (GPSSimulatorNode *)nextNodeWithSpeed:(CGFloat)speed 245 | { 246 | return [self.curNode nextNodeWithDistance:speed * GPSSIMULATOR_OFFLINE_TIMEINTERVAL]; 247 | } 248 | 249 | - (GPSSimulatorNode *)fetchNextNodeWithSpeed:(CGFloat)speed 250 | { 251 | if (!self.curNode) { 252 | self.curNode = self.firstNode; 253 | } else { 254 | GPSSimulatorNode *node = [self nextNodeWithSpeed:speed]; 255 | // 有可能是createNew出来的,需要add到set中,由set管理其生命周期,否则会自己释放。 256 | if (node) { 257 | self.curNode = node; 258 | [self.nodesSet addObject:node]; 259 | 260 | if (self.curNode.preNode) {// 清除走过的点 261 | if (!self.curNode.nextNode) {// 若为最后一个点,先根据prenode计算location后,再删除 262 | [self.curNode curLocation]; 263 | } 264 | [self.nodesSet removeObject:self.curNode.preNode]; 265 | } 266 | } else { 267 | return nil; 268 | } 269 | } 270 | 271 | return self.curNode; 272 | } 273 | 274 | - (void)reset 275 | { 276 | // 重新生成node,否则每次都要创建中间node,会越来越多。 277 | [self fillG20Locations:self.g20Locations]; 278 | } 279 | 280 | - (BOOL)checkData 281 | { 282 | if (!self.firstNode) { 283 | return NO; 284 | } 285 | 286 | return YES; 287 | } 288 | @end 289 | #endif 290 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATGPSEmulatorView/OnlineGPSSimulator.h: -------------------------------------------------------------------------------- 1 | // 2 | // OnlineGPSSimulator.h 3 | // AMapiPhone 4 | // 5 | // Created by wang.fu on 14-7-29. 6 | // 7 | // 和在线工具结合模拟GPS数据 8 | // 打开ONLINEGPSSIMULATOR宏 9 | // 调用-fire 即可实现模拟GPS替换真实GPS。 10 | // 调用-fireGetGPS:或者 设置代理实现代理方法 都可以获得模拟GPS。 11 | // 控制端网址:http://10.19.1.124/WalkDemo/html/busnavi.html 12 | // 调用-pause暂停。 13 | // 调用+releaseManager释放。 14 | 15 | 16 | #ifdef NMONLINEGPSSIMULATOR 17 | #import 18 | //#import "AMLocationService.h" 19 | #import "GPSSimulatorEngine.h" 20 | 21 | #define NM_NOTIFICATION_GPSSIMULATORMODE_DID_CHANGE @"NM_NOTIFICATION_GPSSIMULATORMODE_DID_CHANGE" 22 | 23 | 24 | 25 | @class CLLocation, NMGPSSimulatorIcon; 26 | 27 | typedef void (^OnlineGPSBlock)(CLLocation *newLocation); 28 | typedef void (^SGDowloadFinished)(NSData* fileData); 29 | typedef void (^SGDownloadFailBlock)(NSError*error); 30 | typedef NS_ENUM(NSInteger, GPSSimulatorMode) { 31 | GPSSimulatorModeNone = 1001, 32 | GPSSimulatorModeOnline, 33 | GPSSimulatorModeOffline, 34 | }; 35 | 36 | @protocol OnlineGPSSimulatorDelegate 37 | @optional 38 | - (void)onlineGPSUpdateToLocation:(CLLocation *)newLocation; 39 | 40 | @end 41 | @interface OnlineGPSSimulator : NSObject 42 | { 43 | NSString *_UID; 44 | double _timeInterval; 45 | } 46 | @property (nonatomic, strong) NMGPSSimulatorIcon *simulatorView; 47 | @property (nonatomic, copy) NSString *UID; 48 | @property (nonatomic, assign) double timeInterval; 49 | @property (nonatomic, assign) GPSSimulatorMode currentMode; 50 | @property (nonatomic, assign) BOOL isPause; 51 | 52 | 53 | + (OnlineGPSSimulator *)shareManager; 54 | + (void)releaseManager; 55 | 56 | - (void)addDelegate:(id)delegate; 57 | - (void)removeDelegate:(id)delegate; 58 | - (void)removeAllDelegate; 59 | - (void)setOnlineGPSBlock:(OnlineGPSBlock)onlineGPSBlock; 60 | 61 | - (void)fireWithMode:(GPSSimulatorMode)mode completionHandle:(void (^)(BOOL success, NSString *errorInfo))completionHandle; 62 | 63 | #pragma mark - online 64 | - (void)setUID:(NSString *)UID timeInterval:(double )interval; 65 | 66 | #pragma mark - offline 67 | // 加载路线数据 68 | - (void)loadPath; 69 | // 回到路线起点 70 | - (void)reset; 71 | 72 | @property (nonatomic, assign) CGFloat speed; 73 | 74 | @end 75 | 76 | @interface AMLocationService (AMLocationServiceExtention) 77 | - (void)startSim; 78 | @end 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATSandboxViewerView/ATSandboxViewerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATSandboxViewerView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/16. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ATAssistiveTools.h" 11 | 12 | FOUNDATION_EXTERN NSString * const ATSandboxViewerViewSelecteFileNotification; 13 | FOUNDATION_EXTERN NSString * const ATSandboxViewerViewFilePathKey; 14 | 15 | @interface ATSandboxViewerView : UIView 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATCustomizeViews/ATSandboxViewerView/ATSandboxViewerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATSandboxViewerView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/16. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATSandboxViewerView.h" 10 | #import 11 | 12 | NSString * const ATSandboxViewerViewSelecteFileNotification = @"ATSandboxViewerViewSelecteFileNotification"; 13 | NSString * const ATSandboxViewerViewFilePathKey = @"ATSandboxViewerViewFilePath"; 14 | 15 | #pragma mark - ATFileItem 16 | 17 | typedef NS_ENUM(NSUInteger, ATFileItemType) { 18 | ATFileItemBack, 19 | ATFileItemFile, 20 | ATFileItemDirectory, 21 | }; 22 | 23 | @interface ATFileItem : NSObject 24 | 25 | @property (nonatomic, copy) NSString *itemName; 26 | @property (nonatomic, copy) NSString *itemPath; 27 | @property (nonatomic, assign) ATFileItemType itemType; 28 | 29 | @end 30 | 31 | @implementation ATFileItem 32 | @end 33 | 34 | #pragma mark - ATSandboxViewerView 35 | 36 | @interface ATSandboxViewerView () 37 | 38 | @property (nonatomic, strong) UIButton *webServerButton; 39 | @property (nonatomic, strong) UITableView *mainTableView; 40 | 41 | @property (nonatomic, strong) NSString *currentPath; 42 | @property (nonatomic, strong) NSArray *allItems; 43 | 44 | @property (nonatomic, strong) GCDWebUploader *webUploader; 45 | 46 | @end 47 | 48 | @implementation ATSandboxViewerView 49 | 50 | #pragma mark - Life Cycle 51 | 52 | - (instancetype)initWithFrame:(CGRect)frame 53 | { 54 | if (self = [super initWithFrame:frame]) 55 | { 56 | [self buildSandboxViewerView]; 57 | } 58 | return self; 59 | } 60 | 61 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 62 | { 63 | if (self = [super initWithCoder:aDecoder]) 64 | { 65 | [self buildSandboxViewerView]; 66 | } 67 | return self; 68 | } 69 | 70 | - (void)buildSandboxViewerView 71 | { 72 | self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 73 | 74 | [self initProperties]; 75 | 76 | [self initSubviews]; 77 | } 78 | 79 | - (void)initProperties 80 | { 81 | _currentPath = NSHomeDirectory(); 82 | _allItems = @[]; 83 | } 84 | 85 | - (void)initSubviews 86 | { 87 | self.webServerButton = [UIButton buttonWithType:UIButtonTypeCustom]; 88 | self.webServerButton.translatesAutoresizingMaskIntoConstraints = NO; 89 | self.webServerButton.layer.borderColor = [UIColor blueColor].CGColor; 90 | self.webServerButton.layer.borderWidth = 0.5; 91 | self.webServerButton.titleLabel.font = [UIFont systemFontOfSize:15.0]; 92 | [self.webServerButton setTitle:@"Start" forState:UIControlStateNormal]; 93 | [self.webServerButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 94 | [self.webServerButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted]; 95 | [self.webServerButton addTarget:self action:@selector(webServerButtonAction:) forControlEvents:UIControlEventTouchUpInside]; 96 | 97 | [self addSubview:self.webServerButton]; 98 | 99 | self.mainTableView = [[UITableView alloc] init]; 100 | self.mainTableView.translatesAutoresizingMaskIntoConstraints = NO; 101 | self.mainTableView.delegate = self; 102 | self.mainTableView.dataSource = self; 103 | self.mainTableView.backgroundColor = [UIColor whiteColor]; 104 | self.mainTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; 105 | 106 | [self addSubview:self.mainTableView]; 107 | 108 | NSDictionary *views = NSDictionaryOfVariableBindings(_webServerButton, _mainTableView); 109 | 110 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[_webServerButton(==width)]" options:0 metrics:@{@"width":@(80)} views:views]]; 111 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[_webServerButton(==height)]" options:0 metrics:@{@"height":@(20)} views:views]]; 112 | 113 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[_mainTableView]-5-|" options:0 metrics:nil views:views]]; 114 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_webServerButton]-10-[_mainTableView]-10-|" options:0 metrics:nil views:views]]; 115 | } 116 | 117 | #pragma mark - ATCustomViewProtocol 118 | 119 | - (void)customViewDidAppear 120 | { 121 | [self loadAllItemInPath:self.currentPath]; 122 | } 123 | 124 | #pragma mark - Methods 125 | 126 | - (void)loadAllItemInPath:(NSString *)path 127 | { 128 | NSFileManager *fileManager = [NSFileManager defaultManager]; 129 | NSMutableArray *resultItems = [[NSMutableArray alloc] init]; 130 | 131 | if (path == nil || path.length <= 0 || [path isEqualToString:NSHomeDirectory()]) 132 | { 133 | path = NSHomeDirectory(); 134 | } 135 | else 136 | { 137 | // add parents item 138 | ATFileItem *parentsItem = [[ATFileItem alloc] init]; 139 | parentsItem.itemName = @"< ../"; 140 | parentsItem.itemPath = [path stringByDeletingLastPathComponent]; 141 | parentsItem.itemType = ATFileItemBack; 142 | 143 | [resultItems addObject:parentsItem]; 144 | } 145 | 146 | NSError *error = nil; 147 | NSArray *allFileNames = [fileManager contentsOfDirectoryAtPath:path error:&error]; 148 | if (error != nil) 149 | { 150 | NSLog(@"Load Path Failed:{%@ - %@}", path, error.description); 151 | return; 152 | } 153 | 154 | for (NSString *aFileName in allFileNames) 155 | { 156 | // ignore hidden files (files that begin with a period character) 157 | if ([[aFileName lastPathComponent] hasPrefix:@"."]) 158 | { 159 | continue; 160 | } 161 | 162 | BOOL isDirectory = false; 163 | NSString* fullPath = [path stringByAppendingPathComponent:aFileName]; 164 | [fileManager fileExistsAtPath:fullPath isDirectory:&isDirectory]; 165 | 166 | ATFileItem *aItem = [[ATFileItem alloc] init]; 167 | aItem.itemPath = fullPath; 168 | if (isDirectory) 169 | { 170 | // @"U0001F4C1" -> 📁 171 | aItem.itemName = [NSString stringWithFormat:@"\U0001F4C1 %@", aFileName]; 172 | aItem.itemType = ATFileItemDirectory; 173 | } 174 | else 175 | { 176 | // @"\U0001F4C4" -> 📄 177 | aItem.itemName = [NSString stringWithFormat:@"\U0001F4C4 %@", aFileName]; 178 | aItem.itemType = ATFileItemFile; 179 | } 180 | 181 | [resultItems addObject:aItem]; 182 | } 183 | 184 | self.allItems = resultItems; 185 | self.currentPath = path; 186 | 187 | [self.mainTableView reloadData]; 188 | } 189 | 190 | #pragma mark - Button Action 191 | 192 | - (void)webServerButtonAction:(UIButton *)sender 193 | { 194 | if (self.webUploader == nil) 195 | { 196 | NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 197 | self.webUploader = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath]; 198 | self.webUploader.delegate = self; 199 | [self.webUploader start]; 200 | 201 | NSLog(@"Visit %@ in your web browser", self.webUploader.serverURL); 202 | 203 | [self.webServerButton setTitle:@"Stop" forState:UIControlStateNormal]; 204 | } 205 | else 206 | { 207 | [self.webUploader stop]; 208 | self.webUploader.delegate = nil; 209 | self.webUploader = nil; 210 | 211 | [self.webServerButton setTitle:@"Start" forState:UIControlStateNormal]; 212 | } 213 | } 214 | 215 | - (void)refreshItemsIfNeedForPath:(NSString *)path 216 | { 217 | 218 | } 219 | 220 | #pragma mark - GCDWebUploaderDelegate 221 | 222 | - (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path 223 | { 224 | [self refreshItemsIfNeedForPath:path]; 225 | } 226 | 227 | - (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath 228 | { 229 | [self refreshItemsIfNeedForPath:fromPath]; 230 | [self refreshItemsIfNeedForPath:toPath]; 231 | } 232 | 233 | - (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path 234 | { 235 | [self refreshItemsIfNeedForPath:path]; 236 | } 237 | 238 | - (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path 239 | { 240 | [self refreshItemsIfNeedForPath:[path stringByDeletingLastPathComponent]]; 241 | } 242 | 243 | #pragma mark - UITableViewDelegate 244 | 245 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 246 | { 247 | return self.allItems.count; 248 | } 249 | 250 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 251 | { 252 | if (indexPath.row >= self.allItems.count) 253 | { 254 | return nil; 255 | } 256 | 257 | static NSString *cellIdentifier = @"ATSandboxViewerCellIdentifier"; 258 | UITableViewCell *aCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 259 | if (aCell == nil) 260 | { 261 | aCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 262 | aCell.selectionStyle = UITableViewCellSelectionStyleNone; 263 | } 264 | 265 | ATFileItem *aItem = [self.allItems objectAtIndex:indexPath.row]; 266 | 267 | aCell.textLabel.text = aItem.itemName; 268 | if (aItem.itemType == ATFileItemBack) 269 | { 270 | aCell.accessoryType = UITableViewCellAccessoryNone; 271 | } 272 | else if (aItem.itemType == ATFileItemFile) 273 | { 274 | aCell.accessoryType = UITableViewCellAccessoryDetailButton; 275 | } 276 | else if (aItem.itemType == ATFileItemDirectory) 277 | { 278 | aCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 279 | } 280 | else 281 | { 282 | aCell.accessoryType = UITableViewCellAccessoryNone; 283 | } 284 | 285 | return aCell; 286 | } 287 | 288 | #pragma mark - UITableViewDataSource 289 | 290 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 291 | { 292 | [tableView deselectRowAtIndexPath:indexPath animated:NO]; 293 | 294 | if (indexPath.row >= self.allItems.count) 295 | { 296 | return; 297 | } 298 | 299 | ATFileItem *aItem = [self.allItems objectAtIndex:indexPath.row]; 300 | if (aItem.itemType == ATFileItemBack) 301 | { 302 | [self loadAllItemInPath:[self.currentPath stringByDeletingLastPathComponent]]; 303 | } 304 | else if (aItem.itemType == ATFileItemFile) 305 | { 306 | [self handleFileAtPath:aItem.itemPath]; 307 | } 308 | else if (aItem.itemType == ATFileItemDirectory) 309 | { 310 | [self loadAllItemInPath:aItem.itemPath]; 311 | } 312 | else 313 | { 314 | NSLog(@"Item Type Error!"); 315 | } 316 | } 317 | 318 | #pragma mark - Handle File 319 | 320 | - (void)handleFileAtPath:(NSString*)path 321 | { 322 | // send notification 323 | [[NSNotificationCenter defaultCenter] postNotificationName:ATSandboxViewerViewSelecteFileNotification object:nil userInfo:@{ATSandboxViewerViewFilePathKey:path}]; 324 | 325 | // present shared menu 326 | NSArray *objectsToShare = @[[NSURL fileURLWithPath:path]]; 327 | 328 | UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil]; 329 | NSArray *excludedActivities = @[UIActivityTypePostToTwitter, UIActivityTypePostToFacebook, 330 | UIActivityTypePostToWeibo, 331 | UIActivityTypeMessage, UIActivityTypeMail, 332 | UIActivityTypePrint, UIActivityTypeCopyToPasteboard, 333 | UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll, 334 | UIActivityTypeAddToReadingList, UIActivityTypePostToFlickr, 335 | UIActivityTypePostToVimeo, UIActivityTypePostToTencentWeibo]; 336 | controller.excludedActivityTypes = excludedActivities; 337 | 338 | if ([(NSString *)[UIDevice currentDevice].model hasPrefix:@"iPad"]) 339 | { 340 | controller.popoverPresentationController.sourceView = self; 341 | controller.popoverPresentationController.sourceRect = CGRectMake([UIScreen mainScreen].bounds.size.width * 0.5, [UIScreen mainScreen].bounds.size.height, 10, 10); 342 | } 343 | 344 | [[[[ATAssistiveTools sharedInstance] mainWindow] rootViewController] presentViewController:controller animated:YES completion:nil]; 345 | } 346 | 347 | @end 348 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATExpandInfoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATExpandInfoView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ATCustomViewProtocol.h" 11 | 12 | #define kATExpandViewTopHeight 30.f 13 | #define kATExpandViewDetailHeight 450.f 14 | 15 | #define kATExpandViewWidth 320.f 16 | #define kATExpandViewHeight (kATExpandViewDetailHeight+kATExpandViewTopHeight) 17 | 18 | #define kATExpandViewThemeCloor [UIColor colorWithRed:53/255.0 green:117/255.0 blue:255/255.0 alpha:1] 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | @class ATExpandInfoView; 23 | 24 | @protocol ATExpandInfoViewDelegate 25 | @required 26 | 27 | - (void)expandInfoViewCloseAction:(ATExpandInfoView *)expandView; 28 | 29 | @end 30 | 31 | @interface ATExpandInfoView : UIView 32 | 33 | @property (nonatomic, weak) id delegate; 34 | 35 | @property (nonatomic, readonly) NSArray *currentTitles; 36 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle; 37 | - (void)removeCustiomViewForTitle:(NSString *)aTitle; 38 | - (void)removeAllCustomViews; 39 | 40 | //default [UIFont systemFontOfSize:15] 41 | @property (nonatomic, strong) UIFont *titleFont; 42 | //default [UIColor blackColor] 43 | @property (nonatomic, strong) UIColor *titleNormalColor; 44 | //default kATExpandViewThemeCloor 45 | @property (nonatomic, strong) UIColor *titleSelectColor; 46 | 47 | @end 48 | 49 | @interface ATExpandInfoView (Private) 50 | 51 | - (void)expandInfoViewWillShrink; 52 | - (void)expandInfoViewDidShrink; 53 | - (void)expandInfoViewWillExpand; 54 | - (void)expandInfoViewDidExpand; 55 | 56 | @end 57 | 58 | NS_ASSUME_NONNULL_END 59 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATExpandInfoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATExpandInfoView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATExpandInfoView.h" 10 | #import "ATExpandInfoViewCell.h" 11 | 12 | #define kATExpandViewAlpha 0.95f 13 | #define kATExpandVIewBorderWidth 0.4f 14 | #define kATExpandViewDefaultPadding 10.f 15 | #define kATExpandViewIndicatorHeight 4.f 16 | 17 | #define kATExpandViewDuration 0.25f 18 | 19 | @interface ATExpandInfoView () 20 | 21 | @property (nonatomic, strong) UIView *topContentView; 22 | @property (nonatomic, strong) UIView *detailContentView; 23 | 24 | // top 25 | @property (nonatomic, strong) UIButton *closeButton; 26 | @property (nonatomic, strong) UICollectionView *collectionView; 27 | @property (nonatomic, strong) UIView *indicatorView; 28 | @property (nonatomic, readonly) NSDictionary *titleAttributes; 29 | 30 | // detail 31 | @property (nonatomic, readwrite) NSInteger selectedItemIndex; 32 | @property (nonatomic, strong) NSMutableArray *allTitles; 33 | @property (nonatomic, strong) NSMapTable *> *allContents; 34 | 35 | @end 36 | 37 | @implementation ATExpandInfoView 38 | 39 | #pragma mark - Life Cycle 40 | 41 | - (instancetype)initWithFrame:(CGRect)frame 42 | { 43 | if (self = [super initWithFrame:frame]) 44 | { 45 | [self buildExpandInfoView]; 46 | } 47 | return self; 48 | } 49 | 50 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 51 | { 52 | if (self = [super initWithCoder:aDecoder]) 53 | { 54 | [self buildExpandInfoView]; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)buildExpandInfoView 60 | { 61 | self.layer.borderColor = [UIColor blackColor].CGColor; 62 | self.layer.borderWidth = 0.5f; 63 | self.layer.cornerRadius = 4.f; 64 | self.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:kATExpandViewAlpha]; 65 | self.clipsToBounds = YES; 66 | 67 | [self initProperties]; 68 | 69 | [self createContentView]; 70 | 71 | [self buildTopContentView]; 72 | } 73 | 74 | - (void)initProperties 75 | { 76 | _titleFont = [UIFont systemFontOfSize:15]; 77 | _titleNormalColor = [UIColor blackColor]; 78 | _titleSelectColor = kATExpandViewThemeCloor; 79 | 80 | _selectedItemIndex = -1; 81 | _allTitles = [[NSMutableArray alloc] init]; 82 | _allContents = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsCopyIn valueOptions:NSPointerFunctionsStrongMemory capacity:0]; 83 | } 84 | 85 | - (void)createContentView 86 | { 87 | //top content view 88 | _topContentView = [[UIView alloc] initWithFrame:self.bounds]; 89 | _topContentView.translatesAutoresizingMaskIntoConstraints = NO; 90 | _topContentView.backgroundColor = [UIColor clearColor]; 91 | _topContentView.layer.borderColor = kATExpandViewThemeCloor.CGColor; 92 | _topContentView.layer.borderWidth = kATExpandVIewBorderWidth; 93 | 94 | //detail content view 95 | _detailContentView = [[UIView alloc] initWithFrame:self.bounds]; 96 | _detailContentView.translatesAutoresizingMaskIntoConstraints = NO; 97 | _detailContentView.backgroundColor = [UIColor clearColor]; 98 | 99 | [self addSubview:_topContentView]; 100 | [self addSubview:_detailContentView]; 101 | 102 | NSDictionary *views = NSDictionaryOfVariableBindings(_topContentView,_detailContentView); 103 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_topContentView]|" options:0 metrics:nil views:views]]; 104 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_detailContentView]|" options:0 metrics:nil views:views]]; 105 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_topContentView(==height)][_detailContentView]|" options:0 metrics:@{@"height":@(kATExpandViewTopHeight)} views:views]]; 106 | } 107 | 108 | - (void)buildTopContentView 109 | { 110 | // close button 111 | _closeButton = [[UIButton alloc] init]; 112 | _closeButton.translatesAutoresizingMaskIntoConstraints = NO; 113 | _closeButton.backgroundColor = [UIColor clearColor]; 114 | _closeButton.layer.borderColor = [UIColor blackColor].CGColor; 115 | _closeButton.layer.borderWidth = kATExpandVIewBorderWidth; 116 | 117 | [_closeButton addTarget:self action:@selector(closeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; 118 | [_closeButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 119 | [_closeButton setTitle:@"X" forState:UIControlStateNormal]; 120 | 121 | // collection view 122 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 123 | layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 124 | _collectionView = [[UICollectionView alloc] initWithFrame:self.topContentView.bounds collectionViewLayout:layout]; 125 | _collectionView.translatesAutoresizingMaskIntoConstraints = NO; 126 | _collectionView.backgroundColor = [UIColor clearColor]; 127 | 128 | [_collectionView registerClass:[ATExpandInfoViewCell class] forCellWithReuseIdentifier:NSStringFromClass([ATExpandInfoViewCell class])]; 129 | _collectionView.dataSource = self; 130 | _collectionView.delegate = self; 131 | _collectionView.scrollEnabled = YES; 132 | _collectionView.showsHorizontalScrollIndicator = NO; 133 | _collectionView.contentInset = UIEdgeInsetsZero; 134 | 135 | // indicator view 136 | _indicatorView = [[UIView alloc] init]; 137 | _indicatorView.userInteractionEnabled = NO; 138 | _indicatorView.backgroundColor = kATExpandViewThemeCloor; 139 | [_collectionView addSubview:_indicatorView]; 140 | 141 | [self.topContentView addSubview:_closeButton]; 142 | [self.topContentView addSubview:_collectionView]; 143 | 144 | NSDictionary *views = NSDictionaryOfVariableBindings(_closeButton,_collectionView); 145 | [self.topContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_closeButton]|" options:0 metrics:nil views:views]]; 146 | [self.topContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_collectionView]|" options:0 metrics:nil views:views]]; 147 | [self.topContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_closeButton(==width)][_collectionView]|" options:0 metrics:@{@"width":@(kATExpandViewTopHeight)} views:views]]; 148 | } 149 | 150 | - (NSDictionary *)titleAttributes 151 | { 152 | return @{NSFontAttributeName:[self.titleFont copy], 153 | NSForegroundColorAttributeName:[self.titleNormalColor copy], 154 | NSBackgroundColorAttributeName:[self.titleSelectColor copy]}; 155 | } 156 | 157 | #pragma mark - Private: Close Action 158 | 159 | - (void)closeButtonAction:(UIButton *)button 160 | { 161 | if (self.delegate) 162 | { 163 | [self.delegate expandInfoViewCloseAction:self]; 164 | } 165 | } 166 | 167 | #pragma mark - Model 168 | 169 | - (void)addTitle:(NSString *)title relateView:(UIView *)view 170 | { 171 | @synchronized (self.allTitles) { 172 | if ([self.allContents objectForKey:title]) 173 | { 174 | [self.allTitles removeObject:title]; 175 | [self.allContents removeObjectForKey:title]; 176 | } 177 | 178 | if (view != nil) 179 | { 180 | [self.allTitles addObject:title]; 181 | [self.allContents setObject:view forKey:title]; 182 | } 183 | 184 | [self reloadExpandInfoViewData]; 185 | } 186 | } 187 | 188 | - (void)deleteTitle:(NSString *)title 189 | { 190 | @synchronized (self.allTitles) { 191 | if ([self.allContents objectForKey:title]) 192 | { 193 | [self.allTitles removeObject:title]; 194 | [self.allContents removeObjectForKey:title]; 195 | } 196 | 197 | [self reloadExpandInfoViewData]; 198 | } 199 | } 200 | 201 | - (void)deleteAllTitles 202 | { 203 | @synchronized (self.allTitles) { 204 | [self.allTitles removeAllObjects]; 205 | [self.allContents removeAllObjects]; 206 | 207 | [self reloadExpandInfoViewData]; 208 | } 209 | } 210 | 211 | - (void)reloadExpandInfoViewData 212 | { 213 | [self.collectionView reloadData]; 214 | } 215 | 216 | #pragma mark - Public: Interface 217 | 218 | - (NSArray *)currentTitles 219 | { 220 | @synchronized (self.allTitles) { 221 | return [self.allTitles copy]; 222 | } 223 | } 224 | 225 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle 226 | { 227 | if (aTitle == nil || aTitle.length <= 0) 228 | { 229 | return; 230 | } 231 | 232 | [self addTitle:aTitle relateView:aView]; 233 | } 234 | 235 | - (void)removeCustiomViewForTitle:(NSString *)aTitle 236 | { 237 | if (aTitle == nil || aTitle.length <= 0) 238 | { 239 | return; 240 | } 241 | 242 | [self deleteTitle:aTitle]; 243 | } 244 | 245 | - (void)removeAllCustomViews 246 | { 247 | [self deleteAllTitles]; 248 | } 249 | 250 | #pragma mark - Private: Methods 251 | 252 | - (void)manualSelectedItemWithIndex:(NSInteger)index 253 | { 254 | if (index >= self.allTitles.count || index < 0 || index == self.selectedItemIndex) 255 | { 256 | return; 257 | } 258 | 259 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; 260 | 261 | [self.collectionView selectItemAtIndexPath:indexPath 262 | animated:YES 263 | scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; 264 | 265 | [self collectionView:self.collectionView didSelectItemAtIndexPath:indexPath]; 266 | } 267 | 268 | #pragma mark - UICollectionView Delegate 269 | 270 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 271 | { 272 | if (indexPath.row == self.selectedItemIndex) 273 | { 274 | return; 275 | } 276 | 277 | self.selectedItemIndex = indexPath.row; 278 | 279 | ATExpandInfoViewCell *aCell = (ATExpandInfoViewCell *)[collectionView cellForItemAtIndexPath:indexPath]; 280 | 281 | // indicator view frame && collection view scroll to item 282 | CGRect newFrame = CGRectMake(aCell.frame.origin.x, aCell.frame.size.height - kATExpandViewIndicatorHeight, aCell.frame.size.width, kATExpandViewIndicatorHeight); 283 | [UIView animateWithDuration:kATExpandViewDuration animations:^{ 284 | self.indicatorView.frame = newFrame; 285 | [collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; 286 | }]; 287 | 288 | // update detail content view 289 | UIView *viewForCell = [self.allContents objectForKey:aCell.title]; 290 | 291 | // remove old views 292 | NSArray *allSubviews = self.detailContentView.subviews; 293 | for (UIView *aView in allSubviews) 294 | { 295 | // will disappear 296 | if ([aView respondsToSelector:@selector(customViewWillDisappear)]) 297 | { 298 | [aView customViewWillDisappear]; 299 | } 300 | 301 | [aView removeFromSuperview]; 302 | 303 | // did disappear 304 | if ([aView respondsToSelector:@selector(customViewDidDisappear)]) 305 | { 306 | [aView customViewDidDisappear]; 307 | } 308 | } 309 | 310 | // add new view 311 | if (viewForCell != nil) 312 | { 313 | viewForCell.frame = self.detailContentView.bounds; 314 | viewForCell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 315 | 316 | // will appear 317 | if ([viewForCell respondsToSelector:@selector(customViewWillAppear)]) 318 | { 319 | [viewForCell customViewWillAppear]; 320 | } 321 | 322 | [self.detailContentView addSubview:viewForCell]; 323 | 324 | // did appear 325 | if ([viewForCell respondsToSelector:@selector(customViewDidAppear)]) 326 | { 327 | [viewForCell customViewDidAppear]; 328 | } 329 | } 330 | } 331 | 332 | - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath 333 | { 334 | // do nothing 335 | } 336 | 337 | #pragma mark - UICollectionView DataSource 338 | 339 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 340 | { 341 | return [self.allTitles count]; 342 | } 343 | 344 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 345 | { 346 | ATExpandInfoViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([ATExpandInfoViewCell class]) forIndexPath:indexPath]; 347 | 348 | //set cell properties 349 | cell.title = self.allTitles[indexPath.row]; 350 | cell.titleFont = self.titleFont; 351 | cell.titleNormalColor = self.titleNormalColor; 352 | cell.titleSelectColor = self.titleSelectColor; 353 | 354 | return cell; 355 | } 356 | 357 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 358 | { 359 | // size of cell 360 | CGSize size = [[self.allTitles objectAtIndex:indexPath.row] sizeWithAttributes:self.titleAttributes]; 361 | CGSize resultSize = CGSizeMake(size.width + kATExpandViewDefaultPadding, collectionView.bounds.size.height); 362 | 363 | // if it's the first time to show indicator view, calculate view's frame from the first cell's size 364 | if (CGRectIsEmpty(self.indicatorView.frame) && indexPath.row == 0) 365 | { 366 | CGRect newFrame = CGRectMake(0, collectionView.bounds.size.height - kATExpandViewIndicatorHeight, 0, kATExpandViewIndicatorHeight); 367 | self.indicatorView.frame = newFrame; 368 | } 369 | 370 | return resultSize; 371 | } 372 | 373 | //- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section 374 | //{ 375 | // return UIEdgeInsetsZero; 376 | //} 377 | // 378 | //- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section 379 | //{ 380 | // return kATExpandViewDefaultPadding; 381 | //} 382 | // 383 | //- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section 384 | //{ 385 | // return kATExpandViewDefaultPadding; 386 | //} 387 | 388 | #pragma mark - Shrink && Expand 389 | 390 | - (void)expandInfoViewWillShrink 391 | { 392 | NSArray *allSubviews = self.detailContentView.subviews; 393 | for (UIView *aView in allSubviews) 394 | { 395 | if ([aView respondsToSelector:@selector(customViewWillShrink)]) 396 | { 397 | [aView customViewWillShrink]; 398 | } 399 | } 400 | } 401 | 402 | - (void)expandInfoViewDidShrink 403 | { 404 | NSArray *allSubviews = self.detailContentView.subviews; 405 | for (UIView *aView in allSubviews) 406 | { 407 | if ([aView respondsToSelector:@selector(customViewDidShrink)]) 408 | { 409 | [aView customViewDidShrink]; 410 | } 411 | } 412 | } 413 | 414 | - (void)expandInfoViewWillExpand 415 | { 416 | NSArray *allSubviews = self.detailContentView.subviews; 417 | for (UIView *aView in allSubviews) 418 | { 419 | if ([aView respondsToSelector:@selector(customViewWillExpand)]) 420 | { 421 | [aView customViewWillExpand]; 422 | } 423 | } 424 | } 425 | 426 | - (void)expandInfoViewDidExpand 427 | { 428 | NSArray *allSubviews = self.detailContentView.subviews; 429 | for (UIView *aView in allSubviews) 430 | { 431 | if ([aView respondsToSelector:@selector(customViewDidExpand)]) 432 | { 433 | [aView customViewDidExpand]; 434 | } 435 | } 436 | 437 | // default select the first item 438 | if (self.selectedItemIndex < 0) 439 | { 440 | [self manualSelectedItemWithIndex:0]; 441 | } 442 | } 443 | 444 | @end 445 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATExpandInfoViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATExpandInfoViewCell.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/31. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ATExpandInfoViewCell : UICollectionViewCell 14 | 15 | @property (nonatomic, strong) NSString *title; 16 | 17 | @property (nonatomic, strong) UIFont *titleFont; 18 | @property (nonatomic, strong) UIColor *titleNormalColor; 19 | @property (nonatomic, strong) UIColor *titleSelectColor; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATExpandInfoViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATExpandInfoViewCell.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/31. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATExpandInfoViewCell.h" 10 | 11 | @interface ATExpandInfoViewCell () 12 | 13 | @property (nonatomic, strong) UILabel *titleLabel; 14 | 15 | @end 16 | 17 | @implementation ATExpandInfoViewCell 18 | 19 | #pragma mark - Life Cycle 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame 22 | { 23 | if (self = [super initWithFrame:frame]) 24 | { 25 | [self buildExpandInfoViewCell]; 26 | } 27 | return self; 28 | } 29 | 30 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 31 | { 32 | if (self = [super initWithCoder:aDecoder]) 33 | { 34 | [self buildExpandInfoViewCell]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)buildExpandInfoViewCell 40 | { 41 | self.backgroundColor = [UIColor clearColor]; 42 | 43 | [self initProperties]; 44 | 45 | [self initSubviews]; 46 | } 47 | 48 | - (void)initProperties 49 | { 50 | _titleNormalColor = [UIColor blackColor]; 51 | _titleFont = [UIFont systemFontOfSize:15]; 52 | } 53 | 54 | - (void)initSubviews 55 | { 56 | _titleLabel = [[UILabel alloc] init]; 57 | _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 58 | _titleLabel.textAlignment = NSTextAlignmentCenter; 59 | _titleLabel.numberOfLines = 1; 60 | _titleLabel.textColor = _titleNormalColor; 61 | _titleLabel.font = _titleFont; 62 | 63 | [self addSubview:_titleLabel]; 64 | 65 | NSDictionary *views = NSDictionaryOfVariableBindings(_titleLabel); 66 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|" options:0 metrics:nil views:views]]; 67 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]|" options:0 metrics:nil views:views]]; 68 | } 69 | 70 | #pragma mark - Override 71 | 72 | - (void)prepareForReuse 73 | { 74 | [super prepareForReuse]; 75 | 76 | self.title = @""; 77 | } 78 | 79 | - (void)setSelected:(BOOL)selected 80 | { 81 | [super setSelected:selected]; 82 | 83 | self.titleLabel.textColor = selected ? _titleSelectColor : _titleNormalColor; 84 | } 85 | 86 | - (void)setTitle:(NSString *)title 87 | { 88 | _title = title; 89 | self.titleLabel.text = _title; 90 | } 91 | 92 | - (void)setTitleFont:(UIFont *)titleFont 93 | { 94 | _titleFont = titleFont; 95 | self.titleLabel.font = _titleFont; 96 | } 97 | 98 | - (void)setTitleNormalColor:(UIColor *)titleNormalColor 99 | { 100 | _titleNormalColor = titleNormalColor; 101 | if (!self.selected) 102 | { 103 | self.titleLabel.textColor = _titleNormalColor; 104 | } 105 | } 106 | 107 | - (void)setTitleSelectColor:(UIColor *)titleSelectColor 108 | { 109 | _titleSelectColor = titleSelectColor; 110 | if (self.selected) 111 | { 112 | self.titleLabel.textColor = _titleSelectColor; 113 | } 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATRootViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATRootViewController.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATContainerWindow.h" 10 | #import "ATCustomViewProtocol.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | #pragma mark - ATRootViewController 15 | 16 | @protocol ATRootViewControllerDelegate 17 | 18 | @end 19 | 20 | @interface ATRootViewController : UIViewController 21 | 22 | @property (nonatomic, weak) id delegate; 23 | 24 | // default YES 25 | @property (nonatomic, assign) BOOL autorotateEnabled; 26 | 27 | @property (nonatomic, readonly) NSArray *currentTitles; 28 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle; 29 | - (void)removeCustiomViewForTitle:(NSString *)aTitle; 30 | - (void)removeAllCustomViews; 31 | 32 | // used for ATAssistiveTools 33 | @property (nonatomic, weak) ATContainerWindow *assistiveWindow; 34 | @property (nonatomic, readonly) CGRect shrinkedWindowFrame; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATRootViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATRootViewController.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATRootViewController.h" 10 | #import "ATShrinkInfoView.h" 11 | #import "ATExpandInfoView.h" 12 | 13 | #define kATAnimationDuration 0.2f 14 | 15 | typedef NS_ENUM(NSUInteger, ATRootViewControllerStatus) { 16 | ATRootViewControllerStatusShrink = 1, 17 | ATRootViewControllerStatusExpand = 2, 18 | }; 19 | 20 | #pragma mark - ATRootViewController 21 | 22 | @interface ATRootViewController () 23 | 24 | @property (nonatomic, strong) ATShrinkInfoView *shrinkInfoView; 25 | @property (nonatomic, strong) ATExpandInfoView *expandInfoView; 26 | 27 | @property (nonatomic, assign) CGRect curScreenBounds; 28 | @property (nonatomic, assign) CGRect expandedWindowFrame; 29 | @property (nonatomic, assign) CGRect shrinkedWindowFrame; 30 | 31 | @property (nonatomic, assign) ATRootViewControllerStatus status; 32 | @property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer; 33 | 34 | @end 35 | 36 | @implementation ATRootViewController 37 | 38 | #pragma mark - Private: Life Cycle 39 | 40 | - (instancetype)init 41 | { 42 | if (self = [super init]) 43 | { 44 | [self initProperties]; 45 | } 46 | return self; 47 | } 48 | 49 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 50 | { 51 | if (self = [super initWithCoder:aDecoder]) 52 | { 53 | [self initProperties]; 54 | } 55 | return self; 56 | } 57 | 58 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 59 | { 60 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) 61 | { 62 | [self initProperties]; 63 | } 64 | return self; 65 | } 66 | 67 | - (void)dealloc 68 | { 69 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 70 | 71 | self.shrinkInfoView.status = ATShrinkInfoViewStatusNone; 72 | } 73 | 74 | - (void)viewDidLoad 75 | { 76 | [super viewDidLoad]; 77 | 78 | [self buildRootViewController]; 79 | } 80 | 81 | - (void)viewWillAppear:(BOOL)animated 82 | { 83 | [super viewWillAppear:animated]; 84 | } 85 | 86 | - (void)viewDidAppear:(BOOL)animated 87 | { 88 | [super viewDidAppear:animated]; 89 | } 90 | 91 | - (void)viewWillDisappear:(BOOL)animated 92 | { 93 | [super viewWillDisappear:animated]; 94 | } 95 | 96 | #pragma mark - Private: Initilization 97 | 98 | - (void)buildRootViewController 99 | { 100 | [self initShrinkInfoView]; 101 | 102 | [self initExpandInfoView]; 103 | } 104 | 105 | - (void)initProperties 106 | { 107 | _curScreenBounds = [[UIScreen mainScreen] bounds]; 108 | _expandedWindowFrame = CGRectMake((CGRectGetWidth(_curScreenBounds)-kATExpandViewWidth)/2.0, (CGRectGetHeight(_curScreenBounds)-kATExpandViewHeight)/2.0, kATExpandViewWidth, kATExpandViewHeight); 109 | _shrinkedWindowFrame = [self normalizdFrameToScreenSide:CGRectMake(0, CGRectGetMidY(_curScreenBounds), kATShrinkViewWidth, kATShrinkViewWidth)]; 110 | 111 | _status = ATRootViewControllerStatusShrink; 112 | _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognizerAction:)]; 113 | 114 | _autorotateEnabled = YES; 115 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChangeAction:) name:UIDeviceOrientationDidChangeNotification object:nil]; 116 | } 117 | 118 | - (void)initShrinkInfoView 119 | { 120 | _shrinkInfoView = [[ATShrinkInfoView alloc] initWithFrame:CGRectMake(0, 0, kATShrinkViewWidth, kATShrinkViewWidth)]; 121 | _shrinkInfoView.delegate = self; 122 | _shrinkInfoView.hidden = NO; 123 | 124 | [self.shrinkInfoView addGestureRecognizer:self.panGestureRecognizer]; 125 | [self.view addSubview:self.shrinkInfoView]; 126 | } 127 | 128 | - (void)initExpandInfoView 129 | { 130 | _expandInfoView = [[ATExpandInfoView alloc] initWithFrame:CGRectMake(0, 0, kATExpandViewWidth, kATExpandViewHeight)]; 131 | _expandInfoView.delegate = self; 132 | _expandInfoView.hidden = YES; 133 | 134 | [self.view addSubview:self.expandInfoView]; 135 | } 136 | 137 | #pragma mark - Private: Interface Orientations 138 | 139 | - (BOOL)prefersStatusBarHidden 140 | { 141 | return NO; 142 | } 143 | 144 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations 145 | { 146 | return self.autorotateEnabled ? UIInterfaceOrientationMaskAll : UIInterfaceOrientationMaskPortrait; 147 | } 148 | 149 | - (BOOL)shouldAutorotate 150 | { 151 | return self.autorotateEnabled; 152 | } 153 | 154 | - (void)orientationDidChangeAction:(NSNotification *)notification 155 | { 156 | if (!self.autorotateEnabled) 157 | { 158 | return; 159 | } 160 | 161 | CGRect preScreenBounds = self.curScreenBounds; 162 | CGRect curScreenBounds = [[UIScreen mainScreen] bounds]; 163 | 164 | double xRate = curScreenBounds.size.width / preScreenBounds.size.width; 165 | double yRate = curScreenBounds.size.height / preScreenBounds.size.height; 166 | self.curScreenBounds = curScreenBounds; 167 | 168 | // calculate shrinked window frame 169 | CGRect extendFrame = CGRectInset(self.shrinkedWindowFrame, -kATShrinkViewMargin, -kATShrinkViewMargin); 170 | 171 | CGPoint resOrigin = CGPointMake(extendFrame.origin.x * xRate, extendFrame.origin.y * yRate); 172 | extendFrame.origin.x = resOrigin.x; 173 | extendFrame.origin.y = resOrigin.y; 174 | 175 | CGRect resFrame = CGRectInset(extendFrame, kATShrinkViewMargin, kATShrinkViewMargin); 176 | self.shrinkedWindowFrame = [self normalizdFrameToScreenSide:resFrame]; 177 | 178 | // calculate expended window frame 179 | CGPoint newExpandOrigin = CGPointMake(self.expandedWindowFrame.origin.x * xRate, self.expandedWindowFrame.origin.y * yRate); 180 | CGRect newExpandFrame = CGRectMake(newExpandOrigin.x, newExpandOrigin.y, self.expandedWindowFrame.size.width, self.expandedWindowFrame.size.height); 181 | self.expandedWindowFrame = newExpandFrame; 182 | 183 | if (self.status == ATRootViewControllerStatusShrink) 184 | { 185 | self.assistiveWindow.frame = self.shrinkedWindowFrame; 186 | } 187 | else if (self.status == ATRootViewControllerStatusExpand) 188 | { 189 | self.assistiveWindow.frame = self.expandedWindowFrame; 190 | } 191 | else 192 | { 193 | NSLog(@"ATRootViewControllerStatusShrink Error"); 194 | } 195 | } 196 | 197 | #pragma mark - Private: Gesture Recognizer Action 198 | 199 | - (void)panGestureRecognizerAction:(UIPanGestureRecognizer *)panGesture 200 | { 201 | if (panGesture.state == UIGestureRecognizerStateBegan) 202 | { 203 | // expand assistive window so that shrink info view can move to everywhere 204 | self.assistiveWindow.frame = [[UIScreen mainScreen] bounds]; 205 | 206 | // move shrinkInfoView to touchPoint 207 | CGPoint touchPoint = [panGesture locationInView:self.assistiveWindow]; 208 | self.shrinkInfoView.center = touchPoint; 209 | 210 | // change shrinkInfoView status 211 | self.shrinkInfoView.status = ATShrinkInfoViewStatusActive; 212 | } 213 | else if (panGesture.state == UIGestureRecognizerStateChanged) 214 | { 215 | // move shrink info view 216 | CGPoint touchPoint = [panGesture locationInView:self.assistiveWindow]; 217 | self.shrinkInfoView.center = touchPoint; 218 | } 219 | else if (panGesture.state == UIGestureRecognizerStateEnded 220 | || panGesture.state == UIGestureRecognizerStateFailed 221 | || panGesture.state == UIGestureRecognizerStateCancelled) 222 | { 223 | self.assistiveWindow.frame = CGRectMake(self.shrinkInfoView.frame.origin.x, self.shrinkInfoView.frame.origin.y, kATShrinkViewWidth, kATShrinkViewWidth); 224 | self.shrinkInfoView.frame = CGRectMake(0, 0, kATShrinkViewWidth, kATShrinkViewWidth); 225 | 226 | [UIView animateWithDuration:kATAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 227 | 228 | // strict to screen side 229 | self.assistiveWindow.frame = [self normalizdFrameToScreenSide:self.assistiveWindow.frame]; 230 | 231 | } completion:^(BOOL finished) { 232 | 233 | // save frame for shrink amination 234 | self.shrinkedWindowFrame = self.assistiveWindow.frame; 235 | 236 | // change shrinkInfoView status 237 | self.shrinkInfoView.status = ATShrinkInfoViewStatusCountdown; 238 | }]; 239 | } 240 | } 241 | 242 | #pragma mark - Private: ATShrinkInfoViewDelegate 243 | 244 | - (void)shrinkInfoViewTaped:(ATShrinkInfoView *)shrinkView atPoint:(CGPoint)tapPoint 245 | { 246 | self.status = ATRootViewControllerStatusExpand; 247 | 248 | // change shrinkInfoView status 249 | self.shrinkInfoView.status = ATShrinkInfoViewStatusActive; 250 | 251 | [self.expandInfoView expandInfoViewWillExpand]; 252 | 253 | [UIView animateWithDuration:kATAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 254 | self.shrinkInfoView.hidden = YES; 255 | self.expandInfoView.hidden = NO; 256 | 257 | self.assistiveWindow.frame = self.expandedWindowFrame; 258 | self.expandInfoView.frame = CGRectMake(0, 0, kATExpandViewWidth, kATExpandViewHeight); 259 | 260 | } completion:^(BOOL finished) { 261 | 262 | [self.expandInfoView expandInfoViewDidExpand]; 263 | }]; 264 | } 265 | 266 | #pragma mark - Private: ATExpandInfoViewDelegate 267 | 268 | - (void)expandInfoViewCloseAction:(ATExpandInfoView *)expandView 269 | { 270 | self.status = ATRootViewControllerStatusShrink; 271 | 272 | [self.expandInfoView expandInfoViewWillShrink]; 273 | 274 | [UIView animateWithDuration:kATAnimationDuration animations:^{ 275 | 276 | self.assistiveWindow.frame = self.shrinkedWindowFrame; 277 | self.shrinkInfoView.frame = CGRectMake(0, 0, kATShrinkViewWidth, kATShrinkViewWidth); 278 | 279 | } completion:^(BOOL finished) { 280 | self.shrinkInfoView.hidden = NO; 281 | self.expandInfoView.hidden = YES; 282 | 283 | // change shrinkInfoView status 284 | self.shrinkInfoView.status = ATShrinkInfoViewStatusCountdown; 285 | 286 | [self.expandInfoView expandInfoViewDidShrink]; 287 | }]; 288 | } 289 | 290 | #pragma mark - Public: Interface 291 | 292 | - (NSArray *)currentTitles 293 | { 294 | return self.expandInfoView.currentTitles; 295 | } 296 | 297 | - (void)addCustomView:(UIView *)aView forTitle:(NSString *)aTitle 298 | { 299 | [self.expandInfoView addCustomView:aView forTitle:aTitle]; 300 | } 301 | 302 | - (void)removeCustiomViewForTitle:(NSString *)aTitle 303 | { 304 | [self.expandInfoView removeCustiomViewForTitle:aTitle]; 305 | } 306 | 307 | - (void)removeAllCustomViews 308 | { 309 | [self.expandInfoView removeAllCustomViews]; 310 | } 311 | 312 | #pragma mark - Private: Handle Position 313 | 314 | - (CGRect)normalizdFrameToScreenSide:(CGRect)curFrame 315 | { 316 | CGRect result = curFrame; 317 | 318 | CGSize screenSize = [[UIScreen mainScreen] bounds].size; 319 | CGPoint curCenter = CGPointMake(CGRectGetMidX(curFrame), CGRectGetMidY(curFrame)); 320 | 321 | if (curCenter.y < screenSize.height * 0.15) 322 | { 323 | result.origin.x = MAX(0, MIN(screenSize.width - curFrame.size.width, result.origin.x)); 324 | result.origin.y = kATShrinkViewMargin; 325 | } 326 | else if (curCenter.y > screenSize.height * 0.85) 327 | { 328 | result.origin.x = MAX(0, MIN(screenSize.width - curFrame.size.width, result.origin.x)); 329 | result.origin.y = screenSize.height - curFrame.size.height - kATShrinkViewMargin; 330 | } 331 | else if (curCenter.x < screenSize.width/2.0) 332 | { 333 | result.origin.x = kATShrinkViewMargin; 334 | } 335 | else if (curCenter.x >= screenSize.width/2.0) 336 | { 337 | result.origin.x = screenSize.width - curFrame.size.width - kATShrinkViewMargin; 338 | } 339 | 340 | return result; 341 | } 342 | 343 | @end 344 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATShrinkInfoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATShrinkInfoView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define kATShrinkViewActiveAlpha 0.75f 12 | #define kATShrinkViewDeactiveAlpha 0.25f 13 | #define kATShrinkViewWidth 60.f 14 | #define kATShrinkViewMargin 2.f 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | typedef NS_ENUM(NSUInteger, ATShrinkInfoViewStatus) { 19 | ATShrinkInfoViewStatusNone = 0,//just stop timer 20 | ATShrinkInfoViewStatusActive = 1, 21 | ATShrinkInfoViewStatusCountdown = 2, 22 | ATShrinkInfoViewStatusDeactive = 3, 23 | }; 24 | 25 | @class ATShrinkInfoView; 26 | 27 | @protocol ATShrinkInfoViewDelegate 28 | 29 | - (void)shrinkInfoViewTaped:(ATShrinkInfoView *)shrinkView atPoint:(CGPoint)tapPoint; 30 | 31 | @end 32 | 33 | @interface ATShrinkInfoView : UIView 34 | 35 | @property (nonatomic, weak) id delegate; 36 | 37 | @property (nonatomic, assign) ATShrinkInfoViewStatus status; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ATAssistiveTools/ATShrinkInfoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATShrinkInfoView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/5/26. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATShrinkInfoView.h" 10 | 11 | #define kATShrinkViewCornerRadius (kATShrinkViewWidth/5.0) 12 | #define kATShrinkViewAnimationDuration 0.2f 13 | 14 | @interface ATShrinkInfoView () 15 | 16 | @property (nonatomic, strong) NSTimer *alphaTimer; 17 | @property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer; 18 | 19 | @property (nonatomic, strong) UIView *contentView; 20 | @property (nonatomic, strong) UILabel *mainTipLabel; 21 | 22 | @end 23 | 24 | @implementation ATShrinkInfoView 25 | 26 | #pragma mark - Life Cycle 27 | 28 | - (instancetype)initWithFrame:(CGRect)frame 29 | { 30 | if (self = [super initWithFrame:frame]) 31 | { 32 | [self buildShrinkInfoView]; 33 | } 34 | return self; 35 | } 36 | 37 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 38 | { 39 | if (self = [super initWithCoder:aDecoder]) 40 | { 41 | [self buildShrinkInfoView]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)dealloc 47 | { 48 | [self stopAlphaTimer]; 49 | } 50 | 51 | - (void)buildShrinkInfoView 52 | { 53 | self.layer.cornerRadius = kATShrinkViewCornerRadius; 54 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:kATShrinkViewDeactiveAlpha]; 55 | 56 | [self initProperties]; 57 | 58 | [self initContentView]; 59 | 60 | [self initMainTipLabel]; 61 | 62 | [self.mainTipLabel setText:@"ATAssistiveTools"]; 63 | } 64 | 65 | - (void)initProperties 66 | { 67 | _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureRecognizerAction:)]; 68 | [_tapGestureRecognizer setNumberOfTapsRequired:1]; 69 | [_tapGestureRecognizer setNumberOfTouchesRequired:1]; 70 | 71 | [self addGestureRecognizer:_tapGestureRecognizer]; 72 | } 73 | 74 | - (void)initContentView 75 | { 76 | _contentView = [[UIView alloc] init]; 77 | [_contentView setTranslatesAutoresizingMaskIntoConstraints:NO]; 78 | 79 | [self addSubview:_contentView]; 80 | 81 | NSDictionary *views = NSDictionaryOfVariableBindings(_contentView); 82 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[_contentView]-right-|" options:0 metrics:@{@"left":@(kATShrinkViewCornerRadius/2.0),@"right":@(kATShrinkViewCornerRadius/2.0)} views:views]]; 83 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[_contentView]-bottom-|" options:0 metrics:@{@"top":@(kATShrinkViewCornerRadius/2.0),@"bottom":@(kATShrinkViewCornerRadius/2.0)} views:views]]; 84 | } 85 | 86 | - (void)initMainTipLabel 87 | { 88 | _mainTipLabel = [[UILabel alloc] init]; 89 | _mainTipLabel.translatesAutoresizingMaskIntoConstraints = NO; 90 | _mainTipLabel.numberOfLines = 4; 91 | _mainTipLabel.textColor = [UIColor whiteColor]; 92 | _mainTipLabel.font = [UIFont systemFontOfSize:8]; 93 | 94 | [self.contentView addSubview:_mainTipLabel]; 95 | 96 | NSDictionary *views = NSDictionaryOfVariableBindings(_mainTipLabel); 97 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_mainTipLabel]|" options:0 metrics:nil views:views]]; 98 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_mainTipLabel]|" options:0 metrics:nil views:views]]; 99 | } 100 | 101 | #pragma mark - Timer Action 102 | 103 | - (void)startAlphaTimer 104 | { 105 | if (self.alphaTimer != nil) 106 | { 107 | return; 108 | } 109 | 110 | self.alphaTimer = [NSTimer timerWithTimeInterval:4.f target:self selector:@selector(timerAction:) userInfo:nil repeats:NO]; 111 | [[NSRunLoop currentRunLoop] addTimer:self.alphaTimer forMode:NSRunLoopCommonModes]; 112 | } 113 | 114 | - (void)stopAlphaTimer 115 | { 116 | if (self.alphaTimer == nil) 117 | { 118 | return; 119 | } 120 | 121 | [self.alphaTimer invalidate]; 122 | self.alphaTimer = nil; 123 | } 124 | 125 | - (void)timerAction:(NSTimer *)timer 126 | { 127 | [UIView animateWithDuration:kATShrinkViewAnimationDuration animations:^{ 128 | self.status = ATShrinkInfoViewStatusDeactive; 129 | }]; 130 | } 131 | 132 | #pragma mark - Interface 133 | 134 | - (void)setStatus:(ATShrinkInfoViewStatus)status 135 | { 136 | [self stopAlphaTimer]; 137 | 138 | _status = status; 139 | if (_status == ATShrinkInfoViewStatusActive) 140 | { 141 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:kATShrinkViewActiveAlpha]; 142 | } 143 | else if (_status == ATShrinkInfoViewStatusCountdown) 144 | { 145 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:kATShrinkViewActiveAlpha]; 146 | 147 | [self startAlphaTimer]; 148 | } 149 | else if (_status == ATShrinkInfoViewStatusDeactive) 150 | { 151 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:kATShrinkViewDeactiveAlpha]; 152 | } 153 | else 154 | { 155 | // do nothing 156 | } 157 | } 158 | 159 | #pragma mark - Gesture Recognizer Action 160 | 161 | - (void)tapGestureRecognizerAction:(UITapGestureRecognizer *)tapGesture 162 | { 163 | if (self.delegate) 164 | { 165 | CGPoint touchPoint = [tapGesture locationInView:self]; 166 | [self.delegate shrinkInfoViewTaped:self atPoint:touchPoint]; 167 | } 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ATAssistiveToolsDemo 4 | // 5 | // Created by 刘博 on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ATAssistiveToolsDemo 4 | // 5 | // Created by 刘博 on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | // Assistive Tool 12 | #import "ATAssistiveTools.h" 13 | 14 | // Custom Views 15 | #import "ATDeviceLogsView.h" 16 | #import "ATFakeLocationView.h" 17 | #import "ATSandboxViewerView.h" 18 | 19 | @interface AppDelegate () 20 | 21 | @end 22 | 23 | @implementation AppDelegate 24 | 25 | 26 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 27 | 28 | // Show the Assistive Tool 29 | [[ATAssistiveTools sharedInstance] show]; 30 | 31 | // Add custom views to assistive tool 32 | 33 | // add fake location view 34 | ATFakeLocationView *simLocView = [[ATFakeLocationView alloc] init]; 35 | [[ATAssistiveTools sharedInstance] addCustomView:simLocView forTitle:@"FakeLocation"]; 36 | 37 | // add sandbox viewer view 38 | ATSandboxViewerView *sandboxView = [[ATSandboxViewerView alloc] init]; 39 | [[ATAssistiveTools sharedInstance] addCustomView:sandboxView forTitle:@"SandboxViewer"]; 40 | 41 | // add device log view 42 | ATDeviceLogsView *logsView = [[ATDeviceLogsView alloc] init]; 43 | [[ATAssistiveTools sharedInstance] addCustomView:logsView forTitle:@"DeviceLog"]; 44 | 45 | return YES; 46 | } 47 | 48 | 49 | - (void)applicationWillResignActive:(UIApplication *)application { 50 | // 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. 51 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 52 | } 53 | 54 | 55 | - (void)applicationDidEnterBackground:(UIApplication *)application { 56 | // 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. 57 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 58 | } 59 | 60 | 61 | - (void)applicationWillEnterForeground:(UIApplication *)application { 62 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 63 | } 64 | 65 | 66 | - (void)applicationDidBecomeActive:(UIApplication *)application { 67 | // 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. 68 | } 69 | 70 | 71 | - (void)applicationWillTerminate:(UIApplication *)application { 72 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 73 | } 74 | 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/Assets.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 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSLocationAlwaysUsageDescription 24 | Need Location 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 | 40 | 41 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ATAssistiveToolsDemo 4 | // 5 | // Created by 刘博 on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ATAssistiveToolsDemo 4 | // 5 | // Created by 刘博 on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view, typically from a nib. 20 | } 21 | 22 | 23 | - (void)didReceiveMemoryWarning { 24 | [super didReceiveMemoryWarning]; 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /ATAssistiveToolsDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ATAssistiveToolsDemo 4 | // 5 | // Created by 刘博 on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ATCustomizeViews/ATDeviceLogsView/ATDeviceLogsView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATDeviceLogsView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/11. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ATCustomViewProtocol.h" 11 | 12 | @interface ATDeviceLogsView : UIView 13 | 14 | + (void)asyncReadDeviceLogsWithCompletionBlock:(void (^)(NSString *logs))completionBlock; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ATCustomizeViews/ATDeviceLogsView/ATDeviceLogsView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATDeviceLogsView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/11. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATDeviceLogsView.h" 10 | 11 | #import "asl.h" 12 | #import 13 | 14 | @interface ATDeviceLogsView () 15 | 16 | @property (nonatomic, strong) UITextView *textView; 17 | @property (nonatomic, strong) UIButton *refreshButton; 18 | 19 | @end 20 | 21 | @implementation ATDeviceLogsView 22 | 23 | #pragma mark - Life Cycle 24 | 25 | - (instancetype)initWithFrame:(CGRect)frame 26 | { 27 | if (self = [super initWithFrame:frame]) 28 | { 29 | [self buildDeviceLogsView]; 30 | } 31 | return self; 32 | } 33 | 34 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 35 | { 36 | if (self = [super initWithCoder:aDecoder]) 37 | { 38 | [self buildDeviceLogsView]; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)buildDeviceLogsView 44 | { 45 | self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 46 | 47 | [self initProperties]; 48 | 49 | [self initSubviews]; 50 | } 51 | 52 | - (void)initProperties 53 | { 54 | 55 | } 56 | 57 | - (void)initSubviews 58 | { 59 | self.refreshButton = [UIButton buttonWithType:UIButtonTypeSystem]; 60 | self.refreshButton.translatesAutoresizingMaskIntoConstraints = NO; 61 | self.refreshButton.layer.borderColor = [UIColor blueColor].CGColor; 62 | self.refreshButton.layer.borderWidth = 0.5; 63 | self.refreshButton.titleLabel.font = [UIFont systemFontOfSize:15.0]; 64 | [self.refreshButton setTitle:@"Refresh" forState:UIControlStateNormal]; 65 | [self.refreshButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 66 | [self.refreshButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted]; 67 | [self.refreshButton addTarget:self action:@selector(refreshButtonAction:) forControlEvents:UIControlEventTouchUpInside]; 68 | 69 | self.textView = [[UITextView alloc] init]; 70 | self.textView.translatesAutoresizingMaskIntoConstraints = NO; 71 | self.textView.editable = NO; 72 | self.textView.selectable = NO; 73 | 74 | [self addSubview:self.refreshButton]; 75 | [self addSubview:self.textView]; 76 | 77 | NSDictionary *views = NSDictionaryOfVariableBindings(_textView, _refreshButton); 78 | 79 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_textView]|" options:0 metrics:nil views:views]]; 80 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[_refreshButton(==width)]" options:0 metrics:@{@"width":@(80)} views:views]]; 81 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[_refreshButton(==height)]-10-[_textView]|" options:0 metrics:@{@"height":@(20)} views:views]]; 82 | } 83 | 84 | #pragma mark - Button Action 85 | 86 | - (void)refreshButtonAction:(UIButton *)button 87 | { 88 | self.textView.text = @""; 89 | 90 | [self updateDeviceLog]; 91 | } 92 | 93 | #pragma mark - Methods 94 | 95 | - (void)updateDeviceLog 96 | { 97 | if ([[[UIDevice currentDevice] systemVersion] doubleValue] < 10.0) 98 | { 99 | __weak typeof(self) weakSelf = self; 100 | [ATDeviceLogsView asyncReadDeviceLogsWithCompletionBlock:^(NSString *logs) { 101 | weakSelf.textView.text = logs; 102 | [weakSelf.textView scrollRangeToVisible:NSMakeRange(weakSelf.textView.text.length-10, 10)]; 103 | }]; 104 | } 105 | else 106 | { 107 | self.textView.text = @"Onle applicable to system version less than 10.0!"; 108 | } 109 | } 110 | 111 | #pragma mark - ATCustomViewProtocol 112 | 113 | - (void)customViewDidAppear 114 | { 115 | [self updateDeviceLog]; 116 | } 117 | 118 | #pragma mark - Read Device Log 119 | 120 | + (void)asyncReadDeviceLogsWithCompletionBlock:(void (^)(NSString *logs))completionBlock 121 | { 122 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 123 | NSString *logs = [self readDeviceLogs]; 124 | dispatch_async(dispatch_get_main_queue(), ^{ 125 | if (completionBlock) 126 | { 127 | completionBlock(logs); 128 | } 129 | }); 130 | }); 131 | } 132 | 133 | + (NSString *)readDeviceLogs 134 | { 135 | aslmsg q, m; 136 | int i; 137 | const char *key, *val; 138 | NSMutableString *logs = [NSMutableString stringWithString:@""]; 139 | 140 | q = asl_new(ASL_TYPE_QUERY); 141 | 142 | aslresponse r = asl_search(NULL, q); 143 | while (NULL != (m = asl_next(r))) 144 | { 145 | NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary]; 146 | 147 | for (i = 0; (NULL != (key = asl_key(m, i))); i++) 148 | { 149 | NSString *keyString = [NSString stringWithUTF8String:(char *)key]; 150 | 151 | val = asl_get(m, key); 152 | 153 | NSString *string = val != NULL ? [NSString stringWithUTF8String:val] : nil; 154 | [tmpDict setValue:string forKey:keyString]; 155 | } 156 | 157 | NSString *line = [NSString stringWithFormat:@"%@ %@[%@] %@\n", [NSDate dateWithTimeIntervalSince1970:[tmpDict[@"Time"] intValue]], tmpDict[@"Sender"], tmpDict[@"PID"], tmpDict[@"Message"]]; 158 | 159 | [logs appendString:line]; 160 | } 161 | asl_release(r); 162 | 163 | return logs; 164 | } 165 | 166 | @end 167 | -------------------------------------------------------------------------------- /ATCustomizeViews/ATFakeLocationView/ATFakeLocationView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATFakeLocationView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/14. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "ATCustomViewProtocol.h" 12 | 13 | @interface ATFakeLocationView : UIView 14 | 15 | @end 16 | 17 | @interface ATSimlulateCoordinate : NSObject 18 | 19 | + (instancetype)sharedInstance; 20 | 21 | @property (nonatomic, assign) BOOL useCLLocationCoordiante; 22 | @property (nonatomic, assign) BOOL useCLLocationManager; 23 | 24 | @property (nonatomic, assign) CLLocationCoordinate2D externalWGS84Coordinate; 25 | @property (nonatomic, assign) CLLocationCoordinate2D externalGCJ02Coordinate; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ATCustomizeViews/ATGPSEmulatorView/ATGPSEmulator.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATGPSEmulator.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol ATGPSEmulatorDelegate 13 | @required 14 | 15 | /** 16 | * Called when GPS emulator produce new location. 17 | * 18 | * @param location a new location 19 | */ 20 | - (void)gpsEmulatorUpdateLocation:(CLLocation *)location; 21 | 22 | @end 23 | 24 | @interface ATGPSEmulator : NSObject 25 | 26 | /** 27 | * A object adopt the ATGPSEmulatorDelegate protocol 28 | */ 29 | @property (nonatomic, weak) id delegate; 30 | 31 | /** 32 | * Indicate whether the GPS emulator isSimulating. 33 | */ 34 | @property (atomic, readonly) BOOL isSimulating; 35 | 36 | /** 37 | * Simulate Speed(Unit: km/h; Default: 60km/h;) 38 | */ 39 | @property (nonatomic, assign) double simulateSpeed; 40 | 41 | /** 42 | * Assign coordiantes that used for simulate. Invoke this method after start emulator has no effect. 43 | * 44 | * @param coordinates coordinate list 45 | * @param count coordiantes count 46 | */ 47 | - (void)setCoordinates:(CLLocationCoordinate2D *)coordinates count:(unsigned long)count; 48 | 49 | /** 50 | * Start Emulator 51 | */ 52 | - (void)startEmulator; 53 | 54 | /** 55 | * Stop Emulator 56 | */ 57 | - (void)stopEmulator; 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /ATCustomizeViews/ATGPSEmulatorView/ATGPSEmulator.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATGPSEmulator.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/27. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATGPSEmulator.h" 10 | 11 | #pragma mark - basic 12 | 13 | bool coordinateEqualToCoordiante(CLLocationCoordinate2D coordiante1, CLLocationCoordinate2D coordinate2) 14 | { 15 | #define kATGPSNodePointEquallyValue (0.000001) 16 | 17 | if (fabs(coordiante1.latitude - coordinate2.latitude) < kATGPSNodePointEquallyValue 18 | && fabs(coordiante1.longitude - coordinate2.longitude) < kATGPSNodePointEquallyValue) 19 | { 20 | return true; 21 | } 22 | else 23 | { 24 | return false; 25 | } 26 | } 27 | 28 | double distanceBetweenCoordinates(CLLocationCoordinate2D pointA, CLLocationCoordinate2D pointB) 29 | { 30 | #define AMAPLOC_DEG_TO_RAD 0.0174532925199432958f 31 | #define AMAPLOC_EARTH_RADIUS 6378137.0f 32 | 33 | double latitudeArc = (pointA.latitude - pointB.latitude) * AMAPLOC_DEG_TO_RAD; 34 | double longitudeArc = (pointA.longitude - pointB.longitude) * AMAPLOC_DEG_TO_RAD; 35 | 36 | double latitudeH = sin(latitudeArc * 0.5); 37 | latitudeH *= latitudeH; 38 | double lontitudeH = sin(longitudeArc * 0.5); 39 | lontitudeH *= lontitudeH; 40 | 41 | double tmp = cos(pointA.latitude * AMAPLOC_DEG_TO_RAD) * cos(pointB.latitude*AMAPLOC_DEG_TO_RAD); 42 | return AMAPLOC_EARTH_RADIUS * 2.0 * asin(sqrt(latitudeH + tmp*lontitudeH)); 43 | } 44 | 45 | CLLocationCoordinate2D coordinateAtRateOfCoordinates(CLLocationCoordinate2D from, CLLocationCoordinate2D to, double rate) 46 | { 47 | if (rate >= 1.f) return to; 48 | if (rate <= 0.f) return from; 49 | 50 | double latitudeDelta = (to.latitude - from.latitude) * rate; 51 | double longitudeDelta = (to.longitude - from.longitude) * rate; 52 | 53 | return CLLocationCoordinate2DMake(from.latitude + latitudeDelta, from.longitude + longitudeDelta); 54 | } 55 | 56 | double normalizeDegree(double degree) 57 | { 58 | double normalizationDegree = fmod(degree, 360.f); 59 | return (normalizationDegree < 0) ? normalizationDegree += 360.f : normalizationDegree; 60 | } 61 | 62 | double angleBetweenCoordinates(CLLocationCoordinate2D pointA, CLLocationCoordinate2D pointB) 63 | { 64 | double longitudeDelta = pointB.longitude - pointA.longitude; 65 | double latitudeDelta = pointB.latitude - pointA.latitude; 66 | double azimuth = (M_PI * .5f) - atan2(latitudeDelta, longitudeDelta); 67 | 68 | return normalizeDegree(azimuth * 180 / M_PI); 69 | } 70 | 71 | #pragma mark - ATGPSEmulator 72 | 73 | @interface ATGPSEmulator () 74 | { 75 | CLLocationCoordinate2D *_oriCoordinates; 76 | unsigned long _count; 77 | } 78 | 79 | @property (nonatomic, strong) NSThread *locationsThread; 80 | @property (atomic, assign) BOOL isSimulating; 81 | @property (nonatomic, assign) double timeInverval; 82 | 83 | @property (nonatomic, strong) NSRecursiveLock *lock; 84 | @property (nonatomic, assign) double speed; 85 | @property (nonatomic, assign) double distancePerStep; 86 | 87 | @end 88 | 89 | @implementation ATGPSEmulator 90 | 91 | #pragma mark - Life Cycle 92 | 93 | - (instancetype)init 94 | { 95 | if (self = [super init]) 96 | { 97 | [self buildGPSEmulator]; 98 | } 99 | return self; 100 | } 101 | 102 | - (void)dealloc 103 | { 104 | [self stopEmulator]; 105 | 106 | [self deleteCoordinates]; 107 | } 108 | 109 | - (void)buildGPSEmulator 110 | { 111 | [self initProperties]; 112 | } 113 | 114 | - (void)initProperties 115 | { 116 | _isSimulating = NO; 117 | _timeInverval = 1.f; 118 | 119 | self.lock = [[NSRecursiveLock alloc] init]; 120 | self.simulateSpeed = 60.0; 121 | } 122 | 123 | #pragma mark - Interface 124 | 125 | - (void)setSimulateSpeed:(double)simulateSpeed 126 | { 127 | _simulateSpeed = MAX(0, MIN(200, simulateSpeed)); 128 | 129 | [self.lock lock]; 130 | self.speed = _simulateSpeed / 3.6f; 131 | self.distancePerStep = self.timeInverval * self.speed; 132 | [self.lock unlock]; 133 | } 134 | 135 | - (void)setCoordinates:(CLLocationCoordinate2D *)coordinates count:(unsigned long)count 136 | { 137 | if (self.isSimulating) 138 | { 139 | return; 140 | } 141 | 142 | if (coordinates == NULL || count <= 0) 143 | { 144 | return; 145 | } 146 | 147 | [self deleteCoordinates]; 148 | 149 | _count = count; 150 | _oriCoordinates = (CLLocationCoordinate2D *)malloc(sizeof(CLLocationCoordinate2D) * _count); 151 | for (int i = 0; i < _count; i++) 152 | { 153 | _oriCoordinates[i].latitude = (*(coordinates+i)).latitude; 154 | _oriCoordinates[i].longitude = (*(coordinates+i)).longitude; 155 | } 156 | } 157 | 158 | - (void)startEmulator 159 | { 160 | if (self.isSimulating) 161 | { 162 | return; 163 | } 164 | 165 | if (_locationsThread) 166 | { 167 | [_locationsThread cancel]; 168 | _locationsThread = nil; 169 | } 170 | 171 | self.isSimulating = YES; 172 | 173 | _locationsThread = [[NSThread alloc] initWithTarget:self selector:@selector(locationThreadEntryMethod) object:nil]; 174 | [_locationsThread setName:@"com.devliubo.ATGPSEmulatorThread.coordinate"]; 175 | [_locationsThread start]; 176 | } 177 | 178 | - (void)stopEmulator 179 | { 180 | if (_locationsThread) 181 | { 182 | [_locationsThread cancel]; 183 | _locationsThread = nil; 184 | } 185 | 186 | self.isSimulating = NO; 187 | } 188 | 189 | #pragma mark - Mehtods 190 | 191 | - (void)deleteCoordinates 192 | { 193 | if (_oriCoordinates != NULL) 194 | { 195 | free(_oriCoordinates); 196 | _oriCoordinates = NULL; 197 | _count = 0; 198 | } 199 | } 200 | 201 | #pragma mark - Thread Entry Method 202 | 203 | - (void)locationThreadEntryMethod 204 | { 205 | double currentIndex = 0; 206 | double redundantDistance = 0; 207 | 208 | while (currentIndex < _count-1 && _locationsThread && ![_locationsThread isCancelled]) 209 | { 210 | // save a copy of 'distancePerStep' and 'speed' 211 | [self.lock lock]; 212 | double tempDistancePerStep = self.distancePerStep; 213 | double tempSpeed = self.speed; 214 | [self.lock unlock]; 215 | 216 | // generate properties for CLLocation 217 | unsigned long nextIndex = currentIndex; 218 | CLLocationCoordinate2D resultCoordinate = [self findCoordinateFromIndex:currentIndex 219 | afterDistance:(tempDistancePerStep + redundantDistance) 220 | resultLocateIndex:&nextIndex 221 | resultRedundantDistance:&redundantDistance]; 222 | 223 | double course = angleBetweenCoordinates(*(_oriCoordinates + nextIndex), resultCoordinate); 224 | double speed = tempSpeed; 225 | 226 | // save 'nextIndex' 227 | currentIndex = nextIndex; 228 | 229 | // sleep for 'self.timeInverval' 230 | [NSThread sleepForTimeInterval:self.timeInverval]; 231 | 232 | // notify delegate location update 233 | if (self.delegate) 234 | { 235 | CLLocation *newLocation = [[CLLocation alloc] initWithCoordinate:resultCoordinate 236 | altitude:30.f 237 | horizontalAccuracy:10.f 238 | verticalAccuracy:10.f 239 | course:course 240 | speed:speed 241 | timestamp:[NSDate date]]; 242 | 243 | [self.delegate gpsEmulatorUpdateLocation:newLocation]; 244 | } 245 | } 246 | 247 | self.isSimulating = NO; 248 | } 249 | 250 | - (CLLocationCoordinate2D)findCoordinateFromIndex:(unsigned long)startIndex 251 | afterDistance:(double)distance 252 | resultLocateIndex:(unsigned long *)locateIndex 253 | resultRedundantDistance:(double *)redundantDistance 254 | { 255 | startIndex = MAX(0, startIndex); 256 | double totalDistance = distance; 257 | 258 | // if 'totalDistance <= 0', return coordinate at 'startIndex' directly 259 | if (totalDistance <= 0) 260 | { 261 | *locateIndex = startIndex; 262 | *redundantDistance = 0.f; 263 | 264 | CLLocationCoordinate2D reVal = *(_oriCoordinates + startIndex); 265 | return CLLocationCoordinate2DMake(reVal.latitude, reVal.longitude); 266 | } 267 | 268 | CLLocationCoordinate2D resultCoordiante = *(_oriCoordinates + startIndex); 269 | double resultDistance = 0; 270 | 271 | unsigned long i = startIndex; 272 | for (; i < _count-1; i++) 273 | { 274 | double dis = distanceBetweenCoordinates(*(_oriCoordinates + i), *(_oriCoordinates + i + 1)); 275 | if (totalDistance <= dis) 276 | { 277 | resultDistance = totalDistance; 278 | resultCoordiante = coordinateAtRateOfCoordinates(*(_oriCoordinates + i), *(_oriCoordinates + i + 1), (totalDistance / dis)); 279 | break; 280 | } 281 | else 282 | { 283 | totalDistance -= dis; 284 | } 285 | } 286 | 287 | if (i >= _count-1) 288 | { 289 | // reach the end of coordiante list, return the last coordiante 290 | *locateIndex = _count-1; 291 | *redundantDistance = 0.f; 292 | 293 | CLLocationCoordinate2D reVal = *(_oriCoordinates + _count - 1); 294 | return CLLocationCoordinate2DMake(reVal.latitude, reVal.longitude); 295 | } 296 | else 297 | { 298 | // destination coordiante locate between 'i' and 'i+1', return the new coordiante named 'resultCoordiante' 299 | *locateIndex = i; 300 | *redundantDistance = resultDistance; 301 | 302 | return resultCoordiante; 303 | } 304 | } 305 | 306 | @end 307 | -------------------------------------------------------------------------------- /ATCustomizeViews/ATSandboxViewerView/ATSandboxViewerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATSandboxViewerView.h 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/16. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "GCDWebUploader.h" 11 | #import "ATAssistiveTools.h" 12 | 13 | FOUNDATION_EXTERN NSString * const ATSandboxViewerViewSelecteFileNotification; 14 | FOUNDATION_EXTERN NSString * const ATSandboxViewerViewFilePathKey; 15 | 16 | @interface ATSandboxViewerView : UIView 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ATCustomizeViews/ATSandboxViewerView/ATSandboxViewerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATSandboxViewerView.m 3 | // ATAssistiveTools 4 | // 5 | // Created by liubo on 2017/7/16. 6 | // Copyright © 2017年 devliubo. All rights reserved. 7 | // 8 | 9 | #import "ATSandboxViewerView.h" 10 | 11 | NSString * const ATSandboxViewerViewSelecteFileNotification = @"ATSandboxViewerViewSelecteFileNotification"; 12 | NSString * const ATSandboxViewerViewFilePathKey = @"ATSandboxViewerViewFilePath"; 13 | 14 | #pragma mark - ATFileItem 15 | 16 | typedef NS_ENUM(NSUInteger, ATFileItemType) { 17 | ATFileItemBack, 18 | ATFileItemFile, 19 | ATFileItemDirectory, 20 | }; 21 | 22 | @interface ATFileItem : NSObject 23 | 24 | @property (nonatomic, copy) NSString *itemName; 25 | @property (nonatomic, copy) NSString *itemPath; 26 | @property (nonatomic, assign) ATFileItemType itemType; 27 | 28 | @end 29 | 30 | @implementation ATFileItem 31 | @end 32 | 33 | #pragma mark - ATSandboxViewerView 34 | 35 | @interface ATSandboxViewerView () 36 | 37 | @property (nonatomic, strong) UIButton *webServerButton; 38 | @property (nonatomic, strong) UILabel *webServerLabel; 39 | @property (nonatomic, strong) UITableView *mainTableView; 40 | 41 | @property (nonatomic, strong) NSString *currentPath; 42 | @property (nonatomic, strong) NSArray *allItems; 43 | 44 | @property (nonatomic, strong) GCDWebUploader *webUploader; 45 | 46 | @end 47 | 48 | @implementation ATSandboxViewerView 49 | 50 | #pragma mark - Life Cycle 51 | 52 | - (instancetype)initWithFrame:(CGRect)frame 53 | { 54 | if (self = [super initWithFrame:frame]) 55 | { 56 | [self buildSandboxViewerView]; 57 | } 58 | return self; 59 | } 60 | 61 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 62 | { 63 | if (self = [super initWithCoder:aDecoder]) 64 | { 65 | [self buildSandboxViewerView]; 66 | } 67 | return self; 68 | } 69 | 70 | - (void)buildSandboxViewerView 71 | { 72 | self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 73 | 74 | [self initProperties]; 75 | 76 | [self initSubviews]; 77 | } 78 | 79 | - (void)initProperties 80 | { 81 | _currentPath = NSHomeDirectory(); 82 | _allItems = @[]; 83 | } 84 | 85 | - (void)initSubviews 86 | { 87 | self.webServerButton = [UIButton buttonWithType:UIButtonTypeCustom]; 88 | self.webServerButton.translatesAutoresizingMaskIntoConstraints = NO; 89 | self.webServerButton.layer.borderColor = [UIColor blueColor].CGColor; 90 | self.webServerButton.layer.borderWidth = 0.5; 91 | self.webServerButton.titleLabel.font = [UIFont systemFontOfSize:15.0]; 92 | [self.webServerButton setTitle:@"Start" forState:UIControlStateNormal]; 93 | [self.webServerButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 94 | [self.webServerButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted]; 95 | [self.webServerButton addTarget:self action:@selector(webServerButtonAction:) forControlEvents:UIControlEventTouchUpInside]; 96 | 97 | [self addSubview:self.webServerButton]; 98 | 99 | self.webServerLabel = [[UILabel alloc] init]; 100 | self.webServerLabel.translatesAutoresizingMaskIntoConstraints = NO; 101 | self.webServerLabel.font = [UIFont systemFontOfSize:14]; 102 | self.webServerLabel.textAlignment = NSTextAlignmentLeft; 103 | self.webServerLabel.adjustsFontSizeToFitWidth = YES; 104 | self.webServerLabel.text = @"Web Server Stoped"; 105 | 106 | [self addSubview:self.webServerLabel]; 107 | 108 | self.mainTableView = [[UITableView alloc] init]; 109 | self.mainTableView.translatesAutoresizingMaskIntoConstraints = NO; 110 | self.mainTableView.delegate = self; 111 | self.mainTableView.dataSource = self; 112 | self.mainTableView.backgroundColor = [UIColor whiteColor]; 113 | self.mainTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; 114 | 115 | [self addSubview:self.mainTableView]; 116 | 117 | NSDictionary *views = NSDictionaryOfVariableBindings(_webServerButton, _webServerLabel, _mainTableView); 118 | 119 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[_webServerButton(==width)]-10-[_webServerLabel]-10-|" options:0 metrics:@{@"width":@(80)} views:views]]; 120 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[_webServerButton(==height)]" options:0 metrics:@{@"height":@(20)} views:views]]; 121 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[_webServerLabel(==height)]" options:0 metrics:@{@"height":@(20)} views:views]]; 122 | 123 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[_mainTableView]-5-|" options:0 metrics:nil views:views]]; 124 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_webServerButton]-10-[_mainTableView]-10-|" options:0 metrics:nil views:views]]; 125 | } 126 | 127 | #pragma mark - ATCustomViewProtocol 128 | 129 | - (void)customViewDidAppear 130 | { 131 | [self loadAllItemInPath:self.currentPath]; 132 | } 133 | 134 | #pragma mark - Methods 135 | 136 | - (void)loadAllItemInPath:(NSString *)path 137 | { 138 | NSFileManager *fileManager = [NSFileManager defaultManager]; 139 | NSMutableArray *resultItems = [[NSMutableArray alloc] init]; 140 | 141 | if (path == nil || path.length <= 0 || [path isEqualToString:NSHomeDirectory()]) 142 | { 143 | path = NSHomeDirectory(); 144 | } 145 | else 146 | { 147 | // add parents item 148 | ATFileItem *parentsItem = [[ATFileItem alloc] init]; 149 | parentsItem.itemName = @"< ../"; 150 | parentsItem.itemPath = [path stringByDeletingLastPathComponent]; 151 | parentsItem.itemType = ATFileItemBack; 152 | 153 | [resultItems addObject:parentsItem]; 154 | } 155 | 156 | NSError *error = nil; 157 | NSArray *allFileNames = [fileManager contentsOfDirectoryAtPath:path error:&error]; 158 | if (error != nil) 159 | { 160 | NSLog(@"Load Path Failed:{%@ - %@}", path, error.description); 161 | return; 162 | } 163 | 164 | for (NSString *aFileName in allFileNames) 165 | { 166 | // ignore hidden files (files that begin with a period character) 167 | if ([[aFileName lastPathComponent] hasPrefix:@"."]) 168 | { 169 | continue; 170 | } 171 | 172 | BOOL isDirectory = false; 173 | NSString* fullPath = [path stringByAppendingPathComponent:aFileName]; 174 | [fileManager fileExistsAtPath:fullPath isDirectory:&isDirectory]; 175 | 176 | ATFileItem *aItem = [[ATFileItem alloc] init]; 177 | aItem.itemPath = fullPath; 178 | if (isDirectory) 179 | { 180 | // @"U0001F4C1" -> 📁 181 | aItem.itemName = [NSString stringWithFormat:@"\U0001F4C1 %@", aFileName]; 182 | aItem.itemType = ATFileItemDirectory; 183 | } 184 | else 185 | { 186 | // @"\U0001F4C4" -> 📄 187 | aItem.itemName = [NSString stringWithFormat:@"\U0001F4C4 %@", aFileName]; 188 | aItem.itemType = ATFileItemFile; 189 | } 190 | 191 | [resultItems addObject:aItem]; 192 | } 193 | 194 | self.allItems = resultItems; 195 | self.currentPath = path; 196 | 197 | [self.mainTableView reloadData]; 198 | } 199 | 200 | #pragma mark - Button Action 201 | 202 | - (void)webServerButtonAction:(UIButton *)sender 203 | { 204 | if (self.webUploader == nil) 205 | { 206 | NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 207 | self.webUploader = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath]; 208 | self.webUploader.delegate = self; 209 | [self.webUploader start]; 210 | 211 | NSLog(@"Visit %@ in your web browser", self.webUploader.serverURL); 212 | 213 | [self.webServerButton setTitle:@"Stop" forState:UIControlStateNormal]; 214 | self.webServerLabel.text = self.webUploader.serverURL.absoluteString; 215 | } 216 | else 217 | { 218 | [self.webUploader stop]; 219 | self.webUploader.delegate = nil; 220 | self.webUploader = nil; 221 | 222 | [self.webServerButton setTitle:@"Start" forState:UIControlStateNormal]; 223 | self.webServerLabel.text = @"Web Server Stoped"; 224 | } 225 | } 226 | 227 | - (void)refreshItemsIfNeedForPath:(NSString *)path 228 | { 229 | if ([path isEqualToString:self.currentPath]) 230 | { 231 | [self loadAllItemInPath:self.currentPath]; 232 | } 233 | } 234 | 235 | #pragma mark - GCDWebUploaderDelegate 236 | 237 | - (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path 238 | { 239 | [self refreshItemsIfNeedForPath:[path stringByDeletingLastPathComponent]]; 240 | } 241 | 242 | - (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath 243 | { 244 | [self refreshItemsIfNeedForPath:[fromPath stringByDeletingLastPathComponent]]; 245 | [self refreshItemsIfNeedForPath:[toPath stringByDeletingLastPathComponent]]; 246 | } 247 | 248 | - (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path 249 | { 250 | if ([self.currentPath containsString:path]) 251 | { 252 | [self loadAllItemInPath:[path stringByDeletingLastPathComponent]]; 253 | } 254 | else 255 | { 256 | [self refreshItemsIfNeedForPath:[path stringByDeletingLastPathComponent]]; 257 | } 258 | } 259 | 260 | - (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path 261 | { 262 | [self refreshItemsIfNeedForPath:[path stringByDeletingLastPathComponent]]; 263 | } 264 | 265 | #pragma mark - UITableViewDelegate 266 | 267 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 268 | { 269 | return self.allItems.count; 270 | } 271 | 272 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 273 | { 274 | if (indexPath.row >= self.allItems.count) 275 | { 276 | return nil; 277 | } 278 | 279 | static NSString *cellIdentifier = @"ATSandboxViewerCellIdentifier"; 280 | UITableViewCell *aCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 281 | if (aCell == nil) 282 | { 283 | aCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 284 | aCell.selectionStyle = UITableViewCellSelectionStyleNone; 285 | } 286 | 287 | ATFileItem *aItem = [self.allItems objectAtIndex:indexPath.row]; 288 | 289 | aCell.textLabel.text = aItem.itemName; 290 | if (aItem.itemType == ATFileItemBack) 291 | { 292 | aCell.accessoryType = UITableViewCellAccessoryNone; 293 | } 294 | else if (aItem.itemType == ATFileItemFile) 295 | { 296 | aCell.accessoryType = UITableViewCellAccessoryDetailButton; 297 | } 298 | else if (aItem.itemType == ATFileItemDirectory) 299 | { 300 | aCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 301 | } 302 | else 303 | { 304 | aCell.accessoryType = UITableViewCellAccessoryNone; 305 | } 306 | 307 | return aCell; 308 | } 309 | 310 | #pragma mark - UITableViewDataSource 311 | 312 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 313 | { 314 | [tableView deselectRowAtIndexPath:indexPath animated:NO]; 315 | 316 | if (indexPath.row >= self.allItems.count) 317 | { 318 | return; 319 | } 320 | 321 | ATFileItem *aItem = [self.allItems objectAtIndex:indexPath.row]; 322 | if (aItem.itemType == ATFileItemBack) 323 | { 324 | [self loadAllItemInPath:[self.currentPath stringByDeletingLastPathComponent]]; 325 | } 326 | else if (aItem.itemType == ATFileItemFile) 327 | { 328 | [self handleFileAtPath:aItem.itemPath]; 329 | } 330 | else if (aItem.itemType == ATFileItemDirectory) 331 | { 332 | [self loadAllItemInPath:aItem.itemPath]; 333 | } 334 | else 335 | { 336 | NSLog(@"Item Type Error!"); 337 | } 338 | } 339 | 340 | #pragma mark - Handle File 341 | 342 | - (void)handleFileAtPath:(NSString*)path 343 | { 344 | // send notification 345 | [[NSNotificationCenter defaultCenter] postNotificationName:ATSandboxViewerViewSelecteFileNotification object:nil userInfo:@{ATSandboxViewerViewFilePathKey:path}]; 346 | 347 | // present shared menu 348 | NSArray *objectsToShare = @[[NSURL fileURLWithPath:path]]; 349 | 350 | UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil]; 351 | NSArray *excludedActivities = @[UIActivityTypePostToTwitter, UIActivityTypePostToFacebook, 352 | UIActivityTypePostToWeibo, 353 | UIActivityTypeMessage, UIActivityTypeMail, 354 | UIActivityTypePrint, UIActivityTypeCopyToPasteboard, 355 | UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll, 356 | UIActivityTypeAddToReadingList, UIActivityTypePostToFlickr, 357 | UIActivityTypePostToVimeo, UIActivityTypePostToTencentWeibo]; 358 | controller.excludedActivityTypes = excludedActivities; 359 | 360 | if ([(NSString *)[UIDevice currentDevice].model hasPrefix:@"iPad"]) 361 | { 362 | controller.popoverPresentationController.sourceView = self; 363 | controller.popoverPresentationController.sourceRect = CGRectMake([UIScreen mainScreen].bounds.size.width * 0.5, [UIScreen mainScreen].bounds.size.height, 10, 10); 364 | } 365 | 366 | [[[[ATAssistiveTools sharedInstance] mainWindow] rootViewController] presentViewController:controller animated:YES completion:nil]; 367 | } 368 | 369 | @end 370 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 devliubo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | 2 | target 'ATAssistiveToolsDemo' do 3 | 4 | pod 'GCDWebServer/WebUploader' 5 | 6 | end 7 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - GCDWebServer/Core (3.4) 3 | - GCDWebServer/WebUploader (3.4): 4 | - GCDWebServer/WebUploader/Core (= 3.4) 5 | - GCDWebServer/WebUploader/Core (3.4): 6 | - GCDWebServer/Core 7 | 8 | DEPENDENCIES: 9 | - GCDWebServer/WebUploader 10 | 11 | SPEC CHECKSUMS: 12 | GCDWebServer: 20d53c3181b3c78d865e93e326faf506306f5338 13 | 14 | PODFILE CHECKSUM: c144712fcf8b1076627406c921f6f7009c1d7f4b 15 | 16 | COCOAPODS: 1.3.1 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ATAssistiveTools 2 | 3 | ATAssistiveTools 是一个辅助调试工具容器,包含收缩和扩展两个状态。 4 | * 扩展界面支持在任意时刻,加载任意的自定义界面,通过实现 ATCustomViewProtocol 协议,支持自定义界面获取 appear、 disappear、 shrink、 expand 四种状态的 will 和 did 事件,以便进行特定逻辑处理。 5 | * 收缩界面外观显示效果仿照 iOS 系统的 AssistiveTouch 。 6 | 7 | ## ATCustomizeViews 8 | 9 | ATCustomizeViews 目录中包含了以下的自定义界面,一方面可以作为使用示例,另一方面也可以直接使用这些自定义界面的功能: 10 | 11 | * ATFakeLocationView 虚拟定位 12 | * ATSandboxViewerView 沙盒目录浏览 13 | * ATDeviceLogsView 设备日志查看 14 | * ATGPSEmulatorView 对指定的坐标串按特定速度进行模拟 15 | 16 | ## Installation 17 | 18 | ### Manual 19 | 20 | 将ATAssistiveTools目录下载导入到工程中即可。 21 | 22 | 如果需要ATCustomizeViews,则需要下载ATCustomizeViews目录,导入到工程即可。 23 | 24 | ## Usage 25 | 26 | ### 使用 ATAssistiveTools 27 | 28 | * 导入头文件 29 | 30 | ```objective-c 31 | #import "ATAssistiveTools.h" 32 | ``` 33 | 34 | * 显示 ATAssistiveTools 35 | 36 | ``` 37 | [[ATAssistiveTools sharedInstance] show]; 38 | ``` 39 | 40 | ### 使用ATCustomizeViews 41 | 42 | 在显示 ATAssistiveTools 的基础上: 43 | 44 | * 导入头文件 45 | 46 | ```objective-c 47 | #import "ATDeviceLogsView.h" 48 | #import "ATFakeLocationView.h" 49 | #import "ATSandboxViewerView.h" 50 | #import "ATGPSEmulatorView.h" 51 | ``` 52 | 53 | * 加载 ATCustomizeViews 54 | 55 | ``` 56 | // add fake location view 57 | ATFakeLocationView *simLocView = [[ATFakeLocationView alloc] init]; 58 | [[ATAssistiveTools sharedInstance] addCustomView:simLocView forTitle:@"FakeLocation"]; 59 | 60 | // add sandbox viewer view 61 | ATSandboxViewerView *sandboxView = [[ATSandboxViewerView alloc] init]; 62 | [[ATAssistiveTools sharedInstance] addCustomView:sandboxView forTitle:@"SandboxViewer"]; 63 | 64 | // add device log view 65 | ATDeviceLogsView *logsView = [[ATDeviceLogsView alloc] init]; 66 | [[ATAssistiveTools sharedInstance] addCustomView:logsView forTitle:@"DeviceLog"]; 67 | 68 | // add GPS emulator view 69 | ATGPSEmulatorView *emulatorView = [[ATGPSEmulatorView alloc] init]; 70 | [[ATAssistiveTools sharedInstance] addCustomView:emulatorView forTitle:@"GPSEmulator"]; 71 | ``` 72 | 73 | ### 自定义界面 74 | 75 | 如果想自定义界面,需要让custom view实现 ATCustomViewProtocol 协议: 76 | 77 | * 导入头文件 78 | 79 | ```objective-c 80 | #import "ATCustomViewProtocol.h" 81 | ``` 82 | 83 | * 使界面实现 ATCustomViewProtocol 协议 84 | 85 | ``` 86 | @interface ACustomView : UIView 87 | @end 88 | ``` 89 | 90 | ## To Do 91 | 92 | * 补全ATCustomizeViews的介绍说明 93 | * 收缩状态支持显示自定义的届满 ,支持显示自定义的标题 94 | * 增加生成 framework 的 target ,打包成 static library 95 | * ~~增加 podspec 文件~~ 支持使用 cocoapods 安装 96 | 97 | ## Notice 98 | 99 | 不建议用于性能相关的调试,虽然 ATAssistiveTools 本身对性能影响很小,但加载的 custom view 有可能对内存、CPU以及网络造成过大的占用,会导致结论的不严谨和不准确。 100 | 101 | ## License 102 | 103 | ATAssistiveTools is released under the [MIT License](https://raw.githubusercontent.com/devliubo/ATAssistiveTools/master/LICENSE). 104 | --------------------------------------------------------------------------------