ChenYilong / CYLTabBarController

[EN]It is an iOS UI module library for adding animation to iOS tabbar items and icons with Lottie, and adding a bigger center UITabBar Item. [CN]【中国特色 TabBar】一行代码实现 Lottie 动画TabBar,支持中间带+号的TabBar样式,自带红点角标,支持动态刷新。【iOS13 & Dark Mode & iPhone XS MAX supported】
MIT License
6.91k stars 1.45k forks source link

一点建议,仅供参考 #535

Open 295060456 opened 3 years ago

295060456 commented 3 years ago
  1. 项目比较臃肿,比较庞大,里面有些废弃的api 调用没做处理;
  2. 期望解耦,动画的 这些功能性的单独拿个类列出来。比如 pod 'PPBadgeView' #https://github.com/jkpang/PPBadgeView iOS自定义Badge组件, 支持UIView, UITabBarItem, UIBarButtonItem以及子类,又比如Lottie,Lottie在2.5.3以后就全部swift了。按照功能分离原则也应该提取出来,不应该鱼龙混杂, 代码越多,干扰越大,bug就越多; 我想进行替换就进行替换,插件式管理;
  3. 不要用基类继承,有入侵性,GKNavigationBar 去年就是GKNavigationController,因为入侵性很大所以被抛弃。意味着不继承就用不了;因为我见你项目里面有CYLBaseNavigationController、CYLBaseTableViewController、CYLBaseViewController...
  4. 我现在都统一用GKNavigationBar,就是因为系统的不舒服。我为什么单独列这个话题,因为按道理你的demo里面

    //    [firstViewController cyl_setHideNavigationBarSeparator:YES];
    //     [firstViewController cyl_setNavigationBarHidden:YES];

是开启和关闭系统的Navigationbar,但是我的项目中失灵,找了很久,才找到解决方案:

- (void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    [self.navigationController setNavigationBarHidden:YES animated:NO];
    [self.parentViewController.navigationController setNavigationBarHidden:YES animated:NO];
    CYLBaseNavigationController *vc =  (CYLBaseNavigationController *)  self.parentViewController;
    vc.navigationController.navigationBarHidden = YES;
    self.navigationController.parentViewController.navigationController.navigationBarHidden = YES;
}

我看了你这里面写的很复杂,不熟悉项目的人真的比较头疼,面临较大学习成本; viewWillLayoutSubviews是最后刷新的时候调用,我在之前设置了:

    [self.navigationController setNavigationBarHidden:YES animated:NO];
    [self.parentViewController.navigationController setNavigationBarHidden:YES animated:NO];

均不起作用,肯定是中间过程出问题,要么就只有改你的源码,这样不好 其实自定义tabbar有很多种写法,我以前也有一个框架,没挂在github上,主要是这次需要集成lottie我才想到你这个,你这个的star蛮多的,我看了下,其他人的就目前的水平都不如你。所以希望升级有个方向;

ChenYilong commented 3 years ago

很棒的建议; 我参考你的建议, 梳理下思路;

ChenYilong commented 3 years ago

"Lottie在2.5.3以后就全部swift"


Lottie我打算升级到最新版, 不过还是以pod依赖库的形式, 跟目前的集成方式没有太大区别

295060456 commented 3 years ago

对了,还忘记一点,中间的那个,不要区别对待。系统的tabbaritem 虽然不是UIButton,但是他两是亲兄弟,都是继承自UIControl。所以其他按钮是什么类型,中间的那个就是什么类型,只不过frame不一样,点击区域不一样罢了。view和vc不是都有最终的刷新调用的方法吗。viewWillLayoutSubviews 和 layoutSubviews

还有增加一个功能,点击的时候有声音,当然有没有声音这个是熟悉暴露出来,让用户选择,默认没有声音。 还有个就是震动反馈,同样是和声音一样处理方案; 这里我贴下我的方案: 都是点击的时候调用

声音部分:

[PlaySound playSoundEffect:@"Sound" type:@"wav"];

#import "PlaySound.h"

