美文网首页
IOS Toast提示的学习笔记

IOS Toast提示的学习笔记

作者: 寻心_0a46 | 来源:发表于2019-07-14 17:52 被阅读0次
    Toast提示简介

    摘抄自(龙爪槐守望者),他介绍的太形象了,让人忍不住想拷贝过来,尤其是这张图片,绝对的治愈系,甚至都有点饿了......
    除了Android规范,Windows的规范中也有Toast,但定义不一样。Toast在Android中的定义就是大家所熟悉的黑色半透明提示,而在Windows的规范中Toast概念几乎等同于Android的一条Notification(通知)。
    Windows和Android的Toast有着千丝万缕的联系,据说一位微软前员工在开发MSN Messenger时,觉得MSN弹出通知方式很像烤面包(Toast)烤熟时从烤面包机(Toaster)里弹出来一样,因此把这种提示方式命名为Toast,后来这位微软前员工带着这一习惯命名跳槽去了Google。

    屏幕快照 2019-07-11 下午11.23.33.png
    IOS中的Toast

    iOS并没有Toast这个控件,但iOS中确实有类似于Toast样式出现,例如iOS的音量调节提示。 iOS 把这个组件叫做 UIProgressHUD,可惜这个组件是系统私有的,第三方App无法直接获取使用,因此出现了各种模仿它的第三方控件,一个IOS中的Toast提示的gitHub地址:https://github.com/scalessec/Toast

    Toast的优缺点

    优点:
    1.占用屏幕空间小。
    2.不会打断用户操作。
    3.使用简单适用范围广。
    缺点:
    1.出现时间短,在碎片化时代注意力不集中容易错过Toast提示。
    2.虽然非模态,但是黑乎乎的样式上给人一种模态的错觉。
    3.遮盖其他控件,但不能对Toast进行交互。
    基本介绍就这么多了,想看详细的请点击开头的链接,接下来我们开始烤面包吧.....
    这是个很简洁的framework,只有两个头文件

    //
    //  Toast.h
    //  Toast
    //
    //  Created by Charles Scalesse on 11/3/16.
    //
    //
    
    #import <UIKit/UIKit.h>
    
    //Toast的项目版本号
    FOUNDATION_EXPORT double ToastVersionNumber;
    
    //Toast的项目版本字符串
    FOUNDATION_EXPORT const unsigned char ToastVersionString[];
    
    // 在这个标题中,您应该使用诸如import<toast/public header.h>
    #import <Toast/UIView+Toast.h>
    
    //
    //  UIView+Toast.h
    //  Toast
    //
    #import <UIKit/UIKit.h>
    //Toast定义的三个基本位置常量
    extern const NSString * CSToastPositionTop;
    extern const NSString * CSToastPositionCenter;
    extern const NSString * CSToastPositionBottom;
    
    @class CSToastStyle;
    
    /**
     Toast是一个Objective-C类别,它将Toast通知添加到uiview
    对象类。它的目的是简单,轻便,易于使用。大多数
    Toast通知可以用一行代码触发。
    
    'maketoast:'方法创建一个新视图,然后将其显示为toast。
    
    'showtoast:'方法将任何视图显示为toast。
     
     */
    @interface UIView (Toast)
    
    /**
     创建并显示带有消息的新Toast视图,并用
    默认持续时间和位置。使用共享样式设置样式。
    
    @参数message:要显示的消息
     */
    - (void)makeToast:(NSString *)message;
    
    /**
     创建并显示带有消息的新Toast视图。持续时间和位置
    可以显式设置。使用共享样式设置样式。
    
    @参数message:要显示的消息
    @参数duration :Toast持续时间
    @参数position:定位吐司的中心点。可以是预定义的CSTOastPosition之一
    常量或包装在“nsvalue”对象中的“cgpoint”。
     */
    - (void)makeToast:(NSString *)message
             duration:(NSTimeInterval)duration
             position:(id)position;
    
    /**
     创建并显示带有消息的新Toast视图。持续时间、位置和
    可以显式设置样式。
    
    @参数message:要显示的消息
    @参数duration :Toast持续时间
    @参数position:定位吐司的中心点。可以是预定义的CSTOastPosition之一
    常量或包装在“nsvalue”对象中的“cgpoint”。
    @参数style:nil时将使用共享样式
     */
    - (void)makeToast:(NSString *)message
             duration:(NSTimeInterval)duration
             position:(id)position
                style:(CSToastStyle *)style;
    
    /**
    创建并显示带有消息、标题和图像的新Toast视图。持续时间,
    位置和样式可以显式设置。当
    Toast视图完成。`如果toast视图从tap中取消,则didtap“将是”yes“。
     
    @参数message:要显示的消息
    @参数duration :Toast持续时间
    @参数position:定位Toast的中心点。可以是预定义的CSTOastPosition之一
    常量或包装在“nsvalue”对象中的“cgpoint”。
    @参数title:标题
    @参数image:图像
    @参数style:样式。nil时将使用共享样式
    @param completion :toast视图消失后执行的完成块。
    如果toast视图从tap中取消,则didtap将为“是”。
     */
    - (void)makeToast:(NSString *)message
             duration:(NSTimeInterval)duration
             position:(id)position
                title:(NSString *)title
                image:(UIImage *)image
                style:(CSToastStyle *)style
           completion:(void(^)(BOOL didTap))completion;
    
    /**
    创建一个新的toast视图,其中包含消息、标题和图像的任何组合。
    外观和感觉是通过样式配置的。与“makeToast:”方法不同,
    此方法不会自动呈现toast视图。其中一个toast是:
    方法必须用于显示结果视图。
    
    @ 警告:如果消息、标题和图像都是nil,这个方法将返回nil。
    @参数message:要显示的消息
    @参数title:标题
    @参数image:图像
    @参数style:样式。nil时将使用共享样式
    @返回新创建的Toast视图
     */
    - (UIView *)toastViewForMessage:(NSString *)message
                              title:(NSString *)title
                              image:(UIImage *)image
                              style:(CSToastStyle *)style;
    
    /**
     隐藏活跃的Toast。如果一个视图中有多个激活的toast,则此方法
     隐藏最古老的toast(第一个被展示的toast)。
     */
    - (void)hideToast;
    
    /**
     隐藏一个活跃的toast。
    
    @param toast:活跃的toast视图将被取消。任何正在展示的吐司
    在屏幕上被认为是活跃的。
    
    @警告:这没有清除当前正在队列中等待的toast视图。
     */
    - (void)hideToast:(UIView *)toast;
    
    /**
    隐藏所有活动的Toast视图并清除队列。
     */
    - (void)hideAllToasts;
    
    /**
    隐藏所有活动的Toast视图,并提供隐藏活动和清除队列的选项。
    
    @参数includeActivity:如果为“true”,toast活动也将被隐藏。默认值为“false”。
    @param clearqueue:如果为“true”,则从队列中删除所有toast视图。默认值为“true”。
     */
    - (void)hideAllToasts:(BOOL)includeActivity clearQueue:(BOOL)clearQueue;
    
    /**
     从队列中删除所有toast视图。这对活跃的toast视图没有影响。
     */
    - (void)clearToastQueue;
    
    /**
    在指定位置创建并显示新的toast活动指示器视图。
    @警告:每个父视图只能显示一个toast活动指示器视图。后续
    对“makeToastActivity:”的调用将被忽略,直到调用hideToastActivity。
    @警告` maketoastactivity:`独立于showtoas:方法工作。
    在显示Toast视图时,可以显示和取消活跃的Toast视图。“makeToastActivity:”
    对showToast:方法的排队行为没有影响。
    
    @参数position:定位toast的中心点。可以是预定义的CSTOastPosition之一
    常量或包装在“nsvalue”对象中的“cgpoint”。
     */
    - (void)makeToastActivity:(id)position;
    
    /**
    关闭活动的Toast活动指示器视图。
     */
    - (void)hideToastActivity;
    
    /**
     使用默认的持续时间和位置将任何视图显示为toast。
    
    @参数toast:将显示为toast的视图
     */
    - (void)showToast:(UIView *)toast;
    
    /**
     在指定的位置和持续时间内将任何视图显示为toast。当toast视图完成时,完成块执行。
    如果toast视图从tap中被取消,则“didTap”将变为“YES”。
    
    @参数Toast:要显示为Toast的视图
    @参数duration:Toast持续时间
    @参数Toast:定位Toast的中心点。可以是预定义的CSTOastPosition之一
    常量或包装在“nsvalue”对象中的“cgpoint”。
    @参数completion:toast视图消失后执行的完成块。
    如果toast视图从tap中取消,则didtap将为“是”。
     */
    - (void)showToast:(UIView *)toast
             duration:(NSTimeInterval)duration
             position:(id)position
           completion:(void(^)(BOOL didTap))completion;
    
    @end
    
    /**
     “CSToastStyle”实例定义通过“makeToast:”方法创建的toast视图的外观,
    以及直接使用toastViewForMessage:title:image:style:”创建的toast视图的外观。
    
    @警告:' CSToastStyle '为默认toast视图提供了相对简单的样式选项。
    如果需要一个具有更复杂UI的toast视图,
    创建自定义UIView子类并使用' showToast: '方法来呈现它可能更有意义。
     */
    @interface CSToastStyle : NSObject
    
    /**
    背景颜色。默认为' [UIColor blackColor] ',不透明度为80%。 */
    @property (strong, nonatomic) UIColor *backgroundColor;
    
    /**
    标题的颜色。默认值是' [UIColor whiteColor] '。
     */
    @property (strong, nonatomic) UIColor *titleColor;
    
    /**
    消息的颜色。默认值是' [UIColor whiteColor] '。
     */
    @property (strong, nonatomic) UIColor *messageColor;
    
    /**
     从0.0到1.0的百分比值,表示Toast视图相对于其SuperView的最大宽度。
    默认值为0.8(SuperView宽度的80%)。
     */
    @property (assign, nonatomic) CGFloat maxWidthPercentage;
    
    /**
     从0.0到1.0的百分比值,表示Toast视图相对于其SuperView的最大高度。
    默认值为0.8(SuperView高度的80%)。
     */
    @property (assign, nonatomic) CGFloat maxHeightPercentage;
    
    /**
     从toast视图的水平边缘到内容的间距。
    当图像出现时,这也用作图像和文本之间的填充。
    默认是10.0。
     */
    @property (assign, nonatomic) CGFloat horizontalPadding;
    
    /**
    从Toast视图的垂直边缘到内容的间距。
    当出现标题时,它也用作标题和消息之间的填充。
    默认值为10.0。
     */
    @property (assign, nonatomic) CGFloat verticalPadding;
    
    /**
     圆角半径。默认是10.0。
     */
    @property (assign, nonatomic) CGFloat cornerRadius;
    
    /**
    标题字体。默认值为`[uifont-boldSystemFontOfSize:16.0]`。
     */
    @property (strong, nonatomic) UIFont *titleFont;
    
    /**
    消息的字体。默认值是' [UIFont systemFontOfSize:16.0] '。
     */
    @property (strong, nonatomic) UIFont *messageFont;
    
    /**
    标题文本对齐。默认设置是“NSTextAlignmentLeft”。
     */
    @property (assign, nonatomic) NSTextAlignment titleAlignment;
    
    /**
    消息文本对齐。默认设置是“NSTextAlignmentLeft”。
     */
    @property (assign, nonatomic) NSTextAlignment messageAlignment;
    
    /**
    标题的最大行数。默认值为0(无限制)。
     */
    @property (assign, nonatomic) NSInteger titleNumberOfLines;
    
    /**
    消息的最大行数。默认值是0(没有限制)。
     */
    @property (assign, nonatomic) NSInteger messageNumberOfLines;
    
    /**
    启用或禁用Toast视图上的阴影。默认值为“否”。
     */
    @property (assign, nonatomic) BOOL displayShadow;
    
    /**
    阴影颜色。默认值为`[uicolor blackcolor]`。
     */
    @property (strong, nonatomic) UIColor *shadowColor;
    
    /**
    值从0.0到1.0,表示阴影的不透明度。
    默认值为0.8(80%不透明度)。
     */
    @property (assign, nonatomic) CGFloat shadowOpacity;
    
    /**
    阴影半径。默认是6.0。
     */
    @property (assign, nonatomic) CGFloat shadowRadius;
    
    /**
    阴影偏移量。默认值是' CGSizeMake(4.0, 4.0) '。
     */
    @property (assign, nonatomic) CGSize shadowOffset;
    
    /**
    图像的大小。默认值是“CGSizeMake(80.0, 80.0)”。
     */
    @property (assign, nonatomic) CGSize imageSize;
    
    /**
    调用“makeToastActivity:”时toast活动视图的大小。
    默认值是“CGSizeMake(100.0, 100.0)”。
     */
    @property (assign, nonatomic) CGSize activitySize;
    
    /**
    淡入/淡出动画持续时间。默认是0.2。
    
     */
    @property (assign, nonatomic) NSTimeInterval fadeDuration;
    
    /**
    创建一个“CSToastStyle”的新实例,并设置所有默认值。
     */
    - (instancetype)initWithDefaultStyle NS_DESIGNATED_INITIALIZER;
    
    /**
     @警告:只应该使用指定的初始化器来创建“CSToastStyle”的实例。
     */
    - (instancetype)init NS_UNAVAILABLE;
    
    @end
    
    /**
     “CSToastManager”为所有toast通知提供一般配置选项。由单例实例支持。
     */
    @interface CSToastManager : NSObject
    
    /**
     在单例上设置共享样式。使用nil样式调用“makeToast:”方法
    (或“toastViewForMessage:title:image:style:”)时,将使用共享样式。
    默认情况下,这被设置为' CSToastStyle '的默认样式。
    @参数sharedStyle:共享样式
     */
    + (void)setSharedStyle:(CSToastStyle *)sharedStyle;
    
    /**
     从singlton获取共享样式。默认情况下,这是“CSToastStyle”的默认样式。
    
    @ return 共享样式
     */
    + (CSToastStyle *)sharedStyle;
    
    /**
    启用或禁用tap在toast视图上消失。默认是“是的”。
    
    @参数 tapToDismissEnabled:是或不是
     */
    + (void)setTapToDismissEnabled:(BOOL)tapToDismissEnabled;
    
    /**
    如果启用了tap to dismiss,则返回“YES”,否则返回“NO”。
    默认是“是的”。
    
    @return BOOL是或不是
     */
    + (BOOL)isTapToDismissEnabled;
    
    /**
     启用或禁用toast视图的排队行为。当回答“是”时,toast视图将一个接一个
    地出现。当“否”时,多个Toast视图将同时出现(根据它们的位置可能重叠)。
    这对toast活动视图没有影响,它独立于正常的toast视图运行。默认设置是“不”。
    
    @参数queueEnabled:是或否
     */
    + (void)setQueueEnabled:(BOOL)queueEnabled;
    
    /**
    如果启用队列,则返回“YES”,否则返回“NO”。默认设置是“不”。
    
    @return BOOL
     */
    + (BOOL)isQueueEnabled;
    
    /**
     设置默认持续时间。用于不需要显式持续时间的“makeToast:”和“showToast:”方法。默认是3.0。
    @参数 duration:toast 显示持续时间
     */
    + (void)setDefaultDuration:(NSTimeInterval)duration;
    
    /**
    返回默认持续时间。默认是3.0。
    
     @return duration The toast duration
    */
    + (NSTimeInterval)defaultDuration;
    
    /**
     设置默认位置。用于不需要明确位置的“makeToast:”和“showToast:”方法。默认设置是“CSToastPositionBottom”。
    参数position:可以是预定义的CSToastPosition常量之一,也可以是包装在“NSValue”对象中的“CGPoint”。
     */
    + (void)setDefaultPosition:(id)position;
    
    /**
    返回默认toast位置。默认设置是“CSToastPositionBottom”。
    @return定位默认中心点。将是预定义的CSToastPosition常量之一,或包装在' NSValue '对象中的' CGPoint '。
     */
    + (id)defaultPosition;
    
    @end
    
    
    //
    //  UIView+Toast.m
    //  Toast
    //
    //  Copyright (c) 2011-2017 Charles Scalesse.
    //
    
    
    #import "UIView+Toast.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 (Toast)
    
    #pragma mark - Make Toast Methods
    
    - (void)makeToast:(NSString *)message {
        [self makeToast:message duration:[CSToastManager defaultDuration] position:[CSToastManager defaultPosition] style:nil];
    }
    
    - (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position {
        [self makeToast:message duration:duration position:position style:nil];
    }
    
    - (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position style:(CSToastStyle *)style {
        UIView *toast = [self toastViewForMessage:message title:nil image:nil style:style];
        [self showToast:toast duration:duration position:position completion:nil];
    }
    
    - (void)makeToast:(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 toastViewForMessage:message title:title image:image style:style];
        [self showToast:toast duration:duration position:position completion:completion];
    }
    
    #pragma mark - Show Toast Methods
    
    - (void)showToast:(UIView *)toast {
        [self showToast:toast duration:[CSToastManager defaultDuration] position:[CSToastManager defaultPosition] completion:nil];
    }
    
    - (void)showToast:(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)hideToast {
        [self hideToast:[[self cs_activeToasts] firstObject]];
    }
    
    - (void)hideToast:(UIView *)toast {
        // sanity
        if (!toast || ![[self cs_activeToasts] containsObject:toast]) return;
        
        [self cs_hideToast:toast];
    }
    
    - (void)hideAllToasts {
        [self hideAllToasts:NO clearQueue:YES];
    }
    
    - (void)hideAllToasts:(BOOL)includeActivity clearQueue:(BOOL)clearQueue {
        if (clearQueue) {
            [self clearToastQueue];
        }
        
        for (UIView *toast in [self cs_activeToasts]) {
            [self hideToast:toast];
        }
        
        if (includeActivity) {
            [self hideToastActivity];
        }
    }
    
    - (void)clearToastQueue {
        [[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 *)toastViewForMessage:(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)makeToastActivity:(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)hideToastActivity {
        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
    
    
    //
    //  ViewController.m
    
    #import "ViewController.h"
    #import "Masonry.h"
    #import "Toast.h"
    
    @interface ViewController ()
    
    @property(nonatomic, strong)UIView *redView;
    @property(nonatomic, strong)UIView *greenView;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button setTitle:@"显示Toast" forState:UIControlStateNormal];
        [button setBackgroundColor:[UIColor blueColor]];
        [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        [button mas_makeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(self.view);
            make.left.right.equalTo(self.view);
        }];
        
    }
    
    - (void)buttonClick{
        [self.view makeToast:@"toast出现了"];
    }
    
    @end
    
    
    Toast最开始的默认样式: Untitled.gif
    - (void)buttonClick{
        [self.view makeToast:@"Toast出现了" duration:3 position:CSToastPositionCenter];
    }
    
    
    简单的修改了位置,与显示的时长: Untitled.gif
    - (void)buttonClick{
        
        CSToastStyle *style = [[CSToastStyle alloc]initWithDefaultStyle];
        style.backgroundColor = [UIColor redColor];
        style.imageSize = CGSizeMake(50, 40);
        style.messageColor = [UIColor greenColor];
        style.messageFont = [UIFont boldSystemFontOfSize:10.0];
        style.messageAlignment = NSTextAlignmentCenter;
        style.titleColor = [UIColor yellowColor];
        style.titleFont = [UIFont boldSystemFontOfSize:10.0];
        style.titleAlignment = NSTextAlignmentCenter;
        style.verticalPadding = 15;
        style.horizontalPadding = 15;
        style.maxHeightPercentage = 0.5;
        style.maxWidthPercentage = 0.5;
        
        [self.view makeToast:@"Toast出现了" duration:3 position:CSToastPositionCenter title:@"我是Toast啊" image:[UIImage imageNamed:@"Toast"] style:style completion:^(BOOL didTap) {
            NSLog(@"显示成功了");
        }];
    }
    

    修改了默认样式,花里胡哨版:

    当Toast被设置图片时,verticalPadding将调整Toast与图片中心点的下间距,而horizontalPadding将调整Toast与图片中心点的右间距,而不合理的设置将击穿maxHeightPercentage与maxWidthPercentage对Toast大小的限制,图片总是居于左侧,标题与消息居于右侧,标题居于消息之上,这种布局是固定的。(当然这些都是我试试之后的猜测......) Untitled.gif
    但是如果想要一个这样的Toast布局呢: 屏幕快照 2019-07-14 下午4.24.56.png

    那就需要自定义一个View了,然后调用showToast方法了,此时Toast提供的默认属性将全部失效,布局,样式都需要写在自定义的视图中,但是设置的显示时长及显示成功后的回调用还是会执行的。

    //
    //  ToastView.h
    //  FrameworksTest
    //
    
    #import <UIKit/UIKit.h>
    
    
    @interface ToastView : UIView
    
    - (instancetype)initWithFrame:(CGRect)frame andImage:(UIImage *)image andMessage:(NSString *)meaage andTitle:(NSString *)tilte;
    
    @end
    
    
    //
    //  ToastView.m
    //  FrameworksTest
    
    
    #import "ToastView.h"
    #import "Masonry.h"
    
    @interface ToastView()
    
    @property (nonatomic, strong)UILabel *titleLabel;
    @property (nonatomic, strong)UILabel *messageLabel;
    @property (nonatomic, strong)UIImageView *toastImageView;
    
    @end
    
    @implementation ToastView
    
    - (instancetype)initWithFrame:(CGRect)frame andImage:(UIImage *)image andMessage:(NSString *)meaage andTitle:(NSString *)tilte{
        self = [super initWithFrame:frame];
        if (self) {
            self.backgroundColor = [UIColor greenColor];
            [self drawViewAndImage:image andMessage:meaage andTitle:tilte];
        }
        return self;
    }
    
    - (void)drawViewAndImage:(UIImage *)image andMessage:(NSString *)meaage andTitle:(NSString *)tilte{
        
        self.toastImageView = [[UIImageView alloc]initWithFrame:CGRectZero];
        [self.toastImageView setImage:image];
        [self addSubview:self.toastImageView];
        
        self.titleLabel = [[UILabel alloc]initWithFrame:CGRectZero];
        self.titleLabel.text = tilte;
        self.titleLabel.font = [UIFont boldSystemFontOfSize:15];
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        self.titleLabel.numberOfLines = 0;
        self.titleLabel.textColor = [UIColor redColor];
        [self addSubview:self.titleLabel];
        
        self.messageLabel = [[UILabel alloc]initWithFrame:CGRectZero];
        self.messageLabel.text = meaage;
        self.messageLabel.font = [UIFont boldSystemFontOfSize:15];
        self.messageLabel.textAlignment = NSTextAlignmentCenter;
        self.messageLabel.numberOfLines = 0;
        self.messageLabel.textColor = [UIColor redColor];
        [self addSubview:self.messageLabel];
        
        [self setUpdateConstraints];
    
    }
    
    - (void)setUpdateConstraints{
        
        [self.toastImageView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.center.equalTo(self);
            make.size.mas_equalTo(CGSizeMake(80, 80));
        }];
        
        [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.toastImageView.mas_bottom).offset(10);
            make.left.equalTo(self).offset(10);
            make.right.equalTo(self).offset(-10);
        }];
        
        [self.messageLabel mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.titleLabel.mas_bottom).offset(10);
            make.left.equalTo(self).offset(10);
            make.right.equalTo(self).offset(-10);
        }];
        
    }
    @end
    
    
    - (void)buttonClick{
        ToastView *tv = [[ToastView alloc]initWithFrame:CGRectZero andImage:[UIImage imageNamed:@"Toast"] andMessage:@"Toastchu出现了" andTitle:@"我是Toasta啊"];
        [self.view addSubview:tv];
        [tv mas_makeConstraints:^(MASConstraintMaker *make) {
            make.center.equalTo(self.view);
            make.size.mas_equalTo(CGSizeMake(200, 250));
        }];
        [self.view showToast:tv duration:6.0 position:CSToastPositionCenter completion:^(BOOL didTap) {
            NSLog(@"显示成功");
        }];
    }
    
    自定义的Toast: Untitled.gif

    可以为Toast设置队列,让Toast排队显示:

    #import "ViewController.h"
    #import "Masonry.h"
    #import "Toast.h"
    #import "ToastView.h"
    
    @interface ViewController ()
    
    @property(nonatomic, strong)UIView *redView;
    @property(nonatomic, strong)UIView *greenView;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button setTitle:@"显示Toast" forState:UIControlStateNormal];
        [button setBackgroundColor:[UIColor blueColor]];
        [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        [button mas_makeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(self.view);
            make.left.equalTo(self.view);
            make.width.mas_equalTo(self.view.frame.size.width / 2);
        }];
        
        UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [cancelButton setTitle:@"隐藏Toast" forState:UIControlStateNormal];
        [cancelButton setBackgroundColor:[UIColor redColor]];
        [cancelButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [cancelButton addTarget:self action:@selector(cancelButtonClick) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:cancelButton];
        [cancelButton mas_makeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(self.view);
            make.left.equalTo(button.mas_right);
            make.width.mas_equalTo(self.view.frame.size.width / 2);
        }];
        
    }
    
    - (void)buttonClick{
        
        [CSToastManager setQueueEnabled:YES];
        
        [self.view makeToast:@"我是Toast 1" duration:2.0
            position:CSToastPositionTop];
        
        [self.view makeToast:@"我是Toast 2" duration:2.0
            position:CSToastPositionCenter];
        
        [self.view makeToast:@"我是Toast 3" duration:2.0
            position:CSToastPositionBottom];
        
    }
    
    - (void)cancelButtonClick{
        [self.view hideToast];
    }
    @end
    
    
    Untitled.gif

    而隐藏Toast提供多种方式,如果隐藏没有清除排队的队列,当显示的Toast被隐藏后,队列中的Toast还会继续显示出来,而队列被清除的话,则不会再显示了。
    呃,这图片看的人实在是饿了,我要去吃饭了......

    相关文章

      网友评论

          本文标题:IOS Toast提示的学习笔记

          本文链接:https://www.haomeiwen.com/subject/edtskctx.html