mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-08-24 11:28:52 -04:00
195 lines
6.0 KiB
Objective-C
195 lines
6.0 KiB
Objective-C
//
|
|
// FHSView.m
|
|
// FLEX
|
|
//
|
|
// Created by Tanner Bennett on 1/6/20.
|
|
//
|
|
|
|
#import "FHSView.h"
|
|
#import "FLEXUtility.h"
|
|
#import "NSArray+FLEX.h"
|
|
|
|
@interface FHSView (Snapshotting)
|
|
+ (UIImage *)_snapshotView:(UIView *)view;
|
|
@end
|
|
|
|
@implementation FHSView
|
|
|
|
+ (instancetype)forView:(UIView *)view isInScrollView:(BOOL)inScrollView {
|
|
return [[self alloc] initWithView:view isInScrollView:inScrollView];
|
|
}
|
|
|
|
- (id)initWithView:(UIView *)view isInScrollView:(BOOL)inScrollView {
|
|
self = [super init];
|
|
if (self) {
|
|
_view = view;
|
|
_inScrollView = inScrollView;
|
|
_identifier = NSUUID.UUID.UUIDString;
|
|
|
|
UIViewController *controller = [FLEXUtility viewControllerForView:view];
|
|
if (controller) {
|
|
_important = YES;
|
|
_title = [NSString stringWithFormat:
|
|
@"%@ (for %@)",
|
|
NSStringFromClass([controller class]),
|
|
NSStringFromClass([view class])
|
|
];
|
|
} else {
|
|
_title = NSStringFromClass([view class]);
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (CGRect)frame {
|
|
if (_inScrollView) {
|
|
CGPoint offset = [(UIScrollView *)self.view.superview contentOffset];
|
|
return CGRectOffset(self.view.frame, -offset.x, -offset.y);
|
|
} else {
|
|
return self.view.frame;
|
|
}
|
|
}
|
|
|
|
- (BOOL)hidden {
|
|
return self.view.isHidden;
|
|
}
|
|
|
|
- (UIImage *)snapshotImage {
|
|
return [FHSView _snapshotView:self.view];
|
|
}
|
|
|
|
- (NSArray<FHSView *> *)children {
|
|
BOOL isScrollView = [self.view isKindOfClass:[UIScrollView class]];
|
|
return [self.view.subviews flex_mapped:^id(UIView *subview, NSUInteger idx) {
|
|
return [FHSView forView:subview isInScrollView:isScrollView];
|
|
}];
|
|
}
|
|
|
|
- (NSString *)summary {
|
|
CGRect f = self.frame;
|
|
return [NSString stringWithFormat:
|
|
@"%@ (%.1f, %.1f, %.1f, %.1f)",
|
|
NSStringFromClass([self.view class]),
|
|
f.origin.x, f.origin.y, f.size.width, f.size.height
|
|
];
|
|
}
|
|
|
|
- (NSString *)description{
|
|
return self.view.description;
|
|
}
|
|
|
|
- (id)ifImportant:(id)importantAttr ifNormal:(id)normalAttr {
|
|
return self.important ? importantAttr : normalAttr;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation FHSView (Snapshotting)
|
|
|
|
+ (UIImage *)drawView:(UIView *)view {
|
|
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0);
|
|
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
|
|
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
|
UIGraphicsEndImageContext();
|
|
return image;
|
|
}
|
|
|
|
/// Recursively hides all views that may be obscuring the given view and collects them
|
|
/// in the given array. You should unhide them all when you are done.
|
|
+ (BOOL)_hideViewsCoveringView:(UIView *)view
|
|
root:(UIView *)rootView
|
|
hiddenViews:(NSMutableArray<UIView *> *)hiddenViews {
|
|
// Stop when we reach this view
|
|
if (view == rootView) {
|
|
return YES;
|
|
}
|
|
|
|
for (UIView *subview in rootView.subviews.reverseObjectEnumerator.allObjects) {
|
|
if ([self _hideViewsCoveringView:view root:subview hiddenViews:hiddenViews]) {
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
if (!rootView.isHidden) {
|
|
rootView.hidden = YES;
|
|
[hiddenViews addObject:rootView];
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
|
|
/// Recursively hides all views that may be obscuring the given view and collects them
|
|
/// in the given array. You should unhide them all when you are done.
|
|
+ (void)hideViewsCoveringView:(UIView *)view doWhileHidden:(void(^)(void))block {
|
|
NSMutableArray *viewsToUnhide = [NSMutableArray new];
|
|
if ([self _hideViewsCoveringView:view root:view.window hiddenViews:viewsToUnhide]) {
|
|
block();
|
|
}
|
|
|
|
for (UIView *v in viewsToUnhide) {
|
|
v.hidden = NO;
|
|
}
|
|
}
|
|
|
|
+ (UIImage *)_snapshotVisualEffectBackdropView:(UIView *)view {
|
|
NSParameterAssert(view.window);
|
|
|
|
// UIVisualEffectView is a special case that cannot be snapshotted
|
|
// the same way as any other view. From Apple docs:
|
|
//
|
|
// Many effects require support from the window that hosts the
|
|
// UIVisualEffectView. Attempting to take a snapshot of only the
|
|
// UIVisualEffectView will result in a snapshot that does not
|
|
// contain the effect. To take a snapshot of a view hierarchy
|
|
// that contains a UIVisualEffectView, you must take a snapshot
|
|
// of the entire UIWindow or UIScreen that contains it.
|
|
//
|
|
// To snapshot this view, we traverse the view hierarchy starting
|
|
// from the window and hide any views that are on top of the
|
|
// _UIVisualEffectBackdropView so that it is visible in a snapshot
|
|
// of the window. We then take a snapshot of the window and crop
|
|
// it to the part that contains the backdrop view. This appears to
|
|
// be the same technique that Xcode's own view debugger uses to
|
|
// snapshot visual effect views.
|
|
__block UIImage *image = nil;
|
|
[self hideViewsCoveringView:view doWhileHidden:^{
|
|
image = [self drawView:view];
|
|
CGRect cropRect = [view.window convertRect:view.bounds fromView:view];
|
|
image = [UIImage imageWithCGImage:CGImageCreateWithImageInRect(image.CGImage, cropRect)];
|
|
}];
|
|
|
|
return image;
|
|
}
|
|
|
|
+ (UIImage *)_snapshotView:(UIView *)view {
|
|
UIView *superview = view.superview;
|
|
// Is this view inside a UIVisualEffectView?
|
|
if ([superview isKindOfClass:[UIVisualEffectView class]]) {
|
|
// Is it (probably) the "backdrop" view of this UIVisualEffectView?
|
|
if (superview.subviews.firstObject == view) {
|
|
return [self _snapshotVisualEffectBackdropView:view];
|
|
}
|
|
}
|
|
|
|
// Hide the view's subviews before we snapshot it
|
|
NSMutableIndexSet *toUnhide = [NSMutableIndexSet new];
|
|
[view.subviews flex_forEach:^(UIView *v, NSUInteger idx) {
|
|
if (!v.isHidden) {
|
|
v.hidden = YES;
|
|
[toUnhide addIndex:idx];
|
|
}
|
|
}];
|
|
|
|
// Snapshot the view, then unhide the previously-unhidden views
|
|
UIImage *snapshot = [self drawView:view];
|
|
for (UIView *v in [view.subviews objectsAtIndexes:toUnhide]) {
|
|
v.hidden = NO;
|
|
}
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
@end
|