#import <AudioToolbox/AudioToolbox.h>

@implementation PlaySound

+ (void)playSoundEffect:(NSString*)name
                   type:(NSString*)type{

    SystemSoundID soundFileObject;

    //得到音效文件的地址
    NSString *soundFilePath = [[NSBundle mainBundle]pathForResource:name ofType:type];

    //将地址字符串转换成url
    NSURL *soundURL = [NSURL fileURLWithPath:soundFilePath];

    //生成系统音效id
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL, &soundFileObject);

    //播放系统音效
    AudioServicesPlaySystemSound(soundFileObject);
}

@end

震动部分:

//震动特效反馈
    [NSObject feedbackGenerator];

#import "NSObject+Extras.h"

@implementation NSObject (Extras)
///震动特效反馈
+(void)feedbackGenerator{
    if (@available(iOS 10.0, *)) {
        UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
        [generator prepare];
        [generator impactOccurred];
    } else {
        // Fallback on earlier versions
        AudioServicesPlaySystemSound(1520);
    }
}
///检测用户是否锁屏:根据屏幕光线来进行判定,而不是系统通知
+(BOOL)didUserPressLockButton{
    //获取屏幕亮度
    CGFloat oldBrightness = [UIScreen mainScreen].brightness;
    //以较小的数量改变屏幕亮度
    [UIScreen mainScreen].brightness = oldBrightness + (oldBrightness <= 0.01 ? (0.01) : (-0.01));
    CGFloat newBrightness  = [UIScreen mainScreen].brightness;
    //恢复屏幕亮度
    [UIScreen mainScreen].brightness = oldBrightness;
    //判断屏幕亮度是否能够被改变
    return oldBrightness != newBrightness;
}
///iOS 限制自动锁屏 lockSwitch:YES(关闭自动锁屏)
+(void)autoLockedScreen:(BOOL)lockSwitch{
    [[UIApplication sharedApplication] setIdleTimerDisabled:lockSwitch];
}

@end
295060456 commented 3 years ago

一些参考demo https://github.com/709213219/TabbarItemLottie https://www.jianshu.com/p/60d2e37b88d3

系统的最好怀着敬畏的心情,不抛弃和不鼓励

295060456 commented 3 years ago
///iOS 子视图超出父视图不响应解决办法
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIView (Chain)

@property(nonatomic,assign)BOOL ableRespose;

@end

NS_ASSUME_NONNULL_END

/**
 使用的时候将需要作用的View的ableRespose设置为YES即可
 */

#import "UIView+Chain.h"

@implementation UIView (Chain)

+ (void)load {
    Class class = self;
    Method originMethod = class_getInstanceMethod(class, @selector(hitTest:withEvent:));
    Method targetMethod = class_getInstanceMethod(class, @selector(exchange_hitTest:withEvent:));
    if (!originMethod || !targetMethod) {
        NSLog(@"交换失败");
        return;
    }
    BOOL didAddMethod = class_addMethod(class,
                                        @selector(hitTest:withEvent:),
                                        method_getImplementation(targetMethod),
                                        method_getTypeEncoding(targetMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            @selector(exchange_hitTest:withEvent:),
                            method_getImplementation(originMethod),
                            method_getTypeEncoding(originMethod));
    } else {
        method_exchangeImplementations(originMethod, targetMethod);
    }
}

- (UIView *)exchange_hitTest:(CGPoint)point
                   withEvent:(UIEvent *)event {
    UIView * view = [self exchange_hitTest:point
                                 withEvent:event];
    if (view) {
        return view;
    } else {
        for (UIView * v in self.subviews) {
            if (v.ableRespose) {
                if (CGRectContainsPoint(v.frame, point)) {
                    return v;
                }
            }
        }return nil;
    }
}

