Merge branch 'feat/share_applet'
# Conflicts: # example/pubspec.lock # ios/mop.podspecmaster
commit
ee45c64eb0
|
@ -153,6 +153,7 @@ public class AppletHandlerModule extends BaseApi {
|
|||
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<MoreMenuItem> getRegisteredMoreMenuItems(@NotNull String s) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
List<MoreMenuItem> moreMenuItems = new ArrayList<>();
|
||||
|
@ -162,9 +163,8 @@ public class AppletHandlerModule extends BaseApi {
|
|||
channel.invokeMethod("extensionApi:getCustomMenus", params, new MethodChannel.Result() {
|
||||
@Override
|
||||
public void success(Object result) {
|
||||
if (result instanceof List) {
|
||||
List<Map<String, Object>> ret = (List<Map<String, Object>>) result;
|
||||
FinAppTrace.d(TAG, "getCustomMenus success : " + ret + " size : " + ret.size());
|
||||
if (ret != null) {
|
||||
for (Map<String, Object> map : ret) {
|
||||
String type = (String) map.get("type");
|
||||
MoreMenuType moreMenuType;
|
||||
|
@ -173,7 +173,28 @@ public class AppletHandlerModule extends BaseApi {
|
|||
} else {
|
||||
moreMenuType = MoreMenuType.ON_MINI_PROGRAM;
|
||||
}
|
||||
moreMenuItems.add(new MoreMenuItem((String) map.get("menuId"), (String) map.get("title"), moreMenuType));
|
||||
String menuId = (String) map.get("menuId");
|
||||
if (menuId == null) {
|
||||
menuId = "";
|
||||
}
|
||||
String title = (String) map.get("title");
|
||||
if (title == null) {
|
||||
title = "";
|
||||
}
|
||||
String image = (String) map.get("image");
|
||||
if (image == null) {
|
||||
image = "";
|
||||
}
|
||||
moreMenuItems.add(
|
||||
new MoreMenuItem(
|
||||
menuId,
|
||||
title,
|
||||
image,
|
||||
-1,
|
||||
moreMenuType,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
latch.countDown();
|
||||
|
|
|
@ -108,7 +108,7 @@ packages:
|
|||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "2.37.13"
|
||||
version: "2.39.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#import "MopPlugin.h"
|
||||
#import "MopCustomMenuModel.h"
|
||||
#import <mop/MOPTools.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface NSString (FATEncode)
|
||||
- (NSString *)fat_encodeString;
|
||||
|
@ -73,7 +74,14 @@
|
|||
MopCustomMenuModel *model = [[MopCustomMenuModel alloc] init];
|
||||
model.menuId = data[@"menuId"];
|
||||
model.menuTitle = data[@"title"];
|
||||
model.menuIconImage = [UIImage imageNamed:data[@"image"]];
|
||||
NSString *imageUrl = data[@"image"];
|
||||
if ([imageUrl hasPrefix:@"http"]) {
|
||||
// 需要异步加载,待优化!
|
||||
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
|
||||
model.menuIconImage = [UIImage imageWithData:data];
|
||||
} else {
|
||||
model.menuIconImage = [UIImage imageNamed:imageUrl];
|
||||
}
|
||||
NSString *typeString = data[@"type"];
|
||||
if (typeString) {
|
||||
FATAppletMenuStyle style = [typeString isEqualToString:@"onMiniProgram"] ? FATAppletMenuStyleOnMiniProgram : FATAppletMenuStyleCommon;
|
||||
|
@ -86,11 +94,16 @@
|
|||
}
|
||||
|
||||
- (void)clickCustomItemMenuWithInfo:(NSDictionary *)contentInfo inApplet:(FATAppletInfo *)appletInfo completion:(void (^)(FATExtensionCode code, NSDictionary *result))completion {
|
||||
NSError *parseError = nil;
|
||||
NSMutableDictionary *shareDic = [[NSMutableDictionary alloc] initWithDictionary:[self dictionaryRepresentation:appletInfo]];
|
||||
[shareDic setValue:@{@"desc" : shareDic[@"originalInfo"][@"customData"][@"detailDescription"]} forKey:@"params"];
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:shareDic options:NSJSONWritingPrettyPrinted error:&parseError];
|
||||
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
NSDictionary *arguments = @{
|
||||
@"appId": contentInfo[@"appId"],
|
||||
@"path": contentInfo[@"path"],
|
||||
@"menuId": contentInfo[@"menuId"],
|
||||
@"appInfo": appletInfo.description
|
||||
@"appInfo": jsonString
|
||||
};
|
||||
FlutterMethodChannel *channel = [[MopPlugin instance] methodChannel];
|
||||
[channel invokeMethod:@"extensionApi:onCustomMenuClick" arguments:arguments result:^(id _Nullable result) {
|
||||
|
@ -102,6 +115,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)dictionaryRepresentation:(FATAppletInfo *)object {
|
||||
unsigned int count;
|
||||
objc_property_t *properties = class_copyPropertyList([object class], &count);
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
|
||||
id value = [object valueForKey:key];
|
||||
if (key && value) {
|
||||
[dict setObject:value forKey:key];
|
||||
}
|
||||
}
|
||||
free(properties);
|
||||
return dict;
|
||||
}
|
||||
|
||||
- (void)applet:(NSString *)appletId didOpenCompletion:(NSError *)error {
|
||||
if (!appletId) {
|
||||
return;
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
#import "MOPApiRequest.h"
|
||||
#import "MOPApiConverter.h"
|
||||
#import "MOPAppletDelegate.h"
|
||||
#import <mop/MOPTools.h>
|
||||
#import "MopShareView.h"
|
||||
#import <UIView+MOPFATToast.h>
|
||||
|
||||
@implementation MopEventStream {
|
||||
FlutterEventSink _eventSink;
|
||||
|
@ -56,6 +59,12 @@ static MopPlugin *_instance;
|
|||
[registrar addMethodCallDelegate:_instance channel:appletChannel];
|
||||
_instance.appletMethodChannel = appletChannel;
|
||||
|
||||
FlutterMethodChannel* appletShareChannel = [FlutterMethodChannel
|
||||
methodChannelWithName:@"plugins.finosprite.finogeeks.com/share_applet"
|
||||
binaryMessenger:[registrar messenger]];
|
||||
[registrar addMethodCallDelegate:_instance channel:appletShareChannel];
|
||||
_instance.shareMethodChannel = appletShareChannel;
|
||||
|
||||
}
|
||||
|
||||
+ (instancetype)instance{
|
||||
|
@ -77,13 +86,54 @@ static MopPlugin *_instance;
|
|||
result(dict);
|
||||
}
|
||||
else if ([@"copyFileAsFinFile" isEqualToString:call.method]) {
|
||||
NSString *appId = call.arguments[@"appId"];
|
||||
// NSString *appId = call.arguments[@"appId"];
|
||||
NSString *path = call.arguments[@"path"];
|
||||
NSString *fileName = [path componentsSeparatedByString:@"/"].lastObject;
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
dict[@"path"] = [[FATClient sharedClient] saveFile:[NSData dataWithContentsOfFile:path] fileName:fileName];
|
||||
result(dict);
|
||||
}
|
||||
else if ([@"showShareAppletDialog" isEqualToString:call.method]) {
|
||||
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
UIImage *image = [MOPTools getCurrentPageImage];
|
||||
MopShareView *view = [MopShareView viewWithData:call.arguments];
|
||||
view.image = image;
|
||||
[view show];
|
||||
[view setDidSelcetTypeBlock:^(NSString *type) {
|
||||
result(type);
|
||||
}];
|
||||
// });
|
||||
}
|
||||
else if ([@"showLoading" isEqualToString:call.method]) {
|
||||
UIViewController *currentVC = [MOPTools topViewController];
|
||||
[currentVC.view fatMakeToastActivity:CSToastPositionCenter];
|
||||
result([self appInfoDictWithAppId:call.arguments[@"appId"]]);
|
||||
}
|
||||
else if ([@"hideLoading" isEqualToString:call.method]) {
|
||||
UIViewController *currentVC = [MOPTools topViewController];
|
||||
[currentVC.view fatHideToastActivity];
|
||||
[currentVC.view fatHideAllToasts];
|
||||
result([self appInfoDictWithAppId:call.arguments[@"appId"]]);
|
||||
}
|
||||
else if ([@"showToast" isEqualToString:call.method]) {
|
||||
UIViewController *currentVC = [MOPTools topViewController];
|
||||
[currentVC.view fatMakeToast:call.arguments[@"msg"]
|
||||
duration:2.0
|
||||
position:CSToastPositionCenter];
|
||||
result([self appInfoDictWithAppId:call.arguments[@"appId"]]);
|
||||
}
|
||||
else if ([@"getScreenshot" isEqualToString:call.method]) {
|
||||
UIViewController *currentVC = [MOPTools topViewController];
|
||||
// [currentVC.view fatHideToastActivity];
|
||||
// [currentVC.view fatHideAllToasts];
|
||||
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
// UIImage *image = [[FATClient sharedClient] getDefaultCurrentAppletImage:0.0f];
|
||||
UIImage *image = [MOPTools getCurrentPageImage];
|
||||
NSString *filePtah = [[FATClient sharedClient] saveFile:UIImagePNGRepresentation(image) fileName:[NSString stringWithFormat:@"%@",call.arguments[@"appId"]]];
|
||||
filePtah = [[FATClient sharedClient] fat_absolutePathWithPath:filePtah];
|
||||
result(filePtah);
|
||||
// });
|
||||
}
|
||||
else if ([@"getPhoneNumberResult" isEqualToString:call.method]) {
|
||||
if ([MOPAppletDelegate instance].bindGetPhoneNumbers) {
|
||||
NSDictionary *dic = [[NSDictionary alloc] initWithDictionary:call.arguments];
|
||||
|
@ -199,4 +249,5 @@ static MopPlugin *_instance;
|
|||
}
|
||||
return nil;;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -18,6 +18,22 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
+ (UIColor *)fat_colorWithHexString:(NSString *)hexColor;
|
||||
|
||||
+ (BOOL)fat_currentLanguageIsEn;
|
||||
|
||||
/// 设置颜色( + 暗黑模式):UIDynamicProviderColor(会随着暗黑模式/明亮模式切换自动变化颜色)
|
||||
/// @param lightHexString (明亮模式 的颜色值)
|
||||
/// @param darkHexString (暗黑模式 的颜色值)
|
||||
+ (UIColor *)fat_dynamicColorWithLightHexString:(NSString *)lightHexString darkHexString:(NSString *)darkHexString;
|
||||
|
||||
/// 生成image对象
|
||||
/// @param view 指定的view
|
||||
+ (UIImage *)snapshotWithView:(UIView *)view;
|
||||
|
||||
/// 根据链接生成二维码图片
|
||||
/// @param string 二维码的内容
|
||||
+ (UIImage *)makeQRCodeForString:(NSString *)string;
|
||||
|
||||
+ (UIImage *)getCurrentPageImage;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
#import "MOPTools.h"
|
||||
#import <FinApplet/FinApplet.h>
|
||||
|
||||
@implementation MOPTools
|
||||
+ (UIViewController *)topViewController{
|
||||
|
@ -120,4 +121,147 @@
|
|||
return YES;
|
||||
}
|
||||
|
||||
/// 设置颜色( + 暗黑模式)
|
||||
/// @param lightHexString (明亮模式 的颜色值)
|
||||
/// @param darkHexString (暗黑模式 的颜色值)
|
||||
+ (UIColor *)fat_dynamicColorWithLightHexString:(NSString *)lightHexString darkHexString:(NSString *)darkHexString {
|
||||
return [self fat_dynamicColorWithLight:[UIColor fat_colorWithRGBAHexString:lightHexString] darkColor:[UIColor fat_colorWithRGBAHexString:darkHexString]];
|
||||
}
|
||||
|
||||
+ (UIColor *)fat_dynamicColorWithLight:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
|
||||
if (!darkColor) {
|
||||
return lightColor;
|
||||
}
|
||||
BOOL autoAdaptDarkMode = [FATClient sharedClient].uiConfig.autoAdaptDarkMode;
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunsupported-availability-guard"
|
||||
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
|
||||
if (@available(iOS 13.0, *) && autoAdaptDarkMode) {
|
||||
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(UITraitCollection *_Nonnull traitCollection) {
|
||||
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
|
||||
#pragma clang diagnostic pop
|
||||
return darkColor;
|
||||
} else {
|
||||
return lightColor;
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
return lightColor;
|
||||
}
|
||||
}
|
||||
|
||||
+ (UIImage *)snapshotWithView:(UIView *)view {
|
||||
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, [UIScreen mainScreen].scale);
|
||||
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+ (UIImage *)makeQRCodeForString:(NSString *)string {
|
||||
NSString *text = string;
|
||||
NSData *stringData = [text dataUsingEncoding: NSUTF8StringEncoding];
|
||||
//生成
|
||||
CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
|
||||
[qrFilter setValue:stringData forKey:@"inputMessage"];
|
||||
[qrFilter setValue:@"M" forKey:@"inputCorrectionLevel"];
|
||||
//二维码颜色
|
||||
UIColor *onColor = [UIColor whiteColor];
|
||||
UIColor *offColor = [UIColor blackColor];
|
||||
//上色,如果只要白底黑块的QRCode可以跳过这一步
|
||||
CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor"
|
||||
keysAndValues:
|
||||
@"inputImage",qrFilter.outputImage,
|
||||
@"inputColor0",[CIColor colorWithCGColor:onColor.CGColor],
|
||||
@"inputColor1",[CIColor colorWithCGColor:offColor.CGColor],
|
||||
nil];
|
||||
//绘制
|
||||
CIImage *qrImage = colorFilter.outputImage;
|
||||
CGSize size = CGSizeMake(300, 300);
|
||||
CGImageRef cgImage = [[CIContext contextWithOptions:nil] createCGImage:qrImage fromRect:qrImage.extent];
|
||||
UIGraphicsBeginImageContext(size);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
|
||||
CGContextScaleCTM(context, 1.0, -1.0);//生成的QRCode就是上下颠倒的,需要翻转一下
|
||||
CGContextDrawImage(context, CGContextGetClipBoundingBox(context), cgImage);
|
||||
UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
CGImageRelease(cgImage);
|
||||
|
||||
return [UIImage imageWithCIImage:qrImage];
|
||||
}
|
||||
|
||||
|
||||
/// 顶部状态栏高度(包括安全区)
|
||||
+ (CGFloat)getStatusHeight {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
NSSet *set = [UIApplication sharedApplication].connectedScenes;
|
||||
UIWindowScene *windowScene = [set anyObject];
|
||||
UIStatusBarManager *statusBarManager = windowScene.statusBarManager;
|
||||
return statusBarManager.statusBarFrame.size.height;
|
||||
} else {
|
||||
return [UIApplication sharedApplication].statusBarFrame.size.height;
|
||||
}
|
||||
}
|
||||
|
||||
+ (UIImage *)getCurrentPageImage {
|
||||
UIViewController *currentVC = [MOPTools topViewController];
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(currentVC.view.frame.size.width, 440), NO, [UIScreen mainScreen].scale);
|
||||
[currentVC.view.layer renderInContext:UIGraphicsGetCurrentContext()];
|
||||
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return snapshotImage;
|
||||
}
|
||||
|
||||
// 截取当前屏幕 ,返回截取到的图片
|
||||
//+ (UIImage *)getCurrentPageImage {
|
||||
//
|
||||
//
|
||||
// CGSize imageSize = CGSizeZero;
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
|
||||
// if (UIInterfaceOrientationIsPortrait(orientation)) {
|
||||
// imageSize = [UIScreen mainScreen].bounds.size;
|
||||
// } else {
|
||||
// imageSize = CGSizeMake([UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width);
|
||||
// }
|
||||
// // 绘制上下文
|
||||
// UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
|
||||
// CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
// for (UIWindow *window in [[UIApplication sharedApplication] windows]) {
|
||||
// CGContextSaveGState(context);
|
||||
// CGContextTranslateCTM(context, window.center.x, window.center.y);
|
||||
// CGContextConcatCTM(context, window.transform);
|
||||
// CGContextTranslateCTM(context, -window.bounds.size.width * window.layer.anchorPoint.x, -window.bounds.size.height * window.layer.anchorPoint.y);
|
||||
// if (orientation == UIInterfaceOrientationLandscapeLeft) {
|
||||
// CGContextRotateCTM(context, M_PI_2);
|
||||
// CGContextTranslateCTM(context, 0, -imageSize.width);
|
||||
// } else if (orientation == UIInterfaceOrientationLandscapeRight) {
|
||||
// CGContextRotateCTM(context, -M_PI_2);
|
||||
// CGContextTranslateCTM(context, -imageSize.height, 0);
|
||||
// } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
|
||||
// CGContextRotateCTM(context, M_PI);
|
||||
// CGContextTranslateCTM(context, -imageSize.width, -imageSize.height);
|
||||
// }
|
||||
// if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
|
||||
// [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
|
||||
// } else {
|
||||
// [window.layer renderInContext:context];
|
||||
// }
|
||||
// CGContextRestoreGState(context);
|
||||
// }
|
||||
// UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
// UIGraphicsEndImageContext();
|
||||
// NSData *imageData = UIImagePNGRepresentation(image);
|
||||
// return [UIImage imageWithData:imageData];
|
||||
//}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// MopShareView.h
|
||||
// mop
|
||||
//
|
||||
// Created by 王兆耀 on 2023/1/2.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^MOPshareBottomViewTypeBlock)(NSString *type);
|
||||
|
||||
@interface MopShareView : UIView
|
||||
@property (nonatomic, copy) MOPshareBottomViewTypeBlock didSelcetTypeBlock;
|
||||
@property (nonatomic, strong) UIImage *image;
|
||||
@property (nonatomic, strong) NSDictionary *dataDic;
|
||||
+ (instancetype)viewWithData:(NSDictionary *)data;
|
||||
- (void)show;
|
||||
- (void)dismiss;
|
||||
@end
|
||||
|
||||
|
||||
@class MOPshareBottomViewCell;
|
||||
|
||||
@protocol MOPCellClickDelegate <NSObject>
|
||||
- (void)iconBtnDidClick:(MOPshareBottomViewCell *)cell;
|
||||
@end
|
||||
|
||||
@interface MOPshareBottomViewCell : UICollectionViewCell
|
||||
@property (nonatomic, strong) NSString *type;
|
||||
@property (nonatomic, strong) UIButton *imageButton;
|
||||
@property (nonatomic, strong) UILabel *label;
|
||||
@property (nonatomic, weak) id<MOPCellClickDelegate> delegate;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,317 @@
|
|||
//
|
||||
// MopShareView.m
|
||||
// mop
|
||||
//
|
||||
// Created by 王兆耀 on 2023/1/2.
|
||||
//
|
||||
|
||||
#import "MopShareView.h"
|
||||
#import "MopPlugin.h"
|
||||
#import "MOPTools.h"
|
||||
#import <FinApplet/FinApplet.h>
|
||||
#import <UIView+MOPFATToast.h>
|
||||
|
||||
//获取安全区域距离
|
||||
#define kFinoSafeAreaTop kFinoWindowSafeAreaInset.top
|
||||
#define kFinoSafeAreaBottom kFinoWindowSafeAreaInset.bottom
|
||||
|
||||
#define kFinoWindowSafeAreaInset \
|
||||
({\
|
||||
UIEdgeInsets returnInsets = UIEdgeInsetsMake([UIApplication sharedApplication].statusBarFrame.size.height, 0, 0, 0);\
|
||||
UIWindow * keyWindow = [UIApplication sharedApplication].keyWindow;\
|
||||
if ([keyWindow respondsToSelector:NSSelectorFromString(@"safeAreaInsets")]) {\
|
||||
UIEdgeInsets inset = [[keyWindow valueForKeyPath:@"safeAreaInsets"] UIEdgeInsetsValue];\
|
||||
if (inset.top < [UIApplication sharedApplication].statusBarFrame.size.height) {\
|
||||
inset.top = [UIApplication sharedApplication].statusBarFrame.size.height;\
|
||||
}\
|
||||
returnInsets = inset;\
|
||||
}\
|
||||
(returnInsets);\
|
||||
})\
|
||||
|
||||
@interface MopShareView ()<UICollectionViewDelegate, UICollectionViewDataSource, MOPCellClickDelegate>
|
||||
|
||||
|
||||
@property (nonatomic, strong) UIView *shareView;
|
||||
@property (nonatomic, strong) UIImageView *appletImageView;
|
||||
|
||||
@property (nonatomic, strong) UIView *contentView;
|
||||
@property (nonatomic, strong) UIButton *cancelButton;
|
||||
@property(nonatomic, strong) UICollectionView *collectionView;
|
||||
@property(nonatomic, strong) NSArray *dataArray;
|
||||
@property (nonatomic, strong) UIImageView *qrCodeImageView;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
@property (nonatomic, strong) UILabel *descLabel;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MopShareView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.dataArray = @[@{@"lightImage":@"share_wechat", @"title":@"微信好友", @"type":@"wechat"},
|
||||
@{@"lightImage":@"share_moments",@"title":@"朋友圈", @"type":@"moments"},
|
||||
@{@"lightImage":@"share_link",@"title":@"复制链接", @"type":@"links"}];
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4f];
|
||||
|
||||
self.shareView = [[UIView alloc] initWithFrame:CGRectMake(52.5 , 46 + kFinoSafeAreaTop, 270, 380)];
|
||||
self.shareView.layer.cornerRadius = 6;
|
||||
self.shareView.backgroundColor = UIColor.whiteColor;
|
||||
|
||||
UIImageView *appletImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 270, 300)];
|
||||
appletImageView.contentMode = UIViewContentModeScaleToFill;
|
||||
self.appletImageView = appletImageView;
|
||||
[self.shareView addSubview:appletImageView];
|
||||
|
||||
float bottomY = appletImageView.frame.size.height + appletImageView.frame.origin.y;
|
||||
UIView *line0 = [[UIView alloc] initWithFrame:CGRectMake(0, bottomY, self.shareView.frame.size.width, 0.5)];
|
||||
line0.backgroundColor = [MOPTools fat_dynamicColorWithLightHexString:@"#eeeeee" darkHexString:@"#eeeeee"];
|
||||
[self.shareView addSubview:line0];
|
||||
|
||||
|
||||
UILabel *descLabel = [[UILabel alloc] init];
|
||||
descLabel.frame = CGRectMake(14, bottomY + 12, 168, 21);
|
||||
descLabel.font = [UIFont fontWithName:@"PingFangSC-Semibold" size:15];
|
||||
descLabel.textAlignment = NSTextAlignmentLeft;
|
||||
descLabel.textColor = [MOPTools fat_dynamicColorWithLightHexString:@"#222222" darkHexString:@"#222222"];
|
||||
self.titleLabel = descLabel;
|
||||
[self.shareView addSubview:descLabel];
|
||||
|
||||
UILabel *detailLabel = [[UILabel alloc] init];
|
||||
detailLabel.frame = CGRectMake(14, self.titleLabel.frame.size.height + self.titleLabel.frame.origin.y - 3, 168, 44);
|
||||
detailLabel.font = [UIFont fontWithName:@"PingFangSC-Regular" size:11];
|
||||
detailLabel.textAlignment = NSTextAlignmentLeft;
|
||||
detailLabel.numberOfLines = 0;
|
||||
detailLabel.textColor = [MOPTools fat_dynamicColorWithLightHexString:@"#666666" darkHexString:@"#666666"];
|
||||
self.descLabel = detailLabel;
|
||||
[self.shareView addSubview:detailLabel];
|
||||
|
||||
self.qrCodeImageView = [[UIImageView alloc] initWithFrame:CGRectMake(196, bottomY + 8, 64, 64)];
|
||||
[self.shareView addSubview:self.qrCodeImageView];
|
||||
|
||||
[self addSubview:self.shareView];
|
||||
|
||||
|
||||
self.contentView = [[UIView alloc] initWithFrame:CGRectMake(0,self.frame.size.height - (221 + kFinoSafeAreaBottom) , self.frame.size.width, (221 + kFinoSafeAreaBottom))];
|
||||
self.contentView.backgroundColor = [MOPTools fat_dynamicColorWithLightHexString:@"#F0F0F0" darkHexString:@"#1A1A1A"];
|
||||
[self addSubview:self.contentView];
|
||||
|
||||
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, 58 )];
|
||||
titleLabel.text = @"分享至";
|
||||
titleLabel.textColor = [MOPTools fat_dynamicColorWithLightHexString:@"#222222" darkHexString:@"#D0D0D0"];
|
||||
titleLabel.font = [UIFont fontWithName:@"PingFangSC-Regular" size:16];
|
||||
titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
[self.contentView addSubview:titleLabel];
|
||||
|
||||
UIView *line1 = [[UIView alloc] initWithFrame:CGRectMake(0, 58 , self.contentView.frame.size.width, 0.5)];
|
||||
line1.backgroundColor = [MOPTools fat_dynamicColorWithLightHexString:@"#E0E0E0" darkHexString:@"#E0E0E0"];
|
||||
[self.contentView addSubview:line1];
|
||||
|
||||
self.cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
NSString *cancel = [MOPTools fat_currentLanguageIsEn] ? @"Cancel" : @"取消";
|
||||
self.cancelButton.titleLabel.font = [UIFont fontWithName:@"PingFangSC-Regular" size:17];
|
||||
[self.cancelButton setTitle:cancel forState:UIControlStateNormal];
|
||||
[self.cancelButton setTitleColor:[MOPTools fat_dynamicColorWithLightHexString:@"#222222" darkHexString:@"#D0D0D0"] forState:UIControlStateNormal];
|
||||
[self.contentView addSubview:self.cancelButton];
|
||||
self.cancelButton.frame = CGRectMake(0, self.contentView.frame.size.height - kFinoSafeAreaBottom - 56, self.contentView.frame.size.width, 56 );
|
||||
[self.cancelButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[self.contentView addSubview:self.collectionView];
|
||||
self.collectionView.frame = CGRectMake(0, 58.5, frame.size.width, 107);
|
||||
|
||||
UIView *line2 = [[UIView alloc] initWithFrame:CGRectMake(0, 165.5, self.collectionView.frame.size.width, 0.5)];
|
||||
line2.backgroundColor = [MOPTools fat_dynamicColorWithLightHexString:@"#E0E0E0" darkHexString:@"#2E2E2E"];
|
||||
[self.contentView addSubview:line2];
|
||||
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.contentView.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(6.0, 6.0)];
|
||||
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
|
||||
maskLayer.frame = self.contentView.bounds;
|
||||
maskLayer.path = maskPath.CGPath;
|
||||
self.contentView.layer.mask = maskLayer;
|
||||
|
||||
UIBezierPath *appletImageViewMask = [UIBezierPath bezierPathWithRoundedRect:self.appletImageView.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(6.0, 6.0)];
|
||||
CAShapeLayer *appletImageMaskLayer = [[CAShapeLayer alloc] init];
|
||||
appletImageMaskLayer.frame = self.appletImageView.bounds;
|
||||
appletImageMaskLayer.path = appletImageViewMask.CGPath;
|
||||
self.appletImageView.layer.mask = appletImageMaskLayer;
|
||||
|
||||
self.shareView.frame = CGRectMake(52.5, self.contentView.frame.origin.y - 400 , 270 , 380);
|
||||
|
||||
UIButton *saveButton = [[UIButton alloc] initWithFrame:CGRectMake(278.5, self.shareView.frame.origin.y + 12, 36, 36)];
|
||||
[saveButton addTarget:self action:@selector(saveOnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[saveButton setImage:[UIImage imageNamed:@"share_download"] forState:UIControlStateNormal];
|
||||
[self addSubview:saveButton];
|
||||
[self p_addNotifications];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)viewWithData:(NSDictionary *)data {
|
||||
MopShareView *view = [[self alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
view.dataDic = data;
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)setDataDic:(NSDictionary *)dataDic {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.qrCodeImageView.image = [MOPTools makeQRCodeForString:dataDic[@"shareUrl"]];
|
||||
});
|
||||
self.titleLabel.text = dataDic[@"miniAppName"];
|
||||
self.descLabel.text = dataDic[@"miniAppDesc"];
|
||||
}
|
||||
|
||||
- (void)saveOnClick {
|
||||
// 把view生成图片并保存
|
||||
UIImage *image = [MOPTools snapshotWithView:self.shareView];
|
||||
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), (__bridge void *)self);
|
||||
}
|
||||
|
||||
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
|
||||
if (!error) {
|
||||
// 出错
|
||||
[self fatMakeToast:@"保存成功" duration:1.5 position:CSToastPositionCenter];
|
||||
} else {
|
||||
[self fatMakeToast:[NSString stringWithFormat:@"保存失败,%@", error.description] duration:1.5 position:CSToastPositionCenter];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)p_addNotifications {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)deviceOrientationDidChange {
|
||||
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
|
||||
CGFloat currentWidth = self.frame.size.width;
|
||||
if (screenWidth != currentWidth) {
|
||||
[self removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)show {
|
||||
self.appletImageView.image = self.image;
|
||||
if (self.superview) {
|
||||
return;
|
||||
}
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
UIWindow * window = [UIApplication sharedApplication].keyWindow;
|
||||
[window addSubview:self];
|
||||
} completion:^(BOOL finished) {
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dismiss {
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
self.contentView.transform = CGAffineTransformMakeTranslation(0, self.contentView.frame.size.height);
|
||||
self.alpha = 0.5f;
|
||||
} completion:^(BOOL finished) {
|
||||
[self removeFromSuperview];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
// [self dismiss];
|
||||
}
|
||||
|
||||
#pragma mark colletionview ------------------------------
|
||||
static NSString *cellID = @"cellid";
|
||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
MOPshareBottomViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
|
||||
[cell.imageButton setImage:[UIImage imageNamed:self.dataArray[indexPath.row][@"lightImage"]] forState:UIControlStateNormal];
|
||||
cell.label.text = self.dataArray[indexPath.row][@"title"];
|
||||
cell.type = self.dataArray[indexPath.row][@"type"];
|
||||
cell.delegate = self;
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)iconBtnDidClick:(MOPshareBottomViewCell *)cell {
|
||||
if (self.didSelcetTypeBlock) {
|
||||
NSString *typeString = cell.type;
|
||||
self.didSelcetTypeBlock(typeString);
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (self.didSelcetTypeBlock) {
|
||||
NSString *typeString = self.dataArray[indexPath.row][@"type"];
|
||||
self.didSelcetTypeBlock(typeString);
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return self.dataArray.count;
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return CGSizeMake(66, self.collectionView.frame.size.height);
|
||||
}
|
||||
|
||||
- (UICollectionViewFlowLayout *)layout {
|
||||
UICollectionViewFlowLayout *collectionViewLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||
collectionViewLayout.minimumLineSpacing = 0;
|
||||
collectionViewLayout.minimumInteritemSpacing = 0;
|
||||
collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
return collectionViewLayout;
|
||||
}
|
||||
|
||||
- (UICollectionView *)collectionView {
|
||||
if (!_collectionView) {
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:[self layout]];
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
[_collectionView registerClass:[MOPshareBottomViewCell class] forCellWithReuseIdentifier:cellID];
|
||||
_collectionView.pagingEnabled = YES;
|
||||
_collectionView.showsVerticalScrollIndicator = NO;
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
_collectionView.backgroundColor = [MOPTools fat_dynamicColorWithLightHexString:@"#F0F0F0" darkHexString:@"#1A1A1A"];
|
||||
if (@available(iOS 11.0, *)) {
|
||||
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
}
|
||||
return _collectionView;;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MOPshareBottomViewCell
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [MOPTools fat_dynamicColorWithLightHexString:@"#F0F0F0" darkHexString:@"#1A1A1A"];
|
||||
|
||||
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(18, 20, 48, 48)];
|
||||
button.backgroundColor = [MOPTools fat_dynamicColorWithLightHexString:@"#FFFFFF " darkHexString:@"#2C2C2C"];
|
||||
[self.contentView addSubview:button];
|
||||
button.layer.masksToBounds = YES;
|
||||
button.layer.cornerRadius = 6;
|
||||
UIImage *highBgImage = [UIImage fat_imageWithColor:[MOPTools fat_dynamicColorWithLightHexString:@"#D7D7D7 " darkHexString:@"#4A4A4A"]];
|
||||
[button setBackgroundImage:highBgImage forState:UIControlStateHighlighted];
|
||||
[button addTarget:self action:@selector(iconBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
self.imageButton = button;
|
||||
// self.label = [[UILabel alloc] initWithFrame:CGRectMake((frame.size.width-50)/2, 65, frame.size.width, 30)];
|
||||
self.label = [[UILabel alloc] initWithFrame:CGRectMake(18, 75, 48, 12)];
|
||||
self.label.font = [UIFont systemFontOfSize:10];
|
||||
self.label.textColor = [UIColor grayColor];
|
||||
self.label.textAlignment = NSTextAlignmentCenter;
|
||||
[self.contentView addSubview:self.label];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)iconBtnClick:(UIButton *)btn {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(iconBtnDidClick:)]) {
|
||||
[self.delegate iconBtnDidClick:self];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,421 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
extern const NSString * CSToastPositionTop;
|
||||
extern const NSString * CSToastPositionCenter;
|
||||
extern const NSString * CSToastPositionBottom;
|
||||
|
||||
@class CSToastStyle;
|
||||
|
||||
/**
|
||||
Toast is an Objective-C category that adds toast notifications to the UIView
|
||||
object class. It is intended to be simple, lightweight, and easy to use. Most
|
||||
toast notifications can be triggered with a single line of code.
|
||||
|
||||
The `makeToast:` methods create a new view and then display it as toast.
|
||||
|
||||
The `showToast:` methods display any view as toast.
|
||||
|
||||
*/
|
||||
@interface UIView (MOPFATToast)
|
||||
|
||||
/**
|
||||
Creates and presents a new toast view with a message and displays it with the
|
||||
default duration and position. Styled using the shared style.
|
||||
|
||||
@param message The message to be displayed
|
||||
*/
|
||||
- (void)fatMakeToast:(NSString *)message;
|
||||
|
||||
/**
|
||||
Creates and presents a new toast view with a message. Duration and position
|
||||
can be set explicitly. Styled using the shared style.
|
||||
|
||||
@param message The message to be displayed
|
||||
@param duration The toast duration
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
*/
|
||||
- (void)fatMakeToast:(NSString *)message
|
||||
duration:(NSTimeInterval)duration
|
||||
position:(id)position;
|
||||
|
||||
/**
|
||||
Creates and presents a new toast view with a message. Duration, position, and
|
||||
style can be set explicitly.
|
||||
|
||||
@param message The message to be displayed
|
||||
@param duration The toast duration
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
@param style The style. The shared style will be used when nil
|
||||
*/
|
||||
- (void)fatMakeToast:(NSString *)message
|
||||
duration:(NSTimeInterval)duration
|
||||
position:(id)position
|
||||
style:(CSToastStyle *)style;
|
||||
|
||||
/**
|
||||
Creates and presents a new toast view with a message, title, and image. Duration,
|
||||
position, and style can be set explicitly. The completion block executes when the
|
||||
toast view completes. `didTap` will be `YES` if the toast view was dismissed from
|
||||
a tap.
|
||||
|
||||
@param message The message to be displayed
|
||||
@param duration The toast duration
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
@param title The title
|
||||
@param image The image
|
||||
@param style The style. The shared style will be used when nil
|
||||
@param completion The completion block, executed after the toast view disappears.
|
||||
didTap will be `YES` if the toast view was dismissed from a tap.
|
||||
*/
|
||||
- (void)fatMakeToast:(NSString *)message
|
||||
duration:(NSTimeInterval)duration
|
||||
position:(id)position
|
||||
title:(NSString *)title
|
||||
image:(UIImage *)image
|
||||
style:(CSToastStyle *)style
|
||||
completion:(void(^)(BOOL didTap))completion;
|
||||
|
||||
/**
|
||||
Creates a new toast view with any combination of message, title, and image.
|
||||
The look and feel is configured via the style. Unlike the `makeToast:` methods,
|
||||
this method does not present the toast view automatically. One of the showToast:
|
||||
methods must be used to present the resulting view.
|
||||
|
||||
@warning if message, title, and image are all nil, this method will return nil.
|
||||
|
||||
@param message The message to be displayed
|
||||
@param title The title
|
||||
@param image The image
|
||||
@param style The style. The shared style will be used when nil
|
||||
@return The newly created toast view
|
||||
*/
|
||||
- (UIView *)fatToastViewForMessage:(NSString *)message
|
||||
title:(NSString *)title
|
||||
image:(UIImage *)image
|
||||
style:(CSToastStyle *)style;
|
||||
|
||||
/**
|
||||
Hides the active toast. If there are multiple toasts active in a view, this method
|
||||
hides the oldest toast (the first of the toasts to have been presented).
|
||||
|
||||
@see `hideAllToasts` to remove all active toasts from a view.
|
||||
|
||||
@warning This method has no effect on activity toasts. Use `hideToastActivity` to
|
||||
hide activity toasts.
|
||||
*/
|
||||
- (void)fatHideToast;
|
||||
|
||||
/**
|
||||
Hides an active toast.
|
||||
|
||||
@param toast The active toast view to dismiss. Any toast that is currently being displayed
|
||||
on the screen is considered active.
|
||||
|
||||
@warning this does not clear a toast view that is currently waiting in the queue.
|
||||
*/
|
||||
- (void)fatHideToast:(UIView *)toast;
|
||||
|
||||
/**
|
||||
Hides all active toast views and clears the queue.
|
||||
*/
|
||||
- (void)fatHideAllToasts;
|
||||
|
||||
/**
|
||||
Hides all active toast views, with options to hide activity and clear the queue.
|
||||
|
||||
@param includeActivity If `true`, toast activity will also be hidden. Default is `false`.
|
||||
@param clearQueue If `true`, removes all toast views from the queue. Default is `true`.
|
||||
*/
|
||||
- (void)fatHideAllToasts:(BOOL)includeActivity clearQueue:(BOOL)clearQueue;
|
||||
|
||||
/**
|
||||
Removes all toast views from the queue. This has no effect on toast views that are
|
||||
active. Use `hideAllToasts` to hide the active toasts views and clear the queue.
|
||||
*/
|
||||
- (void)fatClearToastQueue;
|
||||
|
||||
/**
|
||||
Creates and displays a new toast activity indicator view at a specified position.
|
||||
|
||||
@warning Only one toast activity indicator view can be presented per superview. Subsequent
|
||||
calls to `makeToastActivity:` will be ignored until hideToastActivity is called.
|
||||
|
||||
@warning `makeToastActivity:` works independently of the showToast: methods. Toast activity
|
||||
views can be presented and dismissed while toast views are being displayed. `makeToastActivity:`
|
||||
has no effect on the queueing behavior of the showToast: methods.
|
||||
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
*/
|
||||
- (void)fatMakeToastActivity:(id)position;
|
||||
|
||||
/**
|
||||
Dismisses the active toast activity indicator view.
|
||||
*/
|
||||
- (void)fatHideToastActivity;
|
||||
|
||||
/**
|
||||
Displays any view as toast using the default duration and position.
|
||||
|
||||
@param toast The view to be displayed as toast
|
||||
*/
|
||||
- (void)fatShowToast:(UIView *)toast;
|
||||
|
||||
/**
|
||||
Displays any view as toast at a provided position and duration. The completion block
|
||||
executes when the toast view completes. `didTap` will be `YES` if the toast view was
|
||||
dismissed from a tap.
|
||||
|
||||
@param toast The view to be displayed as toast
|
||||
@param duration The notification duration
|
||||
@param position The toast's center point. Can be one of the predefined CSToastPosition
|
||||
constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
@param completion The completion block, executed after the toast view disappears.
|
||||
didTap will be `YES` if the toast view was dismissed from a tap.
|
||||
*/
|
||||
- (void)fatShowToast:(UIView *)toast
|
||||
duration:(NSTimeInterval)duration
|
||||
position:(id)position
|
||||
completion:(void(^)(BOOL didTap))completion;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
`CSToastStyle` instances define the look and feel for toast views created via the
|
||||
`makeToast:` methods as well for toast views created directly with
|
||||
`toastViewForMessage:title:image:style:`.
|
||||
|
||||
@warning `CSToastStyle` offers relatively simple styling options for the default
|
||||
toast view. If you require a toast view with more complex UI, it probably makes more
|
||||
sense to create your own custom UIView subclass and present it with the `showToast:`
|
||||
methods.
|
||||
*/
|
||||
@interface CSToastStyle : NSObject
|
||||
|
||||
/**
|
||||
The background color. Default is `[UIColor blackColor]` at 80% opacity.
|
||||
*/
|
||||
@property (strong, nonatomic) UIColor *backgroundColor;
|
||||
|
||||
/**
|
||||
The title color. Default is `[UIColor whiteColor]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIColor *titleColor;
|
||||
|
||||
/**
|
||||
The message color. Default is `[UIColor whiteColor]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIColor *messageColor;
|
||||
|
||||
/**
|
||||
A percentage value from 0.0 to 1.0, representing the maximum width of the toast
|
||||
view relative to it's superview. Default is 0.8 (80% of the superview's width).
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat maxWidthPercentage;
|
||||
|
||||
/**
|
||||
A percentage value from 0.0 to 1.0, representing the maximum height of the toast
|
||||
view relative to it's superview. Default is 0.8 (80% of the superview's height).
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat maxHeightPercentage;
|
||||
|
||||
/**
|
||||
The spacing from the horizontal edge of the toast view to the content. When an image
|
||||
is present, this is also used as the padding between the image and the text.
|
||||
Default is 10.0.
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat horizontalPadding;
|
||||
|
||||
/**
|
||||
The spacing from the vertical edge of the toast view to the content. When a title
|
||||
is present, this is also used as the padding between the title and the message.
|
||||
Default is 10.0.
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat verticalPadding;
|
||||
|
||||
/**
|
||||
The corner radius. Default is 10.0.
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat cornerRadius;
|
||||
|
||||
/**
|
||||
The title font. Default is `[UIFont boldSystemFontOfSize:16.0]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIFont *titleFont;
|
||||
|
||||
/**
|
||||
The message font. Default is `[UIFont systemFontOfSize:16.0]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIFont *messageFont;
|
||||
|
||||
/**
|
||||
The title text alignment. Default is `NSTextAlignmentLeft`.
|
||||
*/
|
||||
@property (assign, nonatomic) NSTextAlignment titleAlignment;
|
||||
|
||||
/**
|
||||
The message text alignment. Default is `NSTextAlignmentLeft`.
|
||||
*/
|
||||
@property (assign, nonatomic) NSTextAlignment messageAlignment;
|
||||
|
||||
/**
|
||||
The maximum number of lines for the title. The default is 0 (no limit).
|
||||
*/
|
||||
@property (assign, nonatomic) NSInteger titleNumberOfLines;
|
||||
|
||||
/**
|
||||
The maximum number of lines for the message. The default is 0 (no limit).
|
||||
*/
|
||||
@property (assign, nonatomic) NSInteger messageNumberOfLines;
|
||||
|
||||
/**
|
||||
Enable or disable a shadow on the toast view. Default is `NO`.
|
||||
*/
|
||||
@property (assign, nonatomic) BOOL displayShadow;
|
||||
|
||||
/**
|
||||
The shadow color. Default is `[UIColor blackColor]`.
|
||||
*/
|
||||
@property (strong, nonatomic) UIColor *shadowColor;
|
||||
|
||||
/**
|
||||
A value from 0.0 to 1.0, representing the opacity of the shadow.
|
||||
Default is 0.8 (80% opacity).
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat shadowOpacity;
|
||||
|
||||
/**
|
||||
The shadow radius. Default is 6.0.
|
||||
*/
|
||||
@property (assign, nonatomic) CGFloat shadowRadius;
|
||||
|
||||
/**
|
||||
The shadow offset. The default is `CGSizeMake(4.0, 4.0)`.
|
||||
*/
|
||||
@property (assign, nonatomic) CGSize shadowOffset;
|
||||
|
||||
/**
|
||||
The image size. The default is `CGSizeMake(80.0, 80.0)`.
|
||||
*/
|
||||
@property (assign, nonatomic) CGSize imageSize;
|
||||
|
||||
/**
|
||||
The size of the toast activity view when `makeToastActivity:` is called.
|
||||
Default is `CGSizeMake(100.0, 100.0)`.
|
||||
*/
|
||||
@property (assign, nonatomic) CGSize activitySize;
|
||||
|
||||
/**
|
||||
The fade in/out animation duration. Default is 0.2.
|
||||
*/
|
||||
@property (assign, nonatomic) NSTimeInterval fadeDuration;
|
||||
|
||||
/**
|
||||
Creates a new instance of `CSToastStyle` with all the default values set.
|
||||
*/
|
||||
- (instancetype)initWithDefaultStyle NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
@warning Only the designated initializer should be used to create
|
||||
an instance of `CSToastStyle`.
|
||||
*/
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
`CSToastManager` provides general configuration options for all toast
|
||||
notifications. Backed by a singleton instance.
|
||||
*/
|
||||
@interface CSToastManager : NSObject
|
||||
|
||||
/**
|
||||
Sets the shared style on the singleton. The shared style is used whenever
|
||||
a `makeToast:` method (or `toastViewForMessage:title:image:style:`) is called
|
||||
with with a nil style. By default, this is set to `CSToastStyle`'s default
|
||||
style.
|
||||
|
||||
@param sharedStyle the shared style
|
||||
*/
|
||||
+ (void)setSharedStyle:(CSToastStyle *)sharedStyle;
|
||||
|
||||
/**
|
||||
Gets the shared style from the singlton. By default, this is
|
||||
`CSToastStyle`'s default style.
|
||||
|
||||
@return the shared style
|
||||
*/
|
||||
+ (CSToastStyle *)sharedStyle;
|
||||
|
||||
/**
|
||||
Enables or disables tap to dismiss on toast views. Default is `YES`.
|
||||
|
||||
@param tapToDismissEnabled YES or NO
|
||||
*/
|
||||
+ (void)setTapToDismissEnabled:(BOOL)tapToDismissEnabled;
|
||||
|
||||
/**
|
||||
Returns `YES` if tap to dismiss is enabled, otherwise `NO`.
|
||||
Default is `YES`.
|
||||
|
||||
@return BOOL YES or NO
|
||||
*/
|
||||
+ (BOOL)isTapToDismissEnabled;
|
||||
|
||||
/**
|
||||
Enables or disables queueing behavior for toast views. When `YES`,
|
||||
toast views will appear one after the other. When `NO`, multiple Toast
|
||||
views will appear at the same time (potentially overlapping depending
|
||||
on their positions). This has no effect on the toast activity view,
|
||||
which operates independently of normal toast views. Default is `NO`.
|
||||
|
||||
@param queueEnabled YES or NO
|
||||
*/
|
||||
+ (void)setQueueEnabled:(BOOL)queueEnabled;
|
||||
|
||||
/**
|
||||
Returns `YES` if the queue is enabled, otherwise `NO`.
|
||||
Default is `NO`.
|
||||
|
||||
@return BOOL
|
||||
*/
|
||||
+ (BOOL)isQueueEnabled;
|
||||
|
||||
/**
|
||||
Sets the default duration. Used for the `makeToast:` and
|
||||
`showToast:` methods that don't require an explicit duration.
|
||||
Default is 3.0.
|
||||
|
||||
@param duration The toast duration
|
||||
*/
|
||||
+ (void)setDefaultDuration:(NSTimeInterval)duration;
|
||||
|
||||
/**
|
||||
Returns the default duration. Default is 3.0.
|
||||
|
||||
@return duration The toast duration
|
||||
*/
|
||||
+ (NSTimeInterval)defaultDuration;
|
||||
|
||||
/**
|
||||
Sets the default position. Used for the `makeToast:` and
|
||||
`showToast:` methods that don't require an explicit position.
|
||||
Default is `CSToastPositionBottom`.
|
||||
|
||||
@param position The default center point. Can be one of the predefined
|
||||
CSToastPosition constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
*/
|
||||
+ (void)setDefaultPosition:(id)position;
|
||||
|
||||
/**
|
||||
Returns the default toast position. Default is `CSToastPositionBottom`.
|
||||
|
||||
@return position The default center point. Will be one of the predefined
|
||||
CSToastPosition constants or a `CGPoint` wrapped in an `NSValue` object.
|
||||
*/
|
||||
+ (id)defaultPosition;
|
||||
|
||||
@end
|
|
@ -0,0 +1,561 @@
|
|||
#import "UIView+MOPFATToast.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
// Positions
|
||||
NSString * CSToastPositionTop = @"CSToastPositionTop";
|
||||
NSString * CSToastPositionCenter = @"CSToastPositionCenter";
|
||||
NSString * CSToastPositionBottom = @"CSToastPositionBottom";
|
||||
|
||||
// Keys for values associated with toast views
|
||||
static const NSString * CSToastTimerKey = @"CSToastTimerKey";
|
||||
static const NSString * CSToastDurationKey = @"CSToastDurationKey";
|
||||
static const NSString * CSToastPositionKey = @"CSToastPositionKey";
|
||||
static const NSString * CSToastCompletionKey = @"CSToastCompletionKey";
|
||||
|
||||
// Keys for values associated with self
|
||||
static const NSString * CSToastActiveKey = @"CSToastActiveKey";
|
||||
static const NSString * CSToastActivityViewKey = @"CSToastActivityViewKey";
|
||||
static const NSString * CSToastQueueKey = @"CSToastQueueKey";
|
||||
|
||||
@interface UIView (ToastPrivate)
|
||||
|
||||
/**
|
||||
These private methods are being prefixed with "cs_" to reduce the likelihood of non-obvious
|
||||
naming conflicts with other UIView methods.
|
||||
|
||||
@discussion Should the public API also use the cs_ prefix? Technically it should, but it
|
||||
results in code that is less legible. The current public method names seem unlikely to cause
|
||||
conflicts so I think we should favor the cleaner API for now.
|
||||
*/
|
||||
- (void)cs_showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position;
|
||||
- (void)cs_hideToast:(UIView *)toast;
|
||||
- (void)cs_hideToast:(UIView *)toast fromTap:(BOOL)fromTap;
|
||||
- (void)cs_toastTimerDidFinish:(NSTimer *)timer;
|
||||
- (void)cs_handleToastTapped:(UITapGestureRecognizer *)recognizer;
|
||||
- (CGPoint)cs_centerPointForPosition:(id)position withToast:(UIView *)toast;
|
||||
- (NSMutableArray *)cs_toastQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIView (MOPFATToast)
|
||||
|
||||
#pragma mark - Make Toast Methods
|
||||
|
||||
- (void)fatMakeToast:(NSString *)message {
|
||||
[self fatMakeToast:message duration:[CSToastManager defaultDuration] position:[CSToastManager defaultPosition] style:nil];
|
||||
}
|
||||
|
||||
- (void)fatMakeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position {
|
||||
[self fatMakeToast:message duration:duration position:position style:nil];
|
||||
}
|
||||
|
||||
- (void)fatMakeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position style:(CSToastStyle *)style {
|
||||
UIView *toast = [self fatToastViewForMessage:message title:nil image:nil style:style];
|
||||
[self fatShowToast:toast duration:duration position:position completion:nil];
|
||||
}
|
||||
|
||||
- (void)fatMakeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position title:(NSString *)title image:(UIImage *)image style:(CSToastStyle *)style completion:(void(^)(BOOL didTap))completion {
|
||||
UIView *toast = [self fatToastViewForMessage:message title:title image:image style:style];
|
||||
[self fatShowToast:toast duration:duration position:position completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark - Show Toast Methods
|
||||
|
||||
- (void)fatShowToast:(UIView *)toast {
|
||||
[self fatShowToast:toast duration:[CSToastManager defaultDuration] position:[CSToastManager defaultPosition] completion:nil];
|
||||
}
|
||||
|
||||
- (void)fatShowToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position completion:(void(^)(BOOL didTap))completion {
|
||||
// sanity
|
||||
if (toast == nil) return;
|
||||
|
||||
// store the completion block on the toast view
|
||||
objc_setAssociatedObject(toast, &CSToastCompletionKey, completion, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
if ([CSToastManager isQueueEnabled] && [self.cs_activeToasts count] > 0) {
|
||||
// we're about to queue this toast view so we need to store the duration and position as well
|
||||
objc_setAssociatedObject(toast, &CSToastDurationKey, @(duration), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
objc_setAssociatedObject(toast, &CSToastPositionKey, position, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
// enqueue
|
||||
[self.cs_toastQueue addObject:toast];
|
||||
} else {
|
||||
// present
|
||||
[self cs_showToast:toast duration:duration position:position];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Hide Toast Methods
|
||||
|
||||
- (void)fatHideToast {
|
||||
[self fatHideToast:[[self cs_activeToasts] firstObject]];
|
||||
}
|
||||
|
||||
- (void)fatHideToast:(UIView *)toast {
|
||||
// sanity
|
||||
if (!toast || ![[self cs_activeToasts] containsObject:toast]) return;
|
||||
|
||||
[self cs_hideToast:toast];
|
||||
}
|
||||
|
||||
- (void)fatHideAllToasts {
|
||||
[self fatHideAllToasts:NO clearQueue:YES];
|
||||
}
|
||||
|
||||
- (void)fatHideAllToasts:(BOOL)includeActivity clearQueue:(BOOL)clearQueue {
|
||||
if (clearQueue) {
|
||||
[self fatClearToastQueue];
|
||||
}
|
||||
|
||||
for (UIView *toast in [self cs_activeToasts]) {
|
||||
[self fatHideToast:toast];
|
||||
}
|
||||
|
||||
if (includeActivity) {
|
||||
[self fatHideToastActivity];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fatClearToastQueue {
|
||||
[[self cs_toastQueue] removeAllObjects];
|
||||
}
|
||||
|
||||
#pragma mark - Private Show/Hide Methods
|
||||
|
||||
- (void)cs_showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position {
|
||||
toast.center = [self cs_centerPointForPosition:position withToast:toast];
|
||||
toast.alpha = 0.0;
|
||||
|
||||
if ([CSToastManager isTapToDismissEnabled]) {
|
||||
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cs_handleToastTapped:)];
|
||||
[toast addGestureRecognizer:recognizer];
|
||||
toast.userInteractionEnabled = YES;
|
||||
toast.exclusiveTouch = YES;
|
||||
}
|
||||
|
||||
[[self cs_activeToasts] addObject:toast];
|
||||
|
||||
[self addSubview:toast];
|
||||
|
||||
[UIView animateWithDuration:[[CSToastManager sharedStyle] fadeDuration]
|
||||
delay:0.0
|
||||
options:(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction)
|
||||
animations:^{
|
||||
toast.alpha = 1.0;
|
||||
} completion:^(BOOL finished) {
|
||||
NSTimer *timer = [NSTimer timerWithTimeInterval:duration target:self selector:@selector(cs_toastTimerDidFinish:) userInfo:toast repeats:NO];
|
||||
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
||||
objc_setAssociatedObject(toast, &CSToastTimerKey, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)cs_hideToast:(UIView *)toast {
|
||||
[self cs_hideToast:toast fromTap:NO];
|
||||
}
|
||||
|
||||
- (void)cs_hideToast:(UIView *)toast fromTap:(BOOL)fromTap {
|
||||
NSTimer *timer = (NSTimer *)objc_getAssociatedObject(toast, &CSToastTimerKey);
|
||||
[timer invalidate];
|
||||
|
||||
[UIView animateWithDuration:[[CSToastManager sharedStyle] fadeDuration]
|
||||
delay:0.0
|
||||
options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
|
||||
animations:^{
|
||||
toast.alpha = 0.0;
|
||||
} completion:^(BOOL finished) {
|
||||
[toast removeFromSuperview];
|
||||
|
||||
// remove
|
||||
[[self cs_activeToasts] removeObject:toast];
|
||||
|
||||
// execute the completion block, if necessary
|
||||
void (^completion)(BOOL didTap) = objc_getAssociatedObject(toast, &CSToastCompletionKey);
|
||||
if (completion) {
|
||||
completion(fromTap);
|
||||
}
|
||||
|
||||
if ([self.cs_toastQueue count] > 0) {
|
||||
// dequeue
|
||||
UIView *nextToast = [[self cs_toastQueue] firstObject];
|
||||
[[self cs_toastQueue] removeObjectAtIndex:0];
|
||||
|
||||
// present the next toast
|
||||
NSTimeInterval duration = [objc_getAssociatedObject(nextToast, &CSToastDurationKey) doubleValue];
|
||||
id position = objc_getAssociatedObject(nextToast, &CSToastPositionKey);
|
||||
[self cs_showToast:nextToast duration:duration position:position];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - View Construction
|
||||
|
||||
- (UIView *)fatToastViewForMessage:(NSString *)message title:(NSString *)title image:(UIImage *)image style:(CSToastStyle *)style {
|
||||
// sanity
|
||||
if (message == nil && title == nil && image == nil) return nil;
|
||||
|
||||
// default to the shared style
|
||||
if (style == nil) {
|
||||
style = [CSToastManager sharedStyle];
|
||||
}
|
||||
|
||||
// dynamically build a toast view with any combination of message, title, & image
|
||||
UILabel *messageLabel = nil;
|
||||
UILabel *titleLabel = nil;
|
||||
UIImageView *imageView = nil;
|
||||
|
||||
UIView *wrapperView = [[UIView alloc] init];
|
||||
wrapperView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
|
||||
wrapperView.layer.cornerRadius = style.cornerRadius;
|
||||
|
||||
if (style.displayShadow) {
|
||||
wrapperView.layer.shadowColor = style.shadowColor.CGColor;
|
||||
wrapperView.layer.shadowOpacity = style.shadowOpacity;
|
||||
wrapperView.layer.shadowRadius = style.shadowRadius;
|
||||
wrapperView.layer.shadowOffset = style.shadowOffset;
|
||||
}
|
||||
|
||||
wrapperView.backgroundColor = style.backgroundColor;
|
||||
|
||||
if(image != nil) {
|
||||
imageView = [[UIImageView alloc] initWithImage:image];
|
||||
imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
imageView.frame = CGRectMake(style.horizontalPadding, style.verticalPadding, style.imageSize.width, style.imageSize.height);
|
||||
}
|
||||
|
||||
CGRect imageRect = CGRectZero;
|
||||
|
||||
if(imageView != nil) {
|
||||
imageRect.origin.x = style.horizontalPadding;
|
||||
imageRect.origin.y = style.verticalPadding;
|
||||
imageRect.size.width = imageView.bounds.size.width;
|
||||
imageRect.size.height = imageView.bounds.size.height;
|
||||
}
|
||||
|
||||
if (title != nil) {
|
||||
titleLabel = [[UILabel alloc] init];
|
||||
titleLabel.numberOfLines = style.titleNumberOfLines;
|
||||
titleLabel.font = style.titleFont;
|
||||
titleLabel.textAlignment = style.titleAlignment;
|
||||
titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
titleLabel.textColor = style.titleColor;
|
||||
titleLabel.backgroundColor = [UIColor clearColor];
|
||||
titleLabel.alpha = 1.0;
|
||||
titleLabel.text = title;
|
||||
|
||||
// size the title label according to the length of the text
|
||||
CGSize maxSizeTitle = CGSizeMake((self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, self.bounds.size.height * style.maxHeightPercentage);
|
||||
CGSize expectedSizeTitle = [titleLabel sizeThatFits:maxSizeTitle];
|
||||
// UILabel can return a size larger than the max size when the number of lines is 1
|
||||
expectedSizeTitle = CGSizeMake(MIN(maxSizeTitle.width, expectedSizeTitle.width), MIN(maxSizeTitle.height, expectedSizeTitle.height));
|
||||
titleLabel.frame = CGRectMake(0.0, 0.0, expectedSizeTitle.width, expectedSizeTitle.height);
|
||||
}
|
||||
|
||||
if (message != nil) {
|
||||
messageLabel = [[UILabel alloc] init];
|
||||
messageLabel.numberOfLines = style.messageNumberOfLines;
|
||||
messageLabel.font = style.messageFont;
|
||||
messageLabel.textAlignment = style.messageAlignment;
|
||||
messageLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
messageLabel.textColor = style.messageColor;
|
||||
messageLabel.backgroundColor = [UIColor clearColor];
|
||||
messageLabel.alpha = 1.0;
|
||||
messageLabel.text = message;
|
||||
|
||||
CGSize maxSizeMessage = CGSizeMake((self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, self.bounds.size.height * style.maxHeightPercentage);
|
||||
CGSize expectedSizeMessage = [messageLabel sizeThatFits:maxSizeMessage];
|
||||
// UILabel can return a size larger than the max size when the number of lines is 1
|
||||
expectedSizeMessage = CGSizeMake(MIN(maxSizeMessage.width, expectedSizeMessage.width), MIN(maxSizeMessage.height, expectedSizeMessage.height));
|
||||
messageLabel.frame = CGRectMake(0.0, 0.0, expectedSizeMessage.width, expectedSizeMessage.height);
|
||||
}
|
||||
|
||||
CGRect titleRect = CGRectZero;
|
||||
|
||||
if(titleLabel != nil) {
|
||||
titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding;
|
||||
titleRect.origin.y = style.verticalPadding;
|
||||
titleRect.size.width = titleLabel.bounds.size.width;
|
||||
titleRect.size.height = titleLabel.bounds.size.height;
|
||||
}
|
||||
|
||||
CGRect messageRect = CGRectZero;
|
||||
|
||||
if(messageLabel != nil) {
|
||||
messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding;
|
||||
messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding;
|
||||
messageRect.size.width = messageLabel.bounds.size.width;
|
||||
messageRect.size.height = messageLabel.bounds.size.height;
|
||||
}
|
||||
|
||||
CGFloat longerWidth = MAX(titleRect.size.width, messageRect.size.width);
|
||||
CGFloat longerX = MAX(titleRect.origin.x, messageRect.origin.x);
|
||||
|
||||
// Wrapper width uses the longerWidth or the image width, whatever is larger. Same logic applies to the wrapper height.
|
||||
CGFloat wrapperWidth = MAX((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding));
|
||||
CGFloat wrapperHeight = MAX((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0)));
|
||||
|
||||
wrapperView.frame = CGRectMake(0.0, 0.0, wrapperWidth, wrapperHeight);
|
||||
|
||||
if(titleLabel != nil) {
|
||||
titleLabel.frame = titleRect;
|
||||
[wrapperView addSubview:titleLabel];
|
||||
}
|
||||
|
||||
if(messageLabel != nil) {
|
||||
messageLabel.frame = messageRect;
|
||||
[wrapperView addSubview:messageLabel];
|
||||
}
|
||||
|
||||
if(imageView != nil) {
|
||||
[wrapperView addSubview:imageView];
|
||||
}
|
||||
|
||||
return wrapperView;
|
||||
}
|
||||
|
||||
#pragma mark - Storage
|
||||
|
||||
- (NSMutableArray *)cs_activeToasts {
|
||||
NSMutableArray *cs_activeToasts = objc_getAssociatedObject(self, &CSToastActiveKey);
|
||||
if (cs_activeToasts == nil) {
|
||||
cs_activeToasts = [[NSMutableArray alloc] init];
|
||||
objc_setAssociatedObject(self, &CSToastActiveKey, cs_activeToasts, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
return cs_activeToasts;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)cs_toastQueue {
|
||||
NSMutableArray *cs_toastQueue = objc_getAssociatedObject(self, &CSToastQueueKey);
|
||||
if (cs_toastQueue == nil) {
|
||||
cs_toastQueue = [[NSMutableArray alloc] init];
|
||||
objc_setAssociatedObject(self, &CSToastQueueKey, cs_toastQueue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
return cs_toastQueue;
|
||||
}
|
||||
|
||||
#pragma mark - Events
|
||||
|
||||
- (void)cs_toastTimerDidFinish:(NSTimer *)timer {
|
||||
[self cs_hideToast:(UIView *)timer.userInfo];
|
||||
}
|
||||
|
||||
- (void)cs_handleToastTapped:(UITapGestureRecognizer *)recognizer {
|
||||
UIView *toast = recognizer.view;
|
||||
NSTimer *timer = (NSTimer *)objc_getAssociatedObject(toast, &CSToastTimerKey);
|
||||
[timer invalidate];
|
||||
|
||||
[self cs_hideToast:toast fromTap:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Activity Methods
|
||||
|
||||
- (void)fatMakeToastActivity:(id)position {
|
||||
// sanity
|
||||
UIView *existingActivityView = (UIView *)objc_getAssociatedObject(self, &CSToastActivityViewKey);
|
||||
if (existingActivityView != nil) return;
|
||||
|
||||
CSToastStyle *style = [CSToastManager sharedStyle];
|
||||
|
||||
UIView *activityView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, style.activitySize.width, style.activitySize.height)];
|
||||
activityView.center = [self cs_centerPointForPosition:position withToast:activityView];
|
||||
activityView.backgroundColor = style.backgroundColor;
|
||||
activityView.alpha = 0.0;
|
||||
activityView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
|
||||
activityView.layer.cornerRadius = style.cornerRadius;
|
||||
|
||||
if (style.displayShadow) {
|
||||
activityView.layer.shadowColor = style.shadowColor.CGColor;
|
||||
activityView.layer.shadowOpacity = style.shadowOpacity;
|
||||
activityView.layer.shadowRadius = style.shadowRadius;
|
||||
activityView.layer.shadowOffset = style.shadowOffset;
|
||||
}
|
||||
|
||||
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
|
||||
activityIndicatorView.center = CGPointMake(activityView.bounds.size.width / 2, activityView.bounds.size.height / 2);
|
||||
[activityView addSubview:activityIndicatorView];
|
||||
[activityIndicatorView startAnimating];
|
||||
|
||||
// associate the activity view with self
|
||||
objc_setAssociatedObject (self, &CSToastActivityViewKey, activityView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
[self addSubview:activityView];
|
||||
|
||||
[UIView animateWithDuration:style.fadeDuration
|
||||
delay:0.0
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
activityView.alpha = 1.0;
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
- (void)fatHideToastActivity {
|
||||
UIView *existingActivityView = (UIView *)objc_getAssociatedObject(self, &CSToastActivityViewKey);
|
||||
if (existingActivityView != nil) {
|
||||
[UIView animateWithDuration:[[CSToastManager sharedStyle] fadeDuration]
|
||||
delay:0.0
|
||||
options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
|
||||
animations:^{
|
||||
existingActivityView.alpha = 0.0;
|
||||
} completion:^(BOOL finished) {
|
||||
[existingActivityView removeFromSuperview];
|
||||
objc_setAssociatedObject (self, &CSToastActivityViewKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (CGPoint)cs_centerPointForPosition:(id)point withToast:(UIView *)toast {
|
||||
CSToastStyle *style = [CSToastManager sharedStyle];
|
||||
|
||||
UIEdgeInsets safeInsets = UIEdgeInsetsZero;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
safeInsets = self.safeAreaInsets;
|
||||
}
|
||||
|
||||
CGFloat topPadding = style.verticalPadding + safeInsets.top;
|
||||
CGFloat bottomPadding = style.verticalPadding + safeInsets.bottom;
|
||||
|
||||
if([point isKindOfClass:[NSString class]]) {
|
||||
if([point caseInsensitiveCompare:CSToastPositionTop] == NSOrderedSame) {
|
||||
return CGPointMake(self.bounds.size.width / 2.0, (toast.frame.size.height / 2.0) + topPadding);
|
||||
} else if([point caseInsensitiveCompare:CSToastPositionCenter] == NSOrderedSame) {
|
||||
return CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0);
|
||||
}
|
||||
} else if ([point isKindOfClass:[NSValue class]]) {
|
||||
return [point CGPointValue];
|
||||
}
|
||||
|
||||
// default to bottom
|
||||
return CGPointMake(self.bounds.size.width / 2.0, (self.bounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSToastStyle
|
||||
|
||||
#pragma mark - Constructors
|
||||
|
||||
- (instancetype)initWithDefaultStyle {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
|
||||
self.titleColor = [UIColor whiteColor];
|
||||
self.messageColor = [UIColor whiteColor];
|
||||
self.maxWidthPercentage = 0.8;
|
||||
self.maxHeightPercentage = 0.8;
|
||||
self.horizontalPadding = 10.0;
|
||||
self.verticalPadding = 10.0;
|
||||
self.cornerRadius = 10.0;
|
||||
self.titleFont = [UIFont boldSystemFontOfSize:16.0];
|
||||
self.messageFont = [UIFont systemFontOfSize:16.0];
|
||||
self.titleAlignment = NSTextAlignmentLeft;
|
||||
self.messageAlignment = NSTextAlignmentLeft;
|
||||
self.titleNumberOfLines = 0;
|
||||
self.messageNumberOfLines = 0;
|
||||
self.displayShadow = NO;
|
||||
self.shadowOpacity = 0.8;
|
||||
self.shadowRadius = 6.0;
|
||||
self.shadowOffset = CGSizeMake(4.0, 4.0);
|
||||
self.imageSize = CGSizeMake(80.0, 80.0);
|
||||
self.activitySize = CGSizeMake(100.0, 100.0);
|
||||
self.fadeDuration = 0.2;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setMaxWidthPercentage:(CGFloat)maxWidthPercentage {
|
||||
_maxWidthPercentage = MAX(MIN(maxWidthPercentage, 1.0), 0.0);
|
||||
}
|
||||
|
||||
- (void)setMaxHeightPercentage:(CGFloat)maxHeightPercentage {
|
||||
_maxHeightPercentage = MAX(MIN(maxHeightPercentage, 1.0), 0.0);
|
||||
}
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE {
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface CSToastManager ()
|
||||
|
||||
@property (strong, nonatomic) CSToastStyle *sharedStyle;
|
||||
@property (assign, nonatomic, getter=isTapToDismissEnabled) BOOL tapToDismissEnabled;
|
||||
@property (assign, nonatomic, getter=isQueueEnabled) BOOL queueEnabled;
|
||||
@property (assign, nonatomic) NSTimeInterval defaultDuration;
|
||||
@property (strong, nonatomic) id defaultPosition;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSToastManager
|
||||
|
||||
#pragma mark - Constructors
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
static CSToastManager *_sharedManager = nil;
|
||||
static dispatch_once_t oncePredicate;
|
||||
dispatch_once(&oncePredicate, ^{
|
||||
_sharedManager = [[self alloc] init];
|
||||
});
|
||||
|
||||
return _sharedManager;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.sharedStyle = [[CSToastStyle alloc] initWithDefaultStyle];
|
||||
self.tapToDismissEnabled = YES;
|
||||
self.queueEnabled = NO;
|
||||
self.defaultDuration = 3.0;
|
||||
self.defaultPosition = CSToastPositionBottom;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Singleton Methods
|
||||
|
||||
+ (void)setSharedStyle:(CSToastStyle *)sharedStyle {
|
||||
[[self sharedManager] setSharedStyle:sharedStyle];
|
||||
}
|
||||
|
||||
+ (CSToastStyle *)sharedStyle {
|
||||
return [[self sharedManager] sharedStyle];
|
||||
}
|
||||
|
||||
+ (void)setTapToDismissEnabled:(BOOL)tapToDismissEnabled {
|
||||
[[self sharedManager] setTapToDismissEnabled:tapToDismissEnabled];
|
||||
}
|
||||
|
||||
+ (BOOL)isTapToDismissEnabled {
|
||||
return [[self sharedManager] isTapToDismissEnabled];
|
||||
}
|
||||
|
||||
+ (void)setQueueEnabled:(BOOL)queueEnabled {
|
||||
[[self sharedManager] setQueueEnabled:queueEnabled];
|
||||
}
|
||||
|
||||
+ (BOOL)isQueueEnabled {
|
||||
return [[self sharedManager] isQueueEnabled];
|
||||
}
|
||||
|
||||
+ (void)setDefaultDuration:(NSTimeInterval)duration {
|
||||
[[self sharedManager] setDefaultDuration:duration];
|
||||
}
|
||||
|
||||
+ (NSTimeInterval)defaultDuration {
|
||||
return [[self sharedManager] defaultDuration];
|
||||
}
|
||||
|
||||
+ (void)setDefaultPosition:(id)position {
|
||||
if ([position isKindOfClass:[NSString class]] || [position isKindOfClass:[NSValue class]]) {
|
||||
[[self sharedManager] setDefaultPosition:position];
|
||||
}
|
||||
}
|
||||
|
||||
+ (id)defaultPosition {
|
||||
return [[self sharedManager] defaultPosition];
|
||||
}
|
||||
|
||||
@end
|
|
@ -16,7 +16,6 @@ A finclip miniprogram flutter sdk.
|
|||
s.public_header_files = 'Classes/**/*.h'
|
||||
s.dependency 'Flutter'
|
||||
s.ios.deployment_target = '9.0'
|
||||
|
||||
s.dependency 'FinApplet' , '2.39.1'
|
||||
s.dependency 'FinAppletExt' , '2.39.1'
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue