Просмотр исходного кода

Fix carousel + search bar behavior for pre-iOS 11

Tanner Bennett лет назад: 6
Родитель
Сommit
5d27613b38

+ 157 - 41
Classes/Core/Controllers/FLEXTableViewController.m

@@ -25,9 +25,12 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
 @property (nonatomic) NSTimer *debounceTimer;
 @property (nonatomic) BOOL didInitiallyRevealSearchBar;
 @property (nonatomic) UITableViewStyle style;
+
+@property (nonatomic, readonly) UIView *tableHeaderViewContainer;
 @end
 
 @implementation FLEXTableViewController
+@synthesize tableHeaderViewContainer = _tableHeaderViewContainer;
 @synthesize automaticallyShowsSearchBarCancelButton = _automaticallyShowsSearchBarCancelButton;
 
 #pragma mark - Public
@@ -61,55 +64,59 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
     if (_showsSearchBar == showsSearchBar) return;
     _showsSearchBar = showsSearchBar;
     
-    UIViewController *results = self.searchResultsController;
-    self.searchController = [[UISearchController alloc] initWithSearchResultsController:results];
-    self.searchController.searchBar.placeholder = @"Filter";
-    self.searchController.searchResultsUpdater = (id)self;
-    self.searchController.delegate = (id)self;
-    self.searchController.dimsBackgroundDuringPresentation = NO;
-    self.searchController.hidesNavigationBarDuringPresentation = NO;
-    /// Not necessary in iOS 13; remove this when iOS 13 is the minimum deployment target
-    self.searchController.searchBar.delegate = self;
-
-    self.automaticallyShowsSearchBarCancelButton = YES;
-
-#if FLEX_AT_LEAST_IOS13_SDK
-    if (@available(iOS 13, *)) {
-        self.searchController.automaticallyShowsScopeBar = NO;
-    }
-#endif
-    
-    if (@available(iOS 11.0, *)) {
-        self.navigationItem.searchController = self.searchController;
+    if (showsSearchBar) {
+        UIViewController *results = self.searchResultsController;
+        self.searchController = [[UISearchController alloc] initWithSearchResultsController:results];
+        self.searchController.searchBar.placeholder = @"Filter";
+        self.searchController.searchResultsUpdater = (id)self;
+        self.searchController.delegate = (id)self;
+        self.searchController.dimsBackgroundDuringPresentation = NO;
+        self.searchController.hidesNavigationBarDuringPresentation = NO;
+        /// Not necessary in iOS 13; remove this when iOS 13 is the minimum deployment target
+        self.searchController.searchBar.delegate = self;
+
+        self.automaticallyShowsSearchBarCancelButton = YES;
+
+        #if FLEX_AT_LEAST_IOS13_SDK
+        if (@available(iOS 13, *)) {
+            self.searchController.automaticallyShowsScopeBar = NO;
+        }
+        #endif
+        
+        [self addSearchController:self.searchController];
     } else {
-        self.tableView.tableHeaderView = self.searchController.searchBar;
+        // Search already shown and just set to NO, so remove it
+        [self removeSearchController:self.searchController];
     }
 }
 
 - (void)setShowsCarousel:(BOOL)showsCarousel {
     if (_showsCarousel == showsCarousel) return;
     _showsCarousel = showsCarousel;
+    
+    if (showsCarousel) {
+        _carousel = ({
+            __weak __typeof(self) weakSelf = self;
+
+            FLEXScopeCarousel *carousel = [FLEXScopeCarousel new];
+            carousel.selectedIndexChangedAction = ^(NSInteger idx) {
+                __typeof(self) self = weakSelf;
+                [self updateSearchResults:self.searchText];
+            };
+
+            // UITableView won't update the header size unless you reset the header view
+            [carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *carousel) {
+                __typeof(self) self = weakSelf;
+                [self layoutTableHeaderIfNeeded];
+            }];
 
-    _carousel = ({
-        __weak __typeof(self) weakSelf = self;
-
-        FLEXScopeCarousel *carousel = [FLEXScopeCarousel new];
-        carousel.selectedIndexChangedAction = ^(NSInteger idx) {
-            __typeof(self) self = weakSelf;
-            [self updateSearchResults:self.searchText];
-        };
-
-        self.tableView.tableHeaderView = carousel;
-        [self.tableView layoutIfNeeded];
-        // UITableView won't update the header size unless you reset the header view
-        [carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *carousel) {
-            __typeof(self) self = weakSelf;
-            self.tableView.tableHeaderView = carousel;
-            [self.tableView layoutIfNeeded];
-        }];
-
-        carousel;
-    });
+            carousel;
+        });
+        [self addCarousel:_carousel];
+    } else {
+        // Carousel already shown and just set to NO, so remove it
+        [self removeCarousel:_carousel];
+    }
 }
 
 - (NSInteger)selectedScope {
@@ -192,6 +199,11 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
     }
 }
 