- (void)setAbleRespose:(BOOL)ableRespose {
    objc_setAssociatedObject(self,
                             @selector(ableRespose),
                             @(ableRespose),
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)ableRespose {
    return objc_getAssociatedObject(self, _cmd) != nil ? [objc_getAssociatedObject(self, _cmd) boolValue] : NO;
}

@end
ChenYilong commented 3 years ago

超赞的建议, 我最近刚好要上一个版本, 如果时间赶得上, 我把你的这些建议整理下一并上线, 也可以加我 vx : chenyilong1010 详聊

295060456 commented 3 years ago
///点击放大再缩小
+ (void)addViewAnimation:(UIView *)sender
         completionBlock:(MKDataBlock)completionBlock{
    sender.transform = CGAffineTransformIdentity;
    [UIView animateKeyframesWithDuration:0.5
                                   delay:0
                                 options:0
                              animations: ^{
        [UIView addKeyframeWithRelativeStartTime:0
                                relativeDuration:1 / 3.0
                                      animations: ^{
            sender.transform = CGAffineTransformMakeScale(1.5, 1.5);
        }];
        [UIView addKeyframeWithRelativeStartTime:1/3.0
                                relativeDuration:1/3.0
                                      animations: ^{
            sender.transform = CGAffineTransformMakeScale(0.8, 0.8);
        }];
        [UIView addKeyframeWithRelativeStartTime:2/3.0
                                relativeDuration:1/3.0
                                      animations: ^{

            sender.transform = CGAffineTransformMakeScale(1.0, 1.0);
        }];
    } completion:^(BOOL finished) {
        if (completionBlock) {
            completionBlock(@1);
        }
    }];
}
295060456 commented 3 years ago
//
//  LoadingImage.h
//  TFRememberHistoryInputContentWithDropList
//
//  Created by Jobs on 2020/9/29.
//  Copyright © 2020 Jobs. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "NSString+Extras.h"

/// 直接拖图片在项目文件夹,没用Bundle进行管理,也没有用Assets.xcassets
/// @param imgName 文件可以不强制要求带后缀名,系统会自动识别png文件
static inline UIImage *__nullable KIMG(NSString *__nonnull imgName){
    return [UIImage imageNamed:imgName];
}
/// 读取自定义Bundle文件里面的图片 输出 NSString *
/// @param bundle_folderName 如果在此自定义Bundle下还存在文件夹,不管几级都在此写,属于中间路径,函数内部是进行字符串拼接
/// @param pathForResource 自定义 Bundle 的名字
/// @param fileFullNameWithSuffix 目标图片的名字,如果不带后缀名,函数内部直接强制加 @".png" 后缀
/// @param blueFolderName 如果资源存在于蓝色文件夹下则blueFolderName是蓝色文件夹的名字,如果资源位于黄色文件夹下则不填
static inline NSString *__nullable pathForBuddleIMG(NSString *__nullable blueFolderName,
                                                    NSString *__nonnull pathForResource,
                                                    NSString *__nullable bundle_folderName,
                                                    NSString *__nonnull fileFullNameWithSuffix){
    NSString *filePath = nil;
    if ([NSString isNullString:blueFolderName]) {
        filePath = [[NSBundle mainBundle] pathForResource:pathForResource
                                                   ofType:@"bundle"];
    }else{
        filePath = [[NSBundle mainBundle] pathForResource:pathForResource
                                                   ofType:@"bundle"
                                              inDirectory:blueFolderName];
    }

    if (![NSString isNullString:bundle_folderName]) {
        filePath = [filePath stringByAppendingPathComponent:bundle_folderName];
    }

    //容错处理
    if (![fileFullNameWithSuffix containsString:@"."]) {
        fileFullNameWithSuffix = [fileFullNameWithSuffix stringByAppendingString:@".png"];
    }

    filePath = [filePath stringByAppendingPathComponent:fileFullNameWithSuffix];
    return filePath;
}
/// 读取自定义Bundle文件里面的图片 输出 UIImage *
/// @param bundle_folderName 如果在此自定义Bundle下还存在文件夹,不管几级都在此写,属于中间路径,函数内部是进行字符串拼接
/// @param pathForResource 自定义 Bundle 的名字
/// @param fileFullNameWithSuffix 目标图片的名字,如果不带后缀名,函数内部直接强制加 @".png" 后缀
/// @param blueFolderName 如果资源存在于蓝色文件夹下则blueFolderName是蓝色文件夹的名字,如果资源位于黄色文件夹下则不填
static inline UIImage *__nullable KBuddleIMG(NSString *__nullable blueFolderName,
                                             NSString *__nonnull pathForResource,
                                             NSString *__nullable bundle_folderName,
                                             NSString *__nonnull fileFullNameWithSuffix){

    NSString *filePath = pathForBuddleIMG(blueFolderName, pathForResource, bundle_folderName, fileFullNameWithSuffix);
    UIImage *image = [UIImage imageWithContentsOfFile:filePath];
    return image;
}
/// 读取自定义Bundle文件里面的图片 输出 NSData *
/// @param pathForResource 自定义 Bundle 的名字
/// @param bundle_folderName 如果在此自定义Bundle下还存在文件夹,不管几级都在此写,属于中间路径,函数内部是进行字符串拼接
/// @param fileFullNameWithSuffix  目标图片的名字,如果不带后缀名,函数内部直接强制加 @".png" 后缀
/// @param blueFolderName 如果资源存在于蓝色文件夹下则blueFolderName是蓝色文件夹的名字,如果资源位于黄色文件夹下则不填
static inline NSData *__nullable KDataByBuddleIMG(NSString *__nonnull pathForResource,
                                                  NSString *__nullable blueFolderName,
                                                  NSString *__nullable bundle_folderName,
                                                  NSString *__nonnull fileFullNameWithSuffix){
    NSString *filePath = pathForBuddleIMG(blueFolderName, pathForResource, bundle_folderName, fileFullNameWithSuffix);
    NSData *imgData = [NSData dataWithContentsOfFile:filePath];
    return imgData;
}

static inline UIImage *__nullable KIMGByDataFromBuddleIMG(NSString *__nonnull pathForResource,
                                                          NSString *__nullable blueFolderName,
                                                          NSString *__nullable bundle_folderName,
                                                          NSString *__nonnull fileFullNameWithSuffix){
    UIImage *image = [UIImage imageWithData:KDataByBuddleIMG(pathForResource,
                                                             blueFolderName,
                                                             bundle_folderName,
                                                             fileFullNameWithSuffix)];
    return image;
}
  1. imageNamed,其参数为图片的名字。 这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。 如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。 因此imageNamed的优点是当加载时会缓存图片。 所以当图片会频繁的使用时,那么用imageNamed的方法会比较好。 例如:你需要在 一个TableView里的TableViewCell里都加载同样一个图标,那么用imageNamed加载图像效率很高。 系统会把那个图标Cache到内存,在TableViewCell里每次利用那个图 像的时候,只会把图片指针指向同一块内存。 正是因此使用imageNamed会缓存图片,即将图片的数据放在内存中,iOS的内存非常珍贵并且在内存消耗过大时,会强制释放内存,即会遇到 memory warnings。 而在iOS系统里面释放图像的内存是一件比较麻烦的事情,有可能会造成内存泄漏。 例如:当一个UIView对象的animationImages是一个装有UIImage对象动态数组NSMutableArray,并进行逐帧动画。 当使用imageNamed的方式加载图像到一个动态数组NSMutableArray,这将会很有可能造成内存泄露。 原因很显然的。

  2. imageWithContentsOfFile,其参数也是图片文件的路径。 仅加载图片,图像数据不会缓存。 因此对于较大的图片以及使用情况较少时,那就可以用该方法,降低内存消耗。


这个是存取图片的。内联函数管理,不宏定义,减少编译替换。 用户的图你不能固定他只放在一个地方。最大限度开放

295060456 commented 3 years ago

1、支持图片和lottie; 2、支持长按手势和短按手势; 3、支持自定义tabbar高度,外界至少可以用属性或者extern的形式拿到这个高度; 4、记住不要用单例;个别场景是需要销毁的。单例没法走dealloc; 5、可以自定义tabbar的背景色,乃至于背景色 = 一张图; 6、可以自定义字体大小颜色等,给默认值。用户传的值优先于默认值; 7、支持url资源; 8、点击以后有震动反馈和声音。声音给默认值; ... 暂时想到这么多。

alienjun commented 3 years ago
  1. 项目比较臃肿,比较庞大,里面有些废弃的api 调用没做处理;
  2. 期望解耦,动画的 这些功能性的单独拿个类列出来。比如 pod 'PPBadgeView' #https://github.com/jkpang/PPBadgeView iOS自定义Badge组件, 支持UIView, UITabBarItem, UIBarButtonItem以及子类,又比如Lottie,Lottie在2.5.3以后就全部swift了。按照功能分离原则也应该提取出来,不应该鱼龙混杂, 代码越多,干扰越大,bug就越多; 我想进行替换就进行替换,插件式管理;
  3. 不要用基类继承,有入侵性,GKNavigationBar 去年就是GKNavigationController,因为入侵性很大所以被抛弃。意味着不继承就用不了;因为我见你项目里面有CYLBaseNavigationController、CYLBaseTableViewController、CYLBaseViewController...
  4. 我现在都统一用GKNavigationBar,就是因为系统的不舒服。我为什么单独列这个话题,因为按道理你的demo里面
//    [firstViewController cyl_setHideNavigationBarSeparator:YES];
//     [firstViewController cyl_setNavigationBarHidden:YES];

是开启和关闭系统的Navigationbar,但是我的项目中失灵,找了很久,才找到解决方案:

- (void)viewWillLayoutSubviews{
   [super viewWillLayoutSubviews];
   [self.navigationController setNavigationBarHidden:YES animated:NO];
   [self.parentViewController.navigationController setNavigationBarHidden:YES animated:NO];
   CYLBaseNavigationController *vc =  (CYLBaseNavigationController *)  self.parentViewController;
   vc.navigationController.navigationBarHidden = YES;
   self.navigationController.parentViewController.navigationController.navigationBarHidden = YES;
}

我看了你这里面写的很复杂,不熟悉项目的人真的比较头疼,面临较大学习成本; viewWillLayoutSubviews是最后刷新的时候调用,我在之前设置了:

   [self.navigationController setNavigationBarHidden:YES animated:NO];
   [self.parentViewController.navigationController setNavigationBarHidden:YES animated:NO];

均不起作用,肯定是中间过程出问题,要么就只有改你的源码,这样不好 其实自定义tabbar有很多种写法,我以前也有一个框架,没挂在github上,主要是这次需要集成lottie我才想到你这个,你这个的star蛮多的,我看了下,其他人的就目前的水平都不如你。所以希望升级有个方向;

同感啊,看着star很多,着急赶项目想直接用,但是实际使用发现过度复杂了,不得已又自己写了,但是也来反馈一下

ChenYilong commented 3 years ago

@alienjun 你是源码集成吧,然后想魔改仓库代码吧。目前的代码确实对源码集成,又想魔改的不太方便。目前版本只对CocoaPods集成方式友好。以后考虑拆分下,减少不同功能模块间的耦合。

295060456 commented 3 years ago

swift package 了解一下 可以替代pod

alienjun commented 3 years ago

@alienjun 你是源码集成吧,然后想魔改仓库代码吧。目前的代码确实对源码集成,又想魔改的不太方便。目前版本只对CocoaPods集成方式友好。以后考虑拆分下,减少不同功能模块间的耦合。

用pod 集成的, 只是用起来遇到问题想看源码 ,发现过度复杂我怕再出现问题难以查找修复, 但项目又不能等作者修复bug

295060456 commented 3 years ago

@alienjun你是二进制集成吧,然后想魔改仓库代码吧。当前的代码确实对内核集成,又想魔改的不太方便。目前版本只对CocoaPods集成方式友好。以后考虑扩展下,减少不同功能模块间的重组。

用pod集成的,只是用起来遇到问题想看二进制,发现过度复杂我怕再出现问题难以找到修复,但项目又不能等作者修复bug

你用我写的啊,https://github.com/295060456/TabbarItemLottie