preface
- Simple call reminder animation effect, live gift display
. The Demo address
API & Property
@property(nonatomic,assign)NSInteger maxCount; /// Automatically disappear time, default 5s @property(nonatomic,assign)CGFloat vanishTime; // whether to allow repetition, default NO @property(nonatomic,assign)BOOL repetition; /// NO @property(nonatomic,assign)BOOL tapVanish; // create singleton + (instanceType)kj_shareInstance; // Add call message, - (void)kj_addCallNotify:(void(^)(KJCallNotifyInfo *info))block RepetitionCondition:(bool(^_Nullable)(KJCallNotifyInfo *info))condition; - (void)kj_tapBlock:(void(^)(KJCallNotifyInfo *info))block;Copy the code
A brief introduction
KJCallNotifyInfo Data model
Declare a data model that accepts the outside world. For now, I just need the image address, name, and unique ID.
@interface KJCallNotifyInfo : NSObject
@property(nonatomic,strong)NSString *imageUrl;
@property(nonatomic,strong)NSString *name;
@property(nonatomic,strong)NSString *userid;
@end
Copy the code
The singleton pattern
Because this thing needs to be there all the time and at the top, I use singletons
static KJCallNotifyView *_instance = nil; + (instancetype)kj_shareInstance{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (_instance == nil) { _instance = [[KJCallNotifyView alloc] initWithFrame:CGRectMake(0, 0, kScreenW, kScreenH)]; [kKeyWindow addSubview:_instance]; }}); return _instance; }Copy the code
Of course, you can not use this singleton, as follows
__block KJCallNotifyView *view = [[KJCallNotifyView alloc]initWithFrame:CGRectMake(0, 64, kScreenW, kScreenH-64)]; [self.view addSubview:view]; view.maxCount = 5; view.vanishTime = 5; [view kj_tapBlock:^(KJCallNotifyInfo * _Nonnull info) { }]; __block NSInteger index = 1000; NSArray *names = @[@"Sone",@" painful faith ",@"X"]; [button kj_addAction:^(UIButton * _Nonnull kButton) { [view kj_addCallNotify:^(KJCallNotifyInfo * _Nonnull info) { info.imageUrl = @"xxsf"; info.userid = [NSString stringWithFormat:@"%ld",index]; info.name = names[1]; } RepetitionCondition:nil]; }];Copy the code
Gesture penetration processing
This does not affect subsequent interactions
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event{
NSInteger count = self.subviews.count;
for (int i = 0; i < count; i++){
UIView *childView = self.subviews[count - 1 - I];
CGPoint childPoint = [self convertPoint:point toView:childView];
UIView *view = [childView hitTest:childPoint withEvent:event];
if (view) return view;
}
return nil;
}
Copy the code
KJCallView
Call style UI handling
@implementation KJCallView - (instancetype)kj_initWithFrame:(CGRect)frame Name:(NSString*)name{ if (self==[super initWithFrame:frame]) { self.centerX = kScreenW/2; self.backgroundColor = UIColor.whiteColor; self.cornerRadius = kAutoH(48)/2; Self. shadowColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.16]; Self. ShadowOffset = CGSizeMake (0, 3); self.shadowRadius = 6; self.shadowOpacity = 1; CGFloat height = self.height; self.imageView = [[UIImageView alloc]initWithFrame:CGRectMake(5, 5, height-10, height-10)]; self.imageView.cornerRadius = (height-10)/2; [self addSubview:self.imageView]; self.button = [UIButton kj_createButtonWithImageName:@"button_like_norm"]; self.button.frame = CGRectMake(self.width-18-8, 0, 18, 18); self.button.centerY = self.height/2; [self addSubview:self.button]; self.label = [UILabel kj_createLabelWithText:name]; [self addSubview:self.label]; Self.label. textColor = [UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0]; self.label.textColor = [UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0]; self.label.font = [UIFont fontWithName:@"PingFang SC" size:14]; CGFloat width = [self.label kj_calculateWidth]; CGFloat maxw = self.button.x - self.imageView.maxX - 10 - 23 - 10; if (width>=maxw) width = maxw; self.label.frame = CGRectMake(self.imageView.maxX+10, 0, width, self.height); Self.tvimageview = [[UIImageView alloc]initWithFrame:CGRectMake(self.label.maxx +2.5, 0, 23, 22)]; self.tvImageView.image = kGetImage(@"wode_nor"); self.tvImageView.centerY = self.height/2; [self addSubview:self.tvImageView]; } return self; } - (void)kj_invalidateTimer{ [_timer invalidate]; _timer = nil; } @endCopy the code
Click on the event
- (void)kj_tapBlock:(void(^)(KJCallNotifyInfo *info))block{
self.tapblock = block;
}
Copy the code
Click callback, passing the logic to handle the click
Adding an Incoming call message
Introduces the main methods and emphasis, displayCount: the current display UI number attribute temps: data storage model viewTemps: store call display UI data kj_addCallNotify: RepetitionCondition: : Kj_viewIndex :Info: : Creates call UI kj_displayEnd: : Displays end kj_autoVanish: : Kj_vanish: : Vanish: kj_changeIndex:Y: : Modifies call coordinates recursively
A, kj_addCallNotify: RepetitionCondition:
Add the call message, self.repetition controls whether the call is repeated, and the internal condition determines whether the call is repeated according to the userID
- (void)kj_addCallNotify:(void(^)(KJCallNotifyInfo *))block RepetitionCondition:(bool(^_Nullable)(KJCallNotifyInfo *))condition{ KJCallNotifyInfo *info = [KJCallNotifyInfo new]; if (block) block(info); if (self.repetition == NO) { __block bool skip = false; if (condition) { for (KJCallNotifyInfo *_info in self.temps) { skip = condition(_info); if (skip) break; } }else{ [self.temps enumerateObjectsUsingBlock:^(KJCallNotifyInfo * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([info.userid isEqualToString:obj.userid]) { skip = true;*stop = YES; } }]; } if (skip) return; [self.temps addObject:info]; } @synchronized (@(self.displayCount)) { self.displayCount++; KJCallView *view = [self kj_viewIndex:self.displayCount Info:info]; [self.viewTemps addObject:view]; [self addSubview:view]; if (self.displayCount > self.maxCount) { KJCallView *fristView = self.viewTemps.firstObject; [self kj_autoVanish:fristView]; [self kj_displayEnd:fristView]; }}}Copy the code
Second, kj_viewIndex: the Info:
Create a UI control and add a timer view.timer to manage automatic disappearance
- (KJCallView*)kj_viewIndex:(NSInteger)index Info:(KJCallNotifyInfo*)info{ CGFloat y = kAutoH(17) + (index-1) * kAutoH(58) + kSTATUSBAR_HEIGHT - 20; __block KJCallView *view = [[KJCallView alloc]kj_initWithFrame:CGRectMake(0, y, kAutoW(170), kAutoH(48)) Name:info.name]; view.tag = 520 + index - 1; view.info = info; view.imageView.image = [UIImage imageNamed:info.imageUrl]; _weakself; void (^kRemove)(bool tapX) = ^(bool tapX){ if (tapX) { [weakself kj_vanish:view]; }else{ [weakself kj_autoVanish:view]; } [weakself kj_displayEnd:view]; }; [view kj_AddTapGestureRecognizerBlock:^(UIView * _Nonnull __view, UIGestureRecognizer * _Nonnull gesture) { if (weakself.tapblock) { weakself.tapblock(view.info); if (weakself.tapVanish) { [weakself kj_vanish:view]; [weakself kj_displayEnd:view]; } } }]; [view.button kj_addAction:^(UIButton * _Nonnull kButton) { kRemove(true); }]; view.timer = [NSTimer kj_scheduledNoImmediateTimerWithTimeInterval:self.vanishTime Block:^(NSTimer * _Nonnull timer) { kRemove(false); }]; [[NSRunLoop mainRunLoop] addTimer:view.timer forMode:NSRunLoopCommonModes]; [view.timer fire]; return view; }Copy the code
Three, kj_displayEnd:
Display end, clean up the data, turn off the timer, display the number of processing
- (void)kj_displayEnd:(KJCallView*)view{ [view kj_invalidateTimer]; [self.viewTemps removeObject:view]; if (self.repetition == NO) { [self.temps removeObject:view.info]; } @synchronized (@(self.displayCount)) { self.displayCount--; }}Copy the code
Four, kj_autoVanish:
Automatic disappear processing
- (void)kj_autoVanish:(KJCallView*)view{
[UIView animateWithDuration:.5 animations:^{
[self kj_changeIndex:0 Y:-kAutoH(48)];
} completion:^(BOOL finished) {
[view removeFromSuperview];
}];
}
Copy the code
Fifth, kj_vanish:
Dot and fork disappear processing, the current click control disappears, the rest in turn fill
- (void)kj_vanish:(KJCallView*)view{ [UIView animateWithDuration:.5 animations:^{ view.hidden = 0; if (view.tag == 520) { [self kj_changeIndex:0 Y:-kAutoH(48)]; }else{ [self kj_changeIndex:view.tag-520 Y:view.y]; } } completion:^(BOOL finished) { [view removeFromSuperview]; }]; }Copy the code
Six, kj_changeIndex: Y:
Recursive processing of the complement
- (void)kj_changeIndex:(NSInteger)index Y:(CGFloat)y{ KJCallView *view = self.viewTemps[index]; view.tag = 520 + index - 1; CGFloat xy = view.y; view.y = y; if (index+1<self.viewTemps.count) [self kj_changeIndex:index+1 Y:xy]; }Copy the code
Use the sample
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. UIButton *button = [UIButton kj_createButtonWithFontSize:15 Title: @ "test call" TextColor: UIColor. OrangeColor]; button.frame = CGRectMake(0, 0, 100, 50); button.centerX = kScreenW/2; button.centerY = kScreenH - 100; button.borderWidth = 1; button.borderColor = UIColor.orangeColor; [self.view addSubview:button]; __block NSInteger index = 520; NSArray * names = @ [@ "Sone," @ "pain beliefs," @ "X," @ "Yang"]. [button kj_addAction:^(UIButton * _Nonnull kButton) { [[KJCallNotifyView kj_shareInstance] kj_addCallNotify:^(KJCallNotifyInfo * _Nonnull info) { info.imageUrl = @"xxsf"; info.userid = [NSString stringWithFormat:@"%ld",index++]; info.name = names[index%4]; } RepetitionCondition:^bool(KJCallNotifyInfo * _Nonnull info) { if ([info.name isEqualToString:names[index%4]]) { return true; } return false; }]; }]; [KJCallNotifyView kj_shareInstance].maxCount = 5; [KJCallNotifyView kj_shareInstance].vanishTime = 7; [KJCallNotifyView kj_shareInstance].repetition = YES; [[KJCallNotifyView kj_shareInstance] kj_tapBlock:^(KJCallNotifyInfo * _Nonnull info) { NSLog(@"-----%@",info); [self.navigationController popViewControllerAnimated:YES]; }]; }Copy the code