+- (void)viewWillLayoutSubviews {
+    [super viewWillLayoutSubviews];
+    
+}
+
 - (void)viewWillAppear:(BOOL)animated {
     [super viewWillAppear:animated];
     
@@ -246,6 +258,110 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
     ];
 }
 
+- (void)layoutTableHeaderIfNeeded {
+    if (self.showsCarousel) {
+        self.carousel.frame = FLEXRectSetHeight(
+            self.carousel.frame, self.carousel.intrinsicContentSize.height
+        );
+    }
+    
+    self.tableView.tableHeaderView = self.tableView.tableHeaderView;
+    [self.tableView layoutIfNeeded];
+}
+
+- (void)addCarousel:(FLEXScopeCarousel *)carousel {
+    if (@available(iOS 11.0, *)) {
+        self.tableView.tableHeaderView = carousel;
+    } else {
+        carousel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
+        
+        CGRect frame = self.tableHeaderViewContainer.frame;
+        CGRect subviewFrame = carousel.frame;
+        subviewFrame.origin.y = 0;
+        
+        // Put the carousel below the search bar if it's already there
+        if (self.showsSearchBar) {
+            carousel.frame = subviewFrame = FLEXRectSetY(
+                subviewFrame, self.searchController.searchBar.frame.size.height
+            );
+            frame.size.height += carousel.intrinsicContentSize.height;
+        } else {
+            frame.size.height = carousel.intrinsicContentSize.height;
+        }
+        
+        self.tableHeaderViewContainer.frame = frame;
+        [self.tableHeaderViewContainer addSubview:carousel];
+    }
+    
+    [self layoutTableHeaderIfNeeded];
+}
+
+- (void)removeCarousel:(FLEXScopeCarousel *)carousel {
+    [carousel removeFromSuperview];
+    
+    if (@available(iOS 11.0, *)) {
+        self.tableView.tableHeaderView = nil;
+    } else {
+        if (self.showsSearchBar) {
+            [self removeSearchController:self.searchController];
+            [self addSearchController:self.searchController];
+        } else {
+            self.tableView.tableHeaderView = nil;
+            _tableHeaderViewContainer = nil;
+        }
+    }
+}
+
+- (void)addSearchController:(UISearchController *)controller {
+    if (@available(iOS 11.0, *)) {
+        self.navigationItem.searchController = controller;
+    } else {
+        controller.searchBar.autoresizingMask |= UIViewAutoresizingFlexibleBottomMargin;
+        [self.tableHeaderViewContainer addSubview:controller.searchBar];
+        CGRect subviewFrame = controller.searchBar.frame;
+        CGRect frame = self.tableHeaderViewContainer.frame;
+        frame.size.width = MAX(frame.size.width, subviewFrame.size.width);
+        frame.size.height = subviewFrame.size.height;
+        
+        // Move the carousel down if it's already there
+        if (self.showsCarousel) {
+            self.carousel.frame = FLEXRectSetY(
+                self.carousel.frame, subviewFrame.size.height
+            );
+            frame.size.height += self.carousel.frame.size.height;
+        }
+        
+        self.tableHeaderViewContainer.frame = frame;
+        [self layoutTableHeaderIfNeeded];
+    }
+}
+
+- (void)removeSearchController:(UISearchController *)controller {
+    if (@available(iOS 11.0, *)) {
+        self.navigationItem.searchController = nil;
+    } else {
+        [controller.searchBar removeFromSuperview];
+        
+        if (self.showsCarousel) {
+//            self.carousel.frame = FLEXRectRemake(CGPointZero, self.carousel.frame.size);
+            [self removeCarousel:self.carousel];
+            [self addCarousel:self.carousel];
+        } else {
+            self.tableView.tableHeaderView = nil;
+            _tableHeaderViewContainer = nil;
+        }
+    }
+}
+
+- (UIView *)tableHeaderViewContainer {
+    if (!_tableHeaderViewContainer) {
+        _tableHeaderViewContainer = [UIView new];
+        self.tableView.tableHeaderView = self.tableHeaderViewContainer;
+    }
+    
+    return _tableHeaderViewContainer;
+}
+
 #pragma mark - Search Bar
 
 #pragma mark UISearchResultsUpdating

