123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- //
- // FHSViewController.m
- // FLEX
- //
- // Created by Tanner Bennett on 1/6/20.
- // Copyright © 2020 FLEX Team. All rights reserved.
- //
- #import "FHSViewController.h"
- #import "FHSSnapshotView.h"
- #import "FLEXHierarchyViewController.h"
- #import "FLEXColor.h"
- #import "FLEXAlert.h"
- #import "FLEXWindow.h"
- #import "FLEXResources.h"
- #import "NSArray+FLEX.h"
- #import "UIBarButtonItem+FLEX.h"
- BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
- @interface FHSViewController () <FHSSnapshotViewDelegate>
- /// An array of only the target views whose hierarchies
- /// we wish to snapshot, not every view in the snapshot.
- @property (nonatomic, readonly) NSArray<UIView *> *targetViews;
- @property (nonatomic, readonly) NSArray<FHSView *> *views;
- @property (nonatomic ) NSArray<FHSViewSnapshot *> *snapshots;
- @property (nonatomic, ) FHSSnapshotView *snapshotView;
- @property (nonatomic, readonly) UIView *containerView;
- @property (nonatomic, readonly) NSArray<UIView *> *viewsAtTap;
- @property (nonatomic, readonly) NSMutableSet<Class> *forceHideHeaders;
- @end
- @implementation FHSViewController
- @synthesize views = _views;
- @synthesize snapshotView = _snapshotView;
- #pragma mark - Initialization
- + (instancetype)snapshotWindows:(NSArray<UIWindow *> *)windows {
- return [[self alloc] initWithViews:windows viewsAtTap:nil selectedView:nil];
- }
- + (instancetype)snapshotView:(UIView *)view {
- return [[self alloc] initWithViews:@[view] viewsAtTap:nil selectedView:nil];
- }
- + (instancetype)snapshotViewsAtTap:(NSArray<UIView *> *)viewsAtTap selectedView:(UIView *)view {
- NSParameterAssert(viewsAtTap.count);
- NSParameterAssert(view.window);
- return [[self alloc] initWithViews:@[view.window] viewsAtTap:viewsAtTap selectedView:view];
- }
- - (id)initWithViews:(NSArray<UIView *> *)views
- viewsAtTap:(NSArray<UIView *> *)viewsAtTap
- selectedView:(UIView *)view {
- NSParameterAssert(views.count);
- self = [super init];
- if (self) {
- _forceHideHeaders = [NSMutableSet setWithObject:NSClassFromString(@"_UITableViewCellSeparatorView")];
- _selectedView = view;
- _viewsAtTap = viewsAtTap;
- if (!viewsAtTap && kFHSViewControllerExcludeFLEXWindows) {
- Class flexwindow = [FLEXWindow class];
- views = [views flex_filtered:^BOOL(UIView *view, NSUInteger idx) {
- return [view class] != flexwindow;
- }];
- }
- _targetViews = views;
- _views = [views flex_mapped:^id(UIView *view, NSUInteger idx) {
- BOOL isScrollView = [view.superview isKindOfClass:[UIScrollView class]];
- return [FHSView forView:view isInScrollView:isScrollView];
- }];
- }
- return self;
- }
- - (void)refreshSnapshotView {
- // Alert view to block interaction while we load everything
- UIAlertController *loading = [FLEXAlert makeAlert:^(FLEXAlert *make) {
- make.title(@"Please Wait").message(@"Generating snapshot…");
- }];
- [self presentViewController:loading animated:YES completion:^{
- self.snapshots = [self.views flex_mapped:^id(FHSView *view, NSUInteger idx) {
- return [FHSViewSnapshot snapshotWithView:view];
- }];
- FHSSnapshotView *newSnapshotView = [FHSSnapshotView delegate:self];
- // This work is highly intensive so we do it on a background thread first
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
- // Setting the snapshots computes lots of SCNNodes, takes several seconds
- newSnapshotView.snapshots = self.snapshots;
- // After we finish generating all the model objects and scene nodes, display the view
- dispatch_async(dispatch_get_main_queue(), ^{
- // Dismiss alert
- [loading dismissViewControllerAnimated:YES completion:nil];
- self.snapshotView = newSnapshotView;
- });
- });
- }];
- }
- #pragma mark - View Controller Lifecycle
- - (void)loadView {
- [super loadView];
- self.view.backgroundColor = FLEXColor.primaryBackgroundColor;
- }
- - (void)viewDidLoad {
- [super viewDidLoad];
- // Initialize back bar button item for 3D view to look like a button
- #if !TARGET_OS_TV
- self.navigationItem.hidesBackButton = YES;
- #endif
- self.navigationItem.leftBarButtonItem = [UIBarButtonItem
- flex_itemWithImage:FLEXResources.toggle2DIcon
- target:self.navigationController
- action:@selector(toggleHierarchyMode)
- ];
- }
- - (void)viewDidAppear:(BOOL)animated {
- [super viewDidAppear:animated];
- if (!_snapshotView) {
- [self refreshSnapshotView];
- }
- }
- #pragma mark - Public
- - (void)setSelectedView:(UIView *)view {
- _selectedView = view;
- self.snapshotView.selectedView = view ? [self snapshotForView:view] : nil;
- }
- #pragma mark - Private
- #pragma mark Properties
- - (FHSSnapshotView *)snapshotView {
- return self.isViewLoaded ? _snapshotView : nil;
- }
- - (void)setSnapshotView:(FHSSnapshotView *)snapshotView {
- NSParameterAssert(snapshotView);
- _snapshotView = snapshotView;
- #if !TARGET_OS_TV
- // Initialize our toolbar items
- self.toolbarItems = @[
- [UIBarButtonItem flex_itemWithCustomView:snapshotView.spacingSlider],
- UIBarButtonItem.flex_flexibleSpace,
- [UIBarButtonItem
- flex_itemWithImage:FLEXResources.moreIcon
- target:self action:@selector(didPressOptionsButton:)
- ],
- UIBarButtonItem.flex_flexibleSpace,
- [UIBarButtonItem flex_itemWithCustomView:snapshotView.depthSlider]
- ];
- [self resizeToolbarItems:self.view.frame.size];
- #endif
- // If we have views-at-tap, dim the other views
- [snapshotView emphasizeViews:self.viewsAtTap];
- // Set the selected view, if any
- snapshotView.selectedView = [self snapshotForView:self.selectedView];
- snapshotView.headerExclusions = self.forceHideHeaders.allObjects;
- [snapshotView setNeedsLayout];
- // Remove old snapshot, if any, and add the new one
- [_snapshotView removeFromSuperview];
- snapshotView.frame = self.containerView.bounds;
- [self.containerView addSubview:snapshotView];
- }
- - (UIView *)containerView {
- return self.view;
- }
- #pragma mark Helper
- - (FHSViewSnapshot *)snapshotForView:(UIView *)view {
- if (!view || !self.snapshots.count) return nil;
- for (FHSViewSnapshot *snapshot in self.snapshots) {
- FHSViewSnapshot *found = [snapshot snapshotForView:view];
- if (found) {
- return found;
- }
- }
- // Error: we have snapshots but the view we requested is not in one
- @throw NSInternalInconsistencyException;
- return nil;
- }
- #pragma mark Events
- - (void)didPressOptionsButton:(UIBarButtonItem *)sender {
- [FLEXAlert makeSheet:^(FLEXAlert *make) {
- if (self.selectedView) {
- make.button(@"Hide selected view").handler(^(NSArray<NSString *> *strings) {
- [self.snapshotView hideView:[self snapshotForView:self.selectedView]];
- });
- make.button(@"Hide headers for views like this").handler(^(NSArray<NSString *> *strings) {
- Class cls = [self.selectedView class];
- if (![self.forceHideHeaders containsObject:cls]) {
- [self.forceHideHeaders addObject:[self.selectedView class]];
- self.snapshotView.headerExclusions = self.forceHideHeaders.allObjects;
- }
- });
- }
- make.title(@"Options");
- make.button(@"Toggle headers").handler(^(NSArray<NSString *> *strings) {
- [self.snapshotView toggleShowHeaders];
- });
- make.button(@"Toggle outlines").handler(^(NSArray<NSString *> *strings) {
- [self.snapshotView toggleShowBorders];
- });
- make.button(@"Cancel").cancelStyle();
- } showFrom:self source:sender];
- }
- - (void)resizeToolbarItems:(CGSize)viewSize {
- #if !TARGET_OS_TV
- CGFloat sliderHeights = self.snapshotView.spacingSlider.bounds.size.height;
- CGFloat sliderWidths = viewSize.width / 3.f;
- CGRect frame = CGRectMake(0, 0, sliderWidths, sliderHeights);
- self.snapshotView.spacingSlider.frame = frame;
- self.snapshotView.depthSlider.frame = frame;
- [self.navigationController.toolbar setNeedsLayout];
- #endif
- }
- - (void)viewWillTransitionToSize:(CGSize)size
- withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
- [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
- [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
- [self resizeToolbarItems:self.view.frame.size];
- } completion:nil];
- }
- #pragma mark FHSSnapshotViewDelegate
- - (void)didDeselectView:(FHSViewSnapshot *)snapshot {
- // Our setter would also call the setter for the snapshot view,
- // which we don't need to do here since it is already selected
- _selectedView = nil;
- }
- - (void)didLongPressView:(FHSViewSnapshot *)snapshot {
- }
- - (void)didSelectView:(FHSViewSnapshot *)snapshot {
- // Our setter would also call the setter for the snapshot view,
- // which we don't need to do here since it is already selected
- _selectedView = snapshot.view.view;
- }
- @end
|