diff --git a/Animated Banner/README.md b/Animated Banner/README.md
new file mode 100644
index 0000000..03618c7
--- /dev/null
+++ b/Animated Banner/README.md
@@ -0,0 +1,89 @@
+# Animated Banner
+Great-looking animated banner for your preferences
+
+## Preview
+
+
+ Showcase of the banner
+
+
+## Features
+- Simple but nice
+- Looks good in both light and dark mode
+- Endlessly customizable with animations and gradient
+- Stays always vertically centered to fit the space
+- Dynamically enabled/disabled by a specifier
+
+## Usage
+### Basic implementation
+```objective-c
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ NSArray *myUIColorArray = @[
+ [UIColor redColor],
+ [UIColor blueColor]
+ ];
+ CGFloat navbarOffset = 64; // default navigation bar height for non-notched devices. Required for scroll animation. Can be obtain with:
+ // self.view.window.windowScene.statusBarManager.statusBarFrame.size.height + self.realNavigationController.navigationBar.frame.size.height
+
+ self.table.tableHeaderView = [[XXXAnimatedBanner alloc] initWithTitle:@"Title" subtitle:@"v1.0" colors:myUIColorArray defaultOffset:-navbarOffset];
+}
+```
+
+### Animate on scroll
+```objective-c
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
+ if ([self.table.tableHeaderView isKindOfClass:[SPDAnimatedBanner class]]) {
+ [(SPDAnimatedBanner *)self.table.tableHeaderView adjustStackPositionToScrollOffset:scrollView.contentOffset.y];
+ }
+}
+```
+
+### Tip 1: dynamically enable/disable
+```objective-c
+- (void)setPreferenceValue:(id)value specifier:(PSSpecifier *)specifier {
+ [super setPreferenceValue:value specifier:specifier];
+
+ if ([specifier.properties[@"key"] isEqualToString:@"enabled"] && [self.table.tableHeaderView isKindOfClass:[SPDAnimatedBanner class]]) {
+ ((SPDAnimatedBanner *)self.table.tableHeaderView).activateGradient = [value boolValue];
+ }
+}
+```
+
+### Tip 2: relaunch animations when they get stopped by iOS
+```objective-c
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ self.table.tableHeaderView = [[XXXAnimatedBanner alloc] initWithTitle:...];
+ [self refreshBanner];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshBanner) name:UIApplicationDidBecomeActiveNotification object:nil]; // revive after app closed and reopened
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+
+ if (!self.isMovingToParentViewController) [self refreshBanner]; // revive after going in sub pages
+}
+
+- (void)refreshBanner {
+ if ([self.table.tableHeaderView isKindOfClass:[SPDAnimatedBanner class]]) {
+ ((SPDAnimatedBanner *)self.table.tableHeaderView).activateGradient = yourSwitchValue;
+ }
+}
+```
+
+## Gallery
+
+
+ Animatable
+
+
+
+ Gorgeous in light or dark
+
+
+
+ Can be controlled from a specifier
+
diff --git a/Animated Banner/XXXAnimatedBanner.h b/Animated Banner/XXXAnimatedBanner.h
new file mode 100644
index 0000000..efdcf8b
--- /dev/null
+++ b/Animated Banner/XXXAnimatedBanner.h
@@ -0,0 +1,15 @@
+#import
+
+@interface XXXAnimatedBanner : UIView
+@property(nonatomic, strong) UILabel *titleLabel;
+@property(nonatomic, strong) UIView *gradientView;
+@property(nonatomic, strong) UILabel *subtitleLabel;
+@property(nonatomic, strong) CAGradientLayer *gradient;
+@property(nonatomic, strong) NSMutableArray *animations;
+@property(nonatomic, strong) NSMutableArray *cgcolors;
+@property(nonatomic) CGFloat defaultOffset;
+@property(nonatomic, strong) NSLayoutConstraint *stackCenter;
+@property(nonatomic, assign, getter=isGradientActivated) BOOL activateGradient;
+- (instancetype)initWithTitle:(NSString *)title subtitle:(NSString *)subtitle colors:(NSArray<__kindof UIColor *> *)colors defaultOffset:(CGFloat)offset;
+- (void)adjustStackPositionToScrollOffset:(CGFloat)offset;
+@end
diff --git a/Animated Banner/XXXAnimatedBanner.m b/Animated Banner/XXXAnimatedBanner.m
new file mode 100644
index 0000000..4e265c4
--- /dev/null
+++ b/Animated Banner/XXXAnimatedBanner.m
@@ -0,0 +1,127 @@
+#import "XXXAnimatedBanner.h"
+
+@implementation XXXAnimatedBanner
+
+- (instancetype)initWithTitle:(NSString *)title subtitle:(NSString *)subtitle colors:(NSArray<__kindof UIColor *> *)colors defaultOffset:(CGFloat)defaultOffset {
+ if (self = [super initWithFrame:CGRectMake(0, 0, self.superview.frame.size.width, 200)]) {
+ // Labels
+ self.titleLabel = [[UILabel alloc] init];
+ self.titleLabel.text = title;
+ self.titleLabel.font = [UIFont boldSystemFontOfSize:42.f];
+ self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
+ [self.titleLabel sizeToFit];
+ // CGPoint titleFrameAnchor = CGPointMake(CGRectGetMinX(self.titleLabel.frame), CGRectGetMaxY(self.titleLabel.frame));
+ // self.titleLabel.layer.anchorPoint = CGPointMake(0, 1);
+ // self.titleLabel.layer.position = titleFrameAnchor; // ~ top center
+
+ self.subtitleLabel = [[UILabel alloc] init];
+ self.subtitleLabel.text = subtitle;
+ self.subtitleLabel.font = [UIFont systemFontOfSize:24.f weight:UIFontWeightMedium];
+ if (@available(iOS 13.0, *)) {
+ self.subtitleLabel.textColor = [UIColor secondaryLabelColor];
+ } else {
+ self.subtitleLabel.textColor = [UIColor systemGrayColor];
+ }
+ self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping;
+ [self.subtitleLabel sizeToFit];
+ // CGPoint subtitleFrameAnchor = CGPointMake(CGRectGetMinX(self.subtitleLabel.frame), CGRectGetMinY(self.subtitleLabel.frame));
+ // self.subtitleLabel.layer.anchorPoint = CGPointMake(0, 0);
+ // self.subtitleLabel.layer.position = subtitleFrameAnchor; // bottom center
+
+ // Create gradient
+ self.gradient = [CAGradientLayer layer];
+ self.gradient.frame = self.titleLabel.frame;
+ self.cgcolors = [NSMutableArray new];
+ for (int i = 0; i < colors.count; i++) {
+ self.cgcolors[i] = (id)colors[i].CGColor;
+ }
+ self.gradient.colors = self.cgcolors;
+ self.gradient.locations = @[@0, @1];
+
+ self.gradientView = [[UIView alloc] init];
+ [self.gradientView.layer addSublayer:self.gradient];
+ self.gradientView.maskView = self.titleLabel;
+ // CGPoint gradientFrameAnchor = CGPointMake(CGRectGetMinX(self.gradientView.frame), CGRectGetMaxY(self.gradientView.frame));
+ // self.gradientView.layer.anchorPoint = CGPointMake(0, 1);
+ // self.gradientView.layer.position = gradientFrameAnchor;
+
+ // Animations
+ self.animations = [NSMutableArray new];
+
+ CABasicAnimation *locations = [CABasicAnimation animationWithKeyPath:@"locations"];
+ locations.fromValue = @[@0, @0];
+ locations.toValue = @[@0, @1];
+ locations.autoreverses = YES;
+ locations.repeatCount = FLT_MAX;
+ locations.duration = 5.0;
+ [self.animations addObject:locations];
+
+ CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:@"opacity"];
+ opacity.fromValue = @.75;
+ opacity.toValue = @1;
+ opacity.autoreverses = YES;
+ opacity.repeatCount = FLT_MAX;
+ opacity.duration = 3.0;
+ [self.animations addObject:opacity];
+
+ // Stack
+ UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[self.gradientView, self.subtitleLabel]];
+ stackView.alignment = UIStackViewAlignmentLeading;
+ stackView.axis = UILayoutConstraintAxisVertical;
+ stackView.distribution = UIStackViewDistributionEqualCentering;
+ stackView.spacing = 0;
+ stackView.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:stackView];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:25],
+ self.stackCenter = [stackView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor constant:0],
+ [stackView.heightAnchor constraintEqualToConstant:self.titleLabel.frame.size.height + self.subtitleLabel.frame.size.height],
+
+ [self.gradientView.widthAnchor constraintEqualToConstant:self.titleLabel.frame.size.width],
+ [self.gradientView.heightAnchor constraintEqualToConstant:self.titleLabel.frame.size.height]
+ ]];
+
+ self.defaultOffset = defaultOffset;
+ }
+
+ return self;
+}
+
+- (void)setActivateGradient:(BOOL)activated {
+ _activateGradient = activated;
+
+ // I would like to animate transition between activated and not but nvm
+ if (activated) {
+ self.gradient.colors = self.cgcolors;
+ for (CAAnimation *animation in self.animations) {
+ [self.gradient addAnimation:animation forKey:nil];
+ }
+ } else {
+ if (@available(iOS 13.0, *)) {
+ self.gradient.colors = @[(id)[UIColor labelColor].CGColor, (id)[UIColor labelColor].CGColor];
+ } else {
+ self.gradient.colors = @[(id)[UIColor blackColor].CGColor, (id)[UIColor blackColor].CGColor];
+ }
+ [self.gradient removeAllAnimations];
+ }
+}
+
+- (void)adjustStackPositionToScrollOffset:(CGFloat)offset {
+ // Smooth scroll effect
+ if (offset < self.defaultOffset) {
+ CGFloat space = self.defaultOffset - offset;
+ self.stackCenter.constant = -space / 2;
+ // This + comments above are to make the text a bit bigger on scrolling
+ // pretty much like the stock iOS behavior with big titles but I can't
+ // manage to make it look good so I gave up
+ /* if (space < 100) {
+ CGFloat titleScale = 1 + (space / 750);
+ CGFloat subtitleScale = 1 + (space / 1000);
+ self.titleLabel.transform = CGAffineTransformMakeScale(titleScale, titleScale);
+ self.subtitleLabel.transform = CGAffineTransformMakeScale(subtitleScale, subtitleScale);
+ } */
+ }
+}
+
+@end
diff --git a/Animated Banner/images/animated.gif b/Animated Banner/images/animated.gif
new file mode 100644
index 0000000..1f17896
Binary files /dev/null and b/Animated Banner/images/animated.gif differ
diff --git a/Animated Banner/images/light_and_dark.gif b/Animated Banner/images/light_and_dark.gif
new file mode 100644
index 0000000..f3c61d8
Binary files /dev/null and b/Animated Banner/images/light_and_dark.gif differ
diff --git a/Animated Banner/images/showcase.gif b/Animated Banner/images/showcase.gif
new file mode 100644
index 0000000..2a5c4de
Binary files /dev/null and b/Animated Banner/images/showcase.gif differ
diff --git a/Animated Banner/images/togglable.gif b/Animated Banner/images/togglable.gif
new file mode 100644
index 0000000..e0d6ece
Binary files /dev/null and b/Animated Banner/images/togglable.gif differ
diff --git a/Animated Title View/XXXAnimatedTitleView.h b/Animated Title View/XXXAnimatedTitleView.h
index a4d4321..64332f2 100644
--- a/Animated Title View/XXXAnimatedTitleView.h
+++ b/Animated Title View/XXXAnimatedTitleView.h
@@ -1,6 +1,13 @@
#import
-@interface LBMAnimatedTitleView : UIView
--(instancetype)initWithTitle:(NSString *)title minimumScrollOffsetRequired:(CGFloat)minimumOffset;
--(void)adjustLabelPositionToScrollOffset:(CGFloat)offset;
+@interface XXXAnimatedTitleView : UIView {
+ NSLayoutConstraint *labelYConstraint;
+ NSLayoutConstraint *iconYConstraint;
+ CGFloat minimumOffsetRequired;
+}
+@property (nonatomic, strong, readonly) UIImageView *iconImageView;
+@property (nonatomic, strong, readonly) UILabel *titleLabel;
+- (instancetype)initWithTitle:(NSString *)title minimumScrollOffsetRequired:(CGFloat)minimumOffset;
+- (instancetype)initWithTitle:(NSString *)title image:(_Nullable UIImage *)image minimumScrollOffsetRequired:(CGFloat)minimumOffset;
+- (void)adjustItemsPositionToScrollOffset:(CGFloat)offset;
@end
diff --git a/Animated Title View/XXXAnimatedTitleView.m b/Animated Title View/XXXAnimatedTitleView.m
index 69b8e66..152f3bd 100644
--- a/Animated Title View/XXXAnimatedTitleView.m
+++ b/Animated Title View/XXXAnimatedTitleView.m
@@ -1,50 +1,60 @@
#import "XXXAnimatedTitleView.h"
-@implementation XXXAnimatedTitleView {
- UILabel *_titleLabel;
- NSLayoutConstraint *_yConstraint;
- CGFloat _minimumOffsetRequired;
+@implementation XXXAnimatedTitleView
+
+- (instancetype)initWithTitle:(NSString *)title minimumScrollOffsetRequired:(CGFloat)minimumOffset {
+ return [self initWithTitle:title image:nil minimumScrollOffsetRequired:minimumOffset];
+}
+
+- (instancetype)initWithTitle:(NSString *)title image:(_Nullable UIImage *)image minimumScrollOffsetRequired:(CGFloat)minimumOffset {
+ if (self = [super init]) {
+ self.superview.clipsToBounds = YES;
+
+ _titleLabel = [[UILabel alloc] init];
+ _titleLabel.text = title;
+ _titleLabel.textAlignment = NSTextAlignmentCenter;
+ if (@available(iOS 13.0, *)) {
+ _titleLabel.textColor = [UIColor labelColor];
+ } else {
+ _titleLabel.textColor = [UIColor blackColor];
+ }
+ _titleLabel.font = [UIFont systemFontOfSize:[UIFont labelFontSize] weight:UIFontWeightSemibold];
+ _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
+ [_titleLabel sizeToFit];
+
+ _iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
+ _iconImageView.image = image;
+ _iconImageView.contentMode = UIViewContentModeScaleAspectFit;
+ _iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
+
+ [self addSubview:_titleLabel];
+ [self addSubview:_iconImageView];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [self.widthAnchor constraintEqualToAnchor:_titleLabel.widthAnchor],
+ [self.heightAnchor constraintEqualToAnchor:_titleLabel.heightAnchor],
+ [_titleLabel.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
+ labelYConstraint = [_titleLabel.centerYAnchor constraintEqualToAnchor:self.centerYAnchor constant:50],
+
+ [_iconImageView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
+ iconYConstraint = [_iconImageView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor constant:0]
+ ]];
+
+ minimumOffsetRequired = minimumOffset;
+ }
+
+ return self;
}
- -(instancetype)initWithTitle:(NSString *)title minimumScrollOffsetRequired:(CGFloat)minimumOffset {
- if([super init]) {
- self.superview.clipsToBounds = YES;
-
- _titleLabel = [[UILabel alloc] init];
- _titleLabel.text = title;
- _titleLabel.textAlignment = NSTextAlignmentCenter;
- _titleLabel.textColor = [UIColor whiteColor];
- _titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightHeavy];
- _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
- [_titleLabel sizeToFit];
-
- [self addSubview:_titleLabel];
-
- [NSLayoutConstraint activateConstraints:@[
- [self.widthAnchor constraintEqualToAnchor:_titleLabel.widthAnchor],
- [self.heightAnchor constraintEqualToAnchor:_titleLabel.heightAnchor],
-
- [_titleLabel.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
- _yConstraint = [_titleLabel.centerYAnchor constraintEqualToAnchor:self.centerYAnchor constant:100],
- ]];
-
- _minimumOffsetRequired = minimumOffset;
- }
-
- return self;
- }
-
- -(void)adjustLabelPositionToScrollOffset:(CGFloat)offset {
- CGFloat adjustment = 100 - (offset - _minimumOffsetRequired);
- if(offset >= _minimumOffsetRequired) {
- if(adjustment <= 0) {
- _yConstraint.constant = 0;
- } else {
- _yConstraint.constant = adjustment;
- }
-
- } else {
- _yConstraint.constant = -100;
- }
- }
+- (void)adjustItemsPositionToScrollOffset:(CGFloat)offset {
+ CGFloat adjustment = 50 - (offset - minimumOffsetRequired);
+ if (offset >= minimumOffsetRequired) {
+ labelYConstraint.constant = adjustment <= 0 ? 0 : adjustment;
+ iconYConstraint.constant = adjustment <= 40 ? adjustment <= -10 ? -50 : adjustment - 40 : 0; // don't ask me i don't know
+ } else {
+ labelYConstraint.constant = 50;
+ iconYConstraint.constant = 0;
+ }
+ _iconImageView.alpha = labelYConstraint.constant / 50;
+}
@end
diff --git a/Animated Title View/XXXRootListController.m b/Animated Title View/XXXRootListController.m
index 89439ad..f298dc4 100644
--- a/Animated Title View/XXXRootListController.m
+++ b/Animated Title View/XXXRootListController.m
@@ -1,18 +1,17 @@
--(void)viewDidAppear:(BOOL)animated {
+- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
- //Create view and set as titleView of your navigation bar
- //Set the title and the minimum scroll offset before starting the animation
- if(!self.navigationItem.titleView) {
+ // Create view and set as titleView of your navigation bar
+ // Set the title and the minimum scroll offset before starting the animation
+ if (!self.navigationItem.titleView) {
LBMAnimatedTitleView *titleView = [[LBMAnimatedTitleView alloc] initWithTitle:@"Title" minimumScrollOffsetRequired:100];
self.navigationItem.titleView = titleView;
}
}
--(void)scrollViewDidScroll:(UIScrollView *)scrollView {
-
- //Send scroll offset updates to view
- if([self.navigationItem.titleView respondsToSelector:@selector(adjustLabelPositionToScrollOffset:)]) {
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
+ // Send scroll offset updates to view
+ if ([self.navigationItem.titleView respondsToSelector:@selector(adjustLabelPositionToScrollOffset:)]) {
[(XXXAnimatedTitleView *)self.navigationItem.titleView adjustLabelPositionToScrollOffset:scrollView.contentOffset.y];
}
}
diff --git a/Animated Title View/with_icon.gif b/Animated Title View/with_icon.gif
new file mode 100644
index 0000000..9b07a00
Binary files /dev/null and b/Animated Title View/with_icon.gif differ
diff --git a/Group Cell Header with Button/README.md b/Group Cell Header with Button/README.md
new file mode 100644
index 0000000..6f761f5
--- /dev/null
+++ b/Group Cell Header with Button/README.md
@@ -0,0 +1,43 @@
+# Group Cell Header With Button
+`PSGroupCell` with a button at the opposite of the header title
+
+## Preview
+
+
+ Preview of the PSGroupCell
+
+
+## Features
+- Native look
+- Works with RTL
+- Easily usable in a `.plist`
+
+## Usage
+```plist
+
+ cell
+ PSGroupCell
+ headerCellClass
+ XXXRefreshableHeaderCell
+ label
+ My label
+ actionLabel
+ Refresh
+ action
+ buttonAction:
+
+```
+You need to implement the method called by the button:
+```objective-c
+// MyController.m
+
+- (void)buttonAction:(id)sender {
+ // Actions after button triggering
+ // Note that the ':(id)sender' part
+ // isn't mandatory if you omit the ':'
+ // when declaring the action in the plist
+}
+```
+
+## Known issues
+- The cell will disappear if the table or one of its section is reloaded. It's an issue affecting every subclass of `PSTableCell `. See issue [here](https://github.com/hbang/libcephei/issues/53).
diff --git a/Group Cell Header with Button/XXXRefreshableHeaderCell.h b/Group Cell Header with Button/XXXRefreshableHeaderCell.h
new file mode 100644
index 0000000..fa7b97d
--- /dev/null
+++ b/Group Cell Header with Button/XXXRefreshableHeaderCell.h
@@ -0,0 +1,12 @@
+#import
+#import
+#import
+
+@interface UIColor (Private)
++ (id)_groupTableHeaderFooterTextColor;
+@end
+
+@interface XXXRefreshableHeaderCell : PSTableCell
+@property (nonatomic, strong) UILabel *label;
+@property (nonatomic, strong) UIButton *actionButton;
+@end
diff --git a/Group Cell Header with Button/XXXRefreshableHeaderCell.m b/Group Cell Header with Button/XXXRefreshableHeaderCell.m
new file mode 100644
index 0000000..d36c910
--- /dev/null
+++ b/Group Cell Header with Button/XXXRefreshableHeaderCell.m
@@ -0,0 +1,42 @@
+#import "XXXRefreshableHeaderCell.h"
+
+@implementation XXXRefreshableHeaderCell
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier specifier:(PSSpecifier *)specifier {
+ if (self = [super initWithStyle:style reuseIdentifier:nil specifier:specifier]) {
+ // Recreate main label
+ self.titleLabel.text = [localize(specifier.properties[@"label"], @"MoreSub") uppercaseString];
+ [self.titleLabel sizeToFit];
+ self.titleLabel.textColor = [UIColor _groupTableHeaderFooterTextColor];
+ self.titleLabel.font = [UIFont systemFontOfSize:13.f];
+ self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
+
+ // Button
+ self.actionButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ [self.actionButton setTitle:[specifier.properties[@"actionLabel"] uppercaseString] forState:UIControlStateNormal];
+ [self.actionButton setTitleColor:[self.actionButton.tintColor colorWithAlphaComponent:.5] forState:(UIControlStateHighlighted | UIControlStateSelected)];
+ self.actionButton.titleLabel.font = [self.actionButton.titleLabel.font fontWithSize:13.f];
+ [self.actionButton addTarget:specifier.target action:NSSelectorFromString(specifier.properties[@"action"]) forControlEvents:UIControlEventTouchUpInside];
+ self.actionButton.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.contentView addSubview:self.actionButton];
+
+ // Constraints
+ [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[label]-[action]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:@{ @"label" : self.titleLabel, @"action" : self.actionButton }]];
+ [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[label]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:@{ @"label" : self.titleLabel }]];
+ // For a reason the constraint needs to be inverted for the button
+ [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[action]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:@{ @"action" : self.actionButton }]];
+ }
+ return self;
+}
+
+- (instancetype)initWithSpecifier:(PSSpecifier *)specifier {
+ return self = [self initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil specifier:specifier];
+}
+
+#pragma mark - PSHeaderFooterView
+
+- (CGFloat)preferredHeightForWidth:(CGFloat)width {
+ return 38.f; // default height
+}
+
+@end
diff --git a/Group Cell Header with Button/preview.jpeg b/Group Cell Header with Button/preview.jpeg
new file mode 100644
index 0000000..66cb8ac
Binary files /dev/null and b/Group Cell Header with Button/preview.jpeg differ
diff --git a/README.md b/README.md
index 6071138..ae6c901 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,28 @@
# Preference Cell Examples
-*Custom preference cells among other things to improve your preferences.*
+> Custom preference cells among other things to improve your preferences.
## Table of Contents
| Preference Cell Type | Description | Preview |
| ------------------------------ | ----------- | ------- |
-| [Color Picker Cell](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Color%20Picker%20Cell) | Color picker using iOS 14's UIColorPickerViewController. |
|
-| [Labeled Slider Cell](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Labeled%20Slider%20Cell) | Slider with label centered above it, also allowing custom values to be entered by tapping the value displayed. |
|
-| [Segment Table Image Cell](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Segment%20Table%20Image%20Cell) | PSSegmentCell displaying images instead of strings. |
|
-| [Style Picker Cell](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Style%20Picker%20Cell) | Style picker for options, similar to Apple's light/dark mode selector. |
|
-| [Switch with Info Cell](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Switch%20with%20Info%20Cell) | Add info button next to switch to provide more in-depth info to the user. |
|
-| [Time Interval Picker Cell](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Time%20Interval%20Picker%20Cell) | Lets user select a number of hours, minutes, and seconds. |
|
+| [Color Picker Cell](Color%20Picker%20Cell) | Color picker using iOS 14's UIColorPickerViewController. |
|
+| [Labeled Slider Cell](Labeled%20Slider%20Cell) | Slider with label centered above it, also allowing custom values to be entered by tapping the value displayed. |
|
+| [Segment Table Image Cell](Segment%20Table%20Image%20Cell) | PSSegmentCell displaying images instead of strings. |
|
+| [Style Picker Cell](Style%20Picker%20Cell) | Style picker for options, similar to Apple's light/dark mode selector. |
|
+| [Switch with Info Cell](Switch%20with%20Info%20Cell) | Add info button next to switch to provide more in-depth info to the user. |
|
+| [Time Interval Picker Cell](Time%20Interval%20Picker%20Cell) | Lets user select a number of hours, minutes, and seconds. |
|
-
+
## Other Guides Included
| Guide Type | Description | Preview |
| --------------- | ----------- | ------- |
-| [Animated Title View](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Animated%20Title%20View) | Title label in the navigation bar that 'slides up' when the user scrolls down. |
|
-| [Dynamic Specifiers](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Dynamic%20Specifiers) | Remove or insert specifiers based on other preference values. |
|
-| [Footer Hyperlink](https://github.com/LacertosusRepo/Preference-Cell-Examples/tree/main/Footer%20Hyperlink) | Insert a hyperlink in your footer text. |
|
+| [Animated Title View](Animated%20Title%20View) | Title label in the navigation bar that 'slides up' when the user scrolls down. |
|
+| [Animated Title View With Icon](Animated%20Title%20View) | Title label and icon in the navigation bar that 'slide up' when the user scrolls down. |
|
+| [Animated Banner](Animated%20Banner) | Pretty animated banner with a bunch of features. |
|
+| [Dynamic Specifiers](Dynamic%20Specifiers) | Remove or insert specifiers based on other preference values. |
|
+| [Footer Hyperlink](Footer%20Hyperlink) | Insert a hyperlink in your footer text. |
|
+| [Group Cell Header with Button](Group%20Cell%20Header%20with%20Button) | Add a button for your PSGroupCell header. |
|
-
+
diff --git a/SF Symbols Link Cell/README.md b/SF Symbols Link Cell/README.md
new file mode 100644
index 0000000..bfe97c5
--- /dev/null
+++ b/SF Symbols Link Cell/README.md
@@ -0,0 +1,80 @@
+# SFSymbols Link Cell
+Allow you to use `PSLinkCell`s that have an [SFSymbol](https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/) as icon. (In theory usable with any specifier that supports `icon`)
+
+## Availability
+Already available on Cephei ([with my contribution]()!) since v1.1, so it's not needed if you do use it.
+
+Available for iOS 13+. Will fallback to an image with the same name for versions below, as an usual `PSLinkCell`. Make sure to import images with the same name as the symbol (x1/@2x/@3x) in the same folder as the `plist` file if you want to support older versions.
+
+You can find out which Symbols are available by using [this Mac app](https://developer.apple.com/sf-symbols/) or [downloading this app](https://github.com/MTACS/Symbols) on your jailbroken device.
+
+## How to use
+- Import the `XXXSymbolsLinkCell` files here
+- Use it in your `.plist` file as below:
+```plist
+
+
+
+ cell
+ PSLinkCell
+ cellClass
+ XXXSymbolsLinkCell
+ icon
+ YOUR_ICON_NAME
+ label
+ YOUR_LABEL
+ detail
+ YOUR_CONTROLLER
+ YOUR_PREFIX
+ PLIST_TO_LOAD
+
+
+
+
+ cell
+ PSLinkCell
+ cellClass
+ ABCSymbolsLinkCell
+ icon
+ circle.lefthalf.fill
+ label
+ Light and Dark Mode
+ detail
+ ABCSubListController
+ ABCSub
+ LightDarkSub
+
+```
+
+
+
+ Render of the example
+
+
+## Available options
+Customize your cell icon with these premade options.
+
+key | type | values | default
+:---:|:---:|:---:|:---:
+weight | string | `ultrathin`, `light`, `thin`, `regular`, `medium`, `semibold`, `bold`, `heavy` or `black` | `regular`
+size | real | - | `29` (PSLinkCell default)
+color | string | an hex value (e.g.: `#ab76e9`) | `#007aff` (system blue)
+||| a [system color name](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color/#system-colors) (`blue`, `green`, `indigo`, [...], `gray6`) | `blue`
+darkColor* | string | an hex value (e.g.: `#ab76e9`) | `#0a84ff` (system 'dark' blue)
+||| a [system color name](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color/#system-colors) (`blue`, `green`, `indigo`, [...], `gray6`) | `blue`
+
+> *note that `darkColor` is used when the system **is in dark mode**. It will be ignored if `color` isn't specified.
+
+## Preview
+
+
+ Cell with default settings
+
+
+
+ Cell with several options
+
+
+
+ Support for dynamic colors
+
diff --git a/SF Symbols Link Cell/XXXSymbolsLinkCell.h b/SF Symbols Link Cell/XXXSymbolsLinkCell.h
new file mode 100644
index 0000000..f456fea
--- /dev/null
+++ b/SF Symbols Link Cell/XXXSymbolsLinkCell.h
@@ -0,0 +1,14 @@
+#import
+
+@interface UIColor (Private)
++ (UIColor *)_systemColorWithName:(NSString *)arg1;
+@end
+
+@interface UIColor (SymbolsLinkCell)
++ (UIColor *)systemColorFromString:(NSString *)colorString;
++ (UIColor *)colorFromHexString:(NSString *)hex;
+@end
+
+@interface XXXSymbolsLinkCell : PSTableCell
+@property (nonatomic, strong) UIImage *symbolsImage;
+@end
diff --git a/SF Symbols Link Cell/XXXSymbolsLinkCell.m b/SF Symbols Link Cell/XXXSymbolsLinkCell.m
new file mode 100644
index 0000000..fcf61fd
--- /dev/null
+++ b/SF Symbols Link Cell/XXXSymbolsLinkCell.m
@@ -0,0 +1,134 @@
+#import "XXXSymbolsLinkCell.h"
+
+@implementation UIColor (SymbolsLinkCell)
+
++ (UIColor *)systemColorFromString:(NSString *)colorString {
+ NSString *finalColorString = [NSString stringWithFormat:@"system%@Color", [colorString capitalizedString]];
+
+ if ([UIColor respondsToSelector:@selector(_systemColorWithName:)]) {
+ return [UIColor _systemColorWithName:finalColorString];
+ } else {
+ SEL colorSelector = NSSelectorFromString(finalColorString);
+ if ([UIColor respondsToSelector:colorSelector]) {
+ return [UIColor performSelector:colorSelector];
+ }
+ }
+
+ return nil;
+}
+
+// https://stackoverflow.com/a/12397366/12070367
++ (UIColor *)colorFromHexString:(NSString *)hex {
+ hex = [hex stringByReplacingOccurrencesOfString:@"#" withString:@""];
+ NSCharacterSet *hexChars = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEF"] invertedSet];
+ if ([[hex uppercaseString] rangeOfCharacterFromSet:hexChars].location != NSNotFound) {
+ NSLog(@"[%@] Argument is not a valid hexadecimal color, returning", NSStringFromSelector(_cmd));
+ return nil;
+ }
+
+ unsigned intValue = 0;
+ [[NSScanner scannerWithString:hex] scanHexInt:&intValue];
+ return [UIColor colorWithRed:((intValue & 0xFF0000) >> 16) / 255.0
+ green:((intValue & 0xFF00) >> 8) / 255.0
+ blue:(intValue & 0xFF) / 255.0
+ alpha:1.0];
+}
+
+@end
+
+@implementation XXXSymbolsLinkCell
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier specifier:(PSSpecifier *)specifier {
+ if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier specifier:specifier]) {
+ if (@available(iOS 13.0, *)) {
+ // Checking availability and presence of icon property
+ if (!specifier.properties[@"icon"]) {
+ NSLog(@"[%@ %p] No icon found, falling back to default behavior", NSStringFromClass(self.class), self);
+ return self;
+ }
+
+ // Applying SF Symbol
+ if ((self.symbolsImage = [UIImage systemImageNamed:specifier.properties[@"icon"]])) {
+ NSLog(@"[%@ %p] Applied SF Symbol: %@", NSStringFromClass(self.class), self, specifier.properties[@"icon"]);
+ } else {
+ NSLog(@"[%@ %p] SF Symbol \"%@\" not found, falling back to default behavior", NSStringFromClass(self.class), self, specifier.properties[@"icon"]);
+ return self;
+ }
+
+ CGFloat pointSize = specifier.properties[@"size"] ? [specifier.properties[@"size"] floatValue] : 29.f;
+
+ // Setting custom weight if requested
+ if (specifier.properties[@"weight"]) {
+ NSString *weightString = [specifier.properties[@"weight"] lowercaseString];
+ UIImageSymbolWeight weight;
+ if ([weightString isEqualToString:@"ultralight"]) {
+ weight = UIImageSymbolWeightUltraLight;
+ } else if ([weightString isEqualToString:@"thin"]) {
+ weight = UIImageSymbolWeightThin;
+ } else if ([weightString isEqualToString:@"light"]) {
+ weight = UIImageSymbolWeightLight;
+ } else if ([weightString isEqualToString:@"regular"]) {
+ weight = UIImageSymbolWeightRegular;
+ } else if ([weightString isEqualToString:@"medium"]) {
+ weight = UIImageSymbolWeightMedium;
+ } else if ([weightString isEqualToString:@"semibold"]) {
+ weight = UIImageSymbolWeightSemibold;
+ } else if ([weightString isEqualToString:@"bold"]) {
+ weight = UIImageSymbolWeightBold;
+ } else if ([weightString isEqualToString:@"heavy"]) {
+ weight = UIImageSymbolWeightHeavy;
+ } else if ([weightString isEqualToString:@"black"]) {
+ weight = UIImageSymbolWeightBlack;
+ } else {
+ weight = UIImageSymbolWeightUnspecified;
+ }
+
+ UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:pointSize weight:weight scale:UIImageSymbolScaleSmall];
+ self.symbolsImage = [self.symbolsImage imageWithConfiguration:config];
+ NSLog(@"[%@ %p] Applied weight configuration from weight \"%@\" and size %.f", NSStringFromClass(self.class), self, weightString, pointSize);
+ } else {
+ UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:pointSize weight:UIImageSymbolWeightUnspecified scale:UIImageSymbolScaleSmall];
+ self.symbolsImage = [self.symbolsImage imageWithConfiguration:config];
+ NSLog(@"[%@ %p] Applied size configuration with size %.f", NSStringFromClass(self.class), self, pointSize);
+ }
+
+ // Setting custom color if requested
+ if (specifier.properties[@"color"]) {
+ UIColor *finalColor = nil;
+
+ NSString *stringColor = [specifier.properties[@"color"] lowercaseString];
+ UIColor *color = [UIColor systemColorFromString:stringColor] ?: [UIColor colorFromHexString:stringColor];
+ if (color) {
+ if (specifier.properties[@"darkColor"]) {
+ NSString *stringDarkColor = [specifier.properties[@"darkColor"] lowercaseString];
+ UIColor *darkColor = [UIColor systemColorFromString:stringDarkColor] ?: [UIColor colorFromHexString:stringDarkColor];
+ finalColor = [UIColor colorWithDynamicProvider:^UIColor * (UITraitCollection *traits) {
+ return traits.userInterfaceStyle == UIUserInterfaceStyleLight ? color : darkColor;
+ }];
+ } else {
+ finalColor = color;
+ }
+ }
+
+ if (finalColor) {
+ self.symbolsImage = [self.symbolsImage imageWithTintColor:finalColor renderingMode:UIImageRenderingModeAlwaysOriginal];
+ NSLog(@"[%@ %p] Color changed, now %@", NSStringFromClass(self.class), self, [[CIColor colorWithCGColor:finalColor.CGColor] stringRepresentation]);
+ } else {
+ NSLog(@"[%@ %p] Error when changing color (color: %@, darkColor: %@)", NSStringFromClass(self.class), self, color, specifier.properties[@"darkColor"]);
+ }
+ }
+ }
+ }
+
+ return self;
+}
+
+- (void)refreshCellContentsWithSpecifier:(PSSpecifier *)specifier {
+ [super refreshCellContentsWithSpecifier:specifier];
+
+ if (self.imageView && self.symbolsImage && self.imageView.image != self.symbolsImage) {
+ self.imageView.image = self.symbolsImage;
+ }
+}
+
+@end
diff --git a/SF Symbols Link Cell/images/custom.jpeg b/SF Symbols Link Cell/images/custom.jpeg
new file mode 100644
index 0000000..21360c1
Binary files /dev/null and b/SF Symbols Link Cell/images/custom.jpeg differ
diff --git a/SF Symbols Link Cell/images/default.JPEG b/SF Symbols Link Cell/images/default.JPEG
new file mode 100644
index 0000000..bed2dc5
Binary files /dev/null and b/SF Symbols Link Cell/images/default.JPEG differ
diff --git a/SF Symbols Link Cell/images/example.JPEG b/SF Symbols Link Cell/images/example.JPEG
new file mode 100644
index 0000000..458f739
Binary files /dev/null and b/SF Symbols Link Cell/images/example.JPEG differ
diff --git a/SF Symbols Link Cell/images/light_dark.GIF b/SF Symbols Link Cell/images/light_dark.GIF
new file mode 100644
index 0000000..ef01c6f
Binary files /dev/null and b/SF Symbols Link Cell/images/light_dark.GIF differ