diff --git a/Example/WPMediaPicker.xcodeproj/project.pbxproj b/Example/WPMediaPicker.xcodeproj/project.pbxproj index 18b708d..7d47624 100644 --- a/Example/WPMediaPicker.xcodeproj/project.pbxproj +++ b/Example/WPMediaPicker.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 173B215327873F2E00D4DD6B /* SampleCustomHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 173B215227873F2E00D4DD6B /* SampleCustomHeaderView.m */; }; 17475FB81FB46DED00252689 /* SampleCellOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 17475FB71FB46DED00252689 /* SampleCellOverlayView.m */; }; 17F64FE01E6DDC74006C5A2B /* CustomPreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F64FDF1E6DDC74006C5A2B /* CustomPreviewViewController.m */; }; 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; @@ -42,6 +43,8 @@ /* Begin PBXFileReference section */ 1120051BDDDC8A558883872E /* Pods-WPMediaPicker.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WPMediaPicker.release.xcconfig"; path = "Pods/Target Support Files/Pods-WPMediaPicker/Pods-WPMediaPicker.release.xcconfig"; sourceTree = ""; }; + 173B215127873F2E00D4DD6B /* SampleCustomHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SampleCustomHeaderView.h; sourceTree = ""; }; + 173B215227873F2E00D4DD6B /* SampleCustomHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleCustomHeaderView.m; sourceTree = ""; }; 17475FB61FB46DED00252689 /* SampleCellOverlayView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SampleCellOverlayView.h; sourceTree = ""; }; 17475FB71FB46DED00252689 /* SampleCellOverlayView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleCellOverlayView.m; sourceTree = ""; }; 17F64FDE1E6DDC74006C5A2B /* CustomPreviewViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomPreviewViewController.h; sourceTree = ""; }; @@ -155,6 +158,8 @@ 17F64FDF1E6DDC74006C5A2B /* CustomPreviewViewController.m */, 17475FB61FB46DED00252689 /* SampleCellOverlayView.h */, 17475FB71FB46DED00252689 /* SampleCellOverlayView.m */, + 173B215127873F2E00D4DD6B /* SampleCustomHeaderView.h */, + 173B215227873F2E00D4DD6B /* SampleCustomHeaderView.m */, 6003F59C195388D20070C39A /* AppDelegate.h */, 6003F59D195388D20070C39A /* AppDelegate.m */, 6003F5A8195388D20070C39A /* Images.xcassets */, @@ -387,6 +392,7 @@ B5FF3BEA1CAD8AB100C1D597 /* PostProcessingViewController.m in Sources */, 6003F59E195388D20070C39A /* AppDelegate.m in Sources */, 17475FB81FB46DED00252689 /* SampleCellOverlayView.m in Sources */, + 173B215327873F2E00D4DD6B /* SampleCustomHeaderView.m in Sources */, FF355D9E1FB5EB4A00244E6D /* WPPHAssetDataSource+Search.m in Sources */, FFFFD8811A447E67000FC184 /* DemoViewController.m in Sources */, 6003F59A195388D20070C39A /* main.m in Sources */, diff --git a/Example/WPMediaPicker/DemoViewController.m b/Example/WPMediaPicker/DemoViewController.m index 1d21fb9..570307e 100644 --- a/Example/WPMediaPicker/DemoViewController.m +++ b/Example/WPMediaPicker/DemoViewController.m @@ -4,7 +4,9 @@ #import "OptionsViewController.h" #import "PostProcessingViewController.h" #import "SampleCellOverlayView.h" +#import "SampleCustomHeaderView.h" #import + @import MobileCoreServices; static CGFloat const CellHeight = 100.0f; @@ -51,7 +53,8 @@ - (void)viewDidLoad MediaPickerOptionsCustomPreview:@(NO), MediaPickerOptionsScrollInputPickerVertically:@(YES), MediaPickerOptionsShowSampleCellOverlays:@(NO), - MediaPickerOptionsShowSearchBar:@(YES) + MediaPickerOptionsShowSearchBar:@(YES), + MediaPickerOptionsShowCustomHeader:@(NO) }; } @@ -252,6 +255,25 @@ - (BOOL)mediaPickerController:(nonnull WPMediaPickerViewController *)picker hand return error.domain != WPMediaPickerErrorDomain; } +- (void)mediaPickerController:(WPMediaPickerViewController *)picker configureCustomHeaderView:(UICollectionReusableView *)headerView { + if (![headerView isKindOfClass:[SampleCustomHeaderView class]]) { + return; + } + + SampleCustomHeaderView *view = (SampleCustomHeaderView *)headerView; + view.backgroundColor = [UIColor greenColor]; +} + +- (BOOL)mediaPickerControllerShouldShowCustomHeaderView:(WPMediaPickerViewController *)picker +{ + return [self.options[MediaPickerOptionsShowCustomHeader] boolValue] == YES; +} + +- (CGSize)mediaPickerControllerReferenceSizeForCustomHeaderView:(WPMediaPickerViewController *)picker +{ + return CGSizeMake(300, 200); +} + #pragma - Actions - (void) clearSelection:(id) sender @@ -293,6 +315,10 @@ - (void)showPicker:(id) sender if ([self.options[MediaPickerOptionsShowSampleCellOverlays] boolValue]) { [self.mediaPicker.mediaPicker registerClassForReusableCellOverlayViews:[SampleCellOverlayView class]]; } + + if ([self.options[MediaPickerOptionsShowCustomHeader] boolValue]) { + [self.mediaPicker.mediaPicker registerClassForCustomHeaderView:[SampleCustomHeaderView class]]; + } [self presentViewController:self.mediaPicker animated:YES completion:nil]; [self.quickInputTextField resignFirstResponder]; diff --git a/Example/WPMediaPicker/OptionsViewController.h b/Example/WPMediaPicker/OptionsViewController.h index 802b328..31db7e9 100644 --- a/Example/WPMediaPicker/OptionsViewController.h +++ b/Example/WPMediaPicker/OptionsViewController.h @@ -11,6 +11,9 @@ extern NSString const *MediaPickerOptionsScrollInputPickerVertically; extern NSString const *MediaPickerOptionsShowSampleCellOverlays; extern NSString const *MediaPickerOptionsShowSearchBar; extern NSString const *MediaPickerOptionsShowActionBar; +/// Note that a custom header cannot be displayed at the same time as the in-picker camera capture cell. +/// If both are specified, the custom header will take precedence. +extern NSString const *MediaPickerOptionsShowCustomHeader; @class OptionsViewController; diff --git a/Example/WPMediaPicker/OptionsViewController.m b/Example/WPMediaPicker/OptionsViewController.m index fbff148..53cd870 100644 --- a/Example/WPMediaPicker/OptionsViewController.m +++ b/Example/WPMediaPicker/OptionsViewController.m @@ -13,6 +13,7 @@ NSString const *MediaPickerOptionsShowSampleCellOverlays = @"MediaPickerOptionsShowSampleCellOverlays"; NSString const *MediaPickerOptionsShowSearchBar = @"MediaPickerOptionsShowSearchBar"; NSString const *MediaPickerOptionsShowActionBar = @"MediaPickerOptionsShowActionBar"; +NSString const *MediaPickerOptionsShowCustomHeader = @"MediaPickerOptionsShowCustomHeader"; typedef NS_ENUM(NSInteger, OptionsViewControllerCell){ @@ -27,6 +28,7 @@ typedef NS_ENUM(NSInteger, OptionsViewControllerCell){ OptionsViewControllerCellShowSampleCellOverlays, OptionsViewControllerCellShowSearchBar, OptionsViewControllerCellShowActionBar, + OptionsViewControllerCellShowCustomHeader, OptionsViewControllerCellTotal }; @@ -43,6 +45,7 @@ @interface OptionsViewController () @property (nonatomic, strong) UITableViewCell *cellOverlaysCell; @property (nonatomic, strong) UITableViewCell *showSearchBarCell; @property (nonatomic, strong) UITableViewCell *showActionBarCell; +@property (nonatomic, strong) UITableViewCell *showCustomHeaderCell; @end @@ -119,6 +122,13 @@ - (void)viewDidLoad self.showActionBarCell.accessoryView = [[UISwitch alloc] init]; ((UISwitch *)self.showActionBarCell.accessoryView).on = [self.options[MediaPickerOptionsShowActionBar] boolValue]; self.showActionBarCell.textLabel.text = NSLocalizedString(@"Show Action Bar", @""); + + self.showCustomHeaderCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]; + self.showCustomHeaderCell.accessoryView = [[UISwitch alloc] init]; + ((UISwitch *)self.showCustomHeaderCell.accessoryView).on = [self.options[MediaPickerOptionsShowCustomHeader] boolValue]; + self.showCustomHeaderCell.textLabel.text = NSLocalizedString(@"Show Custom Header", @""); + self.showCustomHeaderCell.detailTextLabel.text = NSLocalizedString(@"If custom header and capture cell are enabled, custom header takes precedence.", @""); + self.showCustomHeaderCell.detailTextLabel.numberOfLines = 2; } #pragma mark - Table view data source @@ -166,6 +176,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return self.showSearchBarCell; case OptionsViewControllerCellShowActionBar: return self.showActionBarCell; + case OptionsViewControllerCellShowCustomHeader: + return self.showCustomHeaderCell; default: break; } @@ -195,7 +207,8 @@ - (void)done:(id) sender MediaPickerOptionsScrollInputPickerVertically:@(((UISwitch *)self.scrollInputPickerCell.accessoryView).on), MediaPickerOptionsShowSampleCellOverlays:@(((UISwitch *)self.cellOverlaysCell.accessoryView).on), MediaPickerOptionsShowSearchBar:@(((UISwitch *)self.showSearchBarCell.accessoryView).on), - MediaPickerOptionsShowActionBar:@(((UISwitch *)self.showActionBarCell.accessoryView).on) + MediaPickerOptionsShowActionBar:@(((UISwitch *)self.showActionBarCell.accessoryView).on), + MediaPickerOptionsShowCustomHeader:@(((UISwitch *)self.showCustomHeaderCell.accessoryView).on) }; [delegate optionsViewController:self changed:newOptions]; diff --git a/Example/WPMediaPicker/SampleCustomHeaderView.h b/Example/WPMediaPicker/SampleCustomHeaderView.h new file mode 100644 index 0000000..522d586 --- /dev/null +++ b/Example/WPMediaPicker/SampleCustomHeaderView.h @@ -0,0 +1,9 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SampleCustomHeaderView : UICollectionReusableView + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/WPMediaPicker/SampleCustomHeaderView.m b/Example/WPMediaPicker/SampleCustomHeaderView.m new file mode 100644 index 0000000..af6c56f --- /dev/null +++ b/Example/WPMediaPicker/SampleCustomHeaderView.m @@ -0,0 +1,36 @@ +#import "SampleCustomHeaderView.h" + +@implementation SampleCustomHeaderView + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + [self commonInit]; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if (self = [super initWithCoder:aDecoder]) { + [self commonInit]; + } + return self; +} + +- (void)commonInit +{ + self.backgroundColor = [UIColor redColor]; + + UILabel *label = [UILabel new]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.text = NSLocalizedString(@"Custom Header", @""); + [self addSubview:label]; + + [NSLayoutConstraint activateConstraints:@[ + [label.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [label.centerYAnchor constraintEqualToAnchor:self.centerYAnchor] + ]]; +} + +@end diff --git a/Pod/Classes/WPMediaPickerViewController.h b/Pod/Classes/WPMediaPickerViewController.h index 8a2be49..4a8b51d 100644 --- a/Pod/Classes/WPMediaPickerViewController.h +++ b/Pod/Classes/WPMediaPickerViewController.h @@ -158,7 +158,7 @@ /** * Asks the delegate whether an overlay view should be shown for the cell for * the specified media asset. If you return `YES` from this method, you must - * have registered a reuse class though `-[WPMediaPickerViewController registerClassForReusableCellOverlayViews:]`. + * have registered a reuse class through `-[WPMediaPickerViewController registerClassForReusableCellOverlayViews:]`. * * @param asset The asset to display an overlay view for. * @return `YES` if an overlay view should be displayed, `NO`, if not. @@ -178,6 +178,33 @@ */ - (void)mediaPickerController:(nonnull WPMediaPickerViewController *)picker willShowOverlayView:(nonnull UIView *)overlayView forCellForAsset:(nonnull id)asset; +/** + * Asks the delegate to configure a custom header view. You must have registered a reuse class + * through `-[WPMediaPickerViewController registerClassForCustomHeaderView:]` and returned `YES` + * from `mediaPickerControllerShouldShowCustomHeaderView` otherwise this method will not be called. + * + * @param picker The controller object managing the assets picker interface. + * @param headerView An instance of the custom header view type to configure. + */ +- (void)mediaPickerController:(nonnull WPMediaPickerViewController *)picker configureCustomHeaderView:(nonnull UICollectionReusableView *)headerView; + +/** + * Asks the delegate whether a custom header view should be displayed. + * + * @param picker The controller object managing the assets picker interface. + * @return `YES` if a custom header view should be shown, otherwise `NO`. + */ +- (BOOL)mediaPickerControllerShouldShowCustomHeaderView:(nonnull WPMediaPickerViewController *)picker; + +/** + * Asks the delegate for a reference size for the registered custom header view, if one has been registered. This will only be called + * if `mediaPickerControllerShouldShowCustomHeaderView` has been implemented and returns `YES`. + * + * @param picker The controller object managing the assets picker interface. + * @return A size for the header view to be displayed at. + */ +- (CGSize)mediaPickerControllerReferenceSizeForCustomHeaderView:(nonnull WPMediaPickerViewController *)picker; + /** * Gives the delegate an oportunity to react to a change in the number * of assets displayed as a consequence of a search filter. @@ -341,6 +368,13 @@ */ - (void)registerClassForReusableCellOverlayViews:(nonnull Class)overlayClass; +/** + Register a `UICollectionReusableView` subclass to be displayed as an optional header at the top of the picker view. + For the header to be displayed, you must register a class using this method, and then + return a configured instance from `customHeaderViewForMediaPickerController:` + */ +- (void)registerClassForCustomHeaderView:(nonnull Class)overlayClass; + /** Shows the search bar that was hidden by `hideSearchBar`. If the `showSearchBar` option is set to `NO`, and the data source does not implement diff --git a/Pod/Classes/WPMediaPickerViewController.m b/Pod/Classes/WPMediaPickerViewController.m index 06f311a..166826d 100644 --- a/Pod/Classes/WPMediaPickerViewController.m +++ b/Pod/Classes/WPMediaPickerViewController.m @@ -18,6 +18,7 @@ static CGFloat const IPadPortraitWidth = 768.0f; static CGFloat const IPadLandscapeWidth = 1024.0f; static CGFloat const IPadPro12LandscapeWidth = 1366.0f; +static NSString *const CustomHeaderReuseIdentifier = @"CustomHeaderReuseIdentifier"; @interface WPMediaPickerViewController () < @@ -183,6 +184,15 @@ - (void)registerClassForReusableCellOverlayViews:(Class)overlayClass self.overlayViewClass = overlayClass; } +- (void)registerClassForCustomHeaderView:(Class)overlayClass +{ + NSParameterAssert([overlayClass isSubclassOfClass:[UICollectionReusableView class]]); + + [self.collectionView registerClass:overlayClass + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:CustomHeaderReuseIdentifier]; +} + - (UICollectionViewFlowLayout *)layout { return (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout; @@ -935,6 +945,12 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { + if ([self shouldShowCustomHeaderView]) { + if ([self.mediaPickerDelegate respondsToSelector:@selector(mediaPickerControllerReferenceSizeForCustomHeaderView:)]) { + return [self.mediaPickerDelegate mediaPickerControllerReferenceSizeForCustomHeaderView:self]; + } + } + if ( [self isShowingCaptureCell] && self.options.showMostRecentFirst) { return self.cameraPreviewSize; @@ -957,6 +973,15 @@ - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { + // Custom header view support + if (kind == UICollectionElementKindSectionHeader && [self shouldShowCustomHeaderView]) { + if ([self.mediaPickerDelegate respondsToSelector:@selector(mediaPickerController:configureCustomHeaderView:)]) { + UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:CustomHeaderReuseIdentifier forIndexPath:indexPath]; + [self.mediaPickerDelegate mediaPickerController:self configureCustomHeaderView:view]; + return view; + } + } + if ((kind == UICollectionElementKindSectionHeader && self.options.showMostRecentFirst) || (kind == UICollectionElementKindSectionFooter && !self.options.showMostRecentFirst)) { @@ -1000,6 +1025,15 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:( } } +- (BOOL)shouldShowCustomHeaderView +{ + if ([self.mediaPickerDelegate respondsToSelector:@selector(mediaPickerControllerShouldShowCustomHeaderView:)]) { + return [self.mediaPickerDelegate mediaPickerControllerShouldShowCustomHeaderView:self]; + } + + return NO; +} + /** Returns the position of the asset in the current selection if any diff --git a/Pod/Classes/WPNavigationMediaPickerViewController.m b/Pod/Classes/WPNavigationMediaPickerViewController.m index a14a2a7..2d801e1 100644 --- a/Pod/Classes/WPNavigationMediaPickerViewController.m +++ b/Pod/Classes/WPNavigationMediaPickerViewController.m @@ -318,6 +318,31 @@ - (void)mediaPickerController:(WPMediaPickerViewController *)picker willShowOver } } +- (BOOL)mediaPickerControllerShouldShowCustomHeaderView:(WPMediaPickerViewController *)picker +{ + if ([self.delegate respondsToSelector:@selector(mediaPickerControllerShouldShowCustomHeaderView:)]) { + return [self.delegate mediaPickerControllerShouldShowCustomHeaderView:picker]; + } + + return NO; +} + +- (CGSize)mediaPickerControllerReferenceSizeForCustomHeaderView:(WPMediaPickerViewController *)picker +{ + if ([self.delegate respondsToSelector:@selector(mediaPickerControllerReferenceSizeForCustomHeaderView:)]) { + return [self.delegate mediaPickerControllerReferenceSizeForCustomHeaderView:picker]; + } + + return CGSizeZero; +} + +- (void)mediaPickerController:(WPMediaPickerViewController *)picker configureCustomHeaderView:(UICollectionReusableView *)headerView +{ + if ([self.delegate respondsToSelector:@selector(mediaPickerController:configureCustomHeaderView:)]) { + [self.delegate mediaPickerController:picker configureCustomHeaderView:headerView]; + } +} + - (nullable UIViewController *)mediaPickerController:(WPMediaPickerViewController *)picker previewViewControllerForAssets:(nonnull NSArray> *)assets selectedIndex:(NSInteger)selected { UIViewController *previewVC; if ([self.delegate respondsToSelector:@selector(mediaPickerController:previewViewControllerForAssets:selectedIndex:)]) { diff --git a/WPMediaPicker.podspec b/WPMediaPicker.podspec index d2f7f7e..d448abf 100644 --- a/WPMediaPicker.podspec +++ b/WPMediaPicker.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = 'WPMediaPicker' - s.version = '1.8.1' + s.version = '1.8.2-beta.1' s.summary = 'WPMediaPicker is an iOS controller that allows capture and picking of media assets.' s.description = <<-DESC