+ 9 - 12
Classes/Core/Views/Carousel/FLEXScopeCarousel.m

@@ -17,7 +17,6 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
 @interface FLEXScopeCarousel () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
 @property (nonatomic, readonly) UICollectionView *collectionView;
 @property (nonatomic, readonly) FLEXCarouselCell *sizingCell;
-@property (nonatomic, readonly) NSLayoutConstraint *heightConstraint;
 
 @property (nonatomic, readonly) id dynamicTypeObserver;
 @property (nonatomic, readonly) NSMutableArray *dynamicTypeHandlers;
@@ -32,6 +31,7 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
     if (self) {
         self.backgroundColor = [FLEXColor primaryBackgroundColor];
         self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+        self.translatesAutoresizingMaskIntoConstraints = YES;
         _dynamicTypeHandlers = [NSMutableArray new];
         
         CGSize itemSize = CGSizeZero;
@@ -117,25 +117,22 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
 
 - (void)updateConstraints {
     if (!self.constraintsInstalled) {
-        self.translatesAutoresizingMaskIntoConstraints = NO;
         self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
-
-        [self.centerXAnchor constraintEqualToAnchor:self.superview.centerXAnchor].active = YES;
-        [self.widthAnchor constraintEqualToAnchor:self.superview.widthAnchor].active = YES;
-        [self.topAnchor constraintEqualToAnchor:self.superview.topAnchor].active = YES;
-
         [self.collectionView pinEdgesToSuperview];
-        _heightConstraint = [self.heightAnchor constraintEqualToConstant:100];
-        self.heightConstraint.active = YES;
-
+        
         self.constraintsInstalled = YES;
     }
-
-    self.heightConstraint.constant = [self.sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
     
     [super updateConstraints];
 }
 
+- (CGSize)intrinsicContentSize {
+    return CGSizeMake(
+        UIViewNoIntrinsicMetric,
+        [self.sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height
+    );
+}
+
 #pragma mark - Public
 
 - (void)setItems:(NSArray<NSString *> *)items {

+ 1 - 1
Classes/Utility/Categories/UIGestureRecognizer+Blocks.h

@@ -3,7 +3,7 @@
 //  FLEX
 //
 //  Created by Tanner Bennett on 12/20/19.
-//Copyright © 2019 Flipboard. All rights reserved.
+//  Copyright © 2019 Flipboard. All rights reserved.
 //
 
 #import <UIKit/UIKit.h>

+ 1 - 1
Classes/Utility/Categories/UIGestureRecognizer+Blocks.m

@@ -3,7 +3,7 @@
 //  FLEX
 //
 //  Created by Tanner Bennett on 12/20/19.
-//Copyright © 2019 Flipboard. All rights reserved.
+//  Copyright © 2019 Flipboard. All rights reserved.
 //
 
 #import "UIGestureRecognizer+Blocks.h"

+ 1 - 1
Classes/Utility/Categories/UIMenu+FLEX.h

@@ -3,7 +3,7 @@
 //  FLEX
 //
 //  Created by Tanner on 1/28/20.
-//Copyright © 2020 Flipboard. All rights reserved.
+//  Copyright © 2020 Flipboard. All rights reserved.
 //
 
 #import <UIKit/UIKit.h>

+ 1 - 1
Classes/Utility/Categories/UIMenu+FLEX.m

@@ -3,7 +3,7 @@
 //  FLEX
 //
 //  Created by Tanner on 1/28/20.
-//Copyright © 2020 Flipboard. All rights reserved.
+//  Copyright © 2020 Flipboard. All rights reserved.
 //
 
 #import "UIMenu+FLEX.h"

+ 28 - 18
Classes/Utility/Categories/UIView+FLEX_Layout.m

@@ -11,22 +11,28 @@
 @implementation UIView (FLEX_Layout)
 
 - (void)centerInView:(UIView *)view {
-    [self.centerXAnchor constraintEqualToAnchor:view.centerXAnchor].active = YES;
-    [self.centerYAnchor constraintEqualToAnchor:view.centerYAnchor].active = YES;
+    [NSLayoutConstraint activateConstraints:@[
+        [self.centerXAnchor constraintEqualToAnchor:view.centerXAnchor],
+        [self.centerYAnchor constraintEqualToAnchor:view.centerYAnchor],
+    ]];
 }
 
 - (void)pinEdgesTo:(UIView *)view {
-    [self.topAnchor constraintEqualToAnchor:view.topAnchor].active = YES;
-    [self.leftAnchor constraintEqualToAnchor:view.leftAnchor].active = YES;
-    [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor].active = YES;
-    [self.rightAnchor constraintEqualToAnchor:view.rightAnchor].active = YES;
+   [NSLayoutConstraint activateConstraints:@[
+       [self.topAnchor constraintEqualToAnchor:view.topAnchor],
+       [self.leftAnchor constraintEqualToAnchor:view.leftAnchor],
+       [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
+       [self.rightAnchor constraintEqualToAnchor:view.rightAnchor],
+   ]]; 
 }
 
 - (void)pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)i {
-    [self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top].active = YES;
-    [self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left].active = YES;
-    [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-i.bottom].active = YES;
-    [self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right].active = YES;
+    [NSLayoutConstraint activateConstraints:@[
+        [self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top],
+        [self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left],
+        [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-i.bottom],
+        [self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right],
+    ]];
 }
 
 - (void)pinEdgesToSuperview {
@@ -39,18 +45,22 @@
 
 - (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i aboveView:(UIView *)sibling {
     UIView *view = self.superview;
-    [self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top].active = YES;
-    [self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left].active = YES;
-    [self.bottomAnchor constraintEqualToAnchor:sibling.topAnchor constant:-i.bottom].active = YES;
-    [self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right].active = YES;
+    [NSLayoutConstraint activateConstraints:@[
+        [self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top],
+        [self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left],
+        [self.bottomAnchor constraintEqualToAnchor:sibling.topAnchor constant:-i.bottom],
+        [self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right],
+    ]];
 }
 
 - (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i belowView:(UIView *)sibling {
     UIView *view = self.superview;
-    [self.topAnchor constraintEqualToAnchor:sibling.bottomAnchor constant:i.top].active = YES;
-    [self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left].active = YES;
-    [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-i.bottom].active = YES;
-    [self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right].active = YES;
+    [NSLayoutConstraint activateConstraints:@[
+        [self.topAnchor constraintEqualToAnchor:sibling.bottomAnchor constant:i.top],
+        [self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left],
+        [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-i.bottom],
+        [self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right],
+    ]];
 }
 
 @end

+ 30 - 0
Classes/Utility/FLEXUtility.h

@@ -26,6 +26,36 @@ NS_INLINE CGRect FLEXRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat heigh
     return CGRectMake(FLEXFloor(x), FLEXFloor(y), FLEXFloor(width), FLEXFloor(height));
 }
 
+/// Adjusts the origin of an existing rect
+NS_INLINE CGRect FLEXRectSetOrigin(CGRect r, CGPoint origin) {
+    r.origin = origin; return r;
+}
+
+/// Adjusts the size of an existing rect
+NS_INLINE CGRect FLEXRectSetSize(CGRect r, CGSize size) {
+    r.size = size; return r;
+}
+
+/// Adjusts the origin.x of an existing rect
+NS_INLINE CGRect FLEXRectSetX(CGRect r, CGFloat x) {
+    r.origin.x = x; return r;
+}
+
+/// Adjusts the origin.y of an existing rect
+NS_INLINE CGRect FLEXRectSetY(CGRect r, CGFloat y) {
+    r.origin.y = y ; return r;
+}
+
+/// Adjusts the size.width of an existing rect
+NS_INLINE CGRect FLEXRectSetWidth(CGRect r, CGFloat width) {
+    r.size.width = width; return r;
+}
+
+/// Adjusts the size.height of an existing rect
+NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
+    r.size.height = height; return r;
+}
+
 #ifdef __IPHONE_13_0
 #define FLEX_AT_LEAST_IOS13_SDK (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
 #else