iOS10 iMessage 带来了你需要的MessageExtension

提示

前段时候因为一些原因,文章被删除了。现在再发一下,demo被删除了.......,有需要就我再码一个补上.....

项目要求

Xcode8 + iOS10
现在都是测试版,去开发者网站可以下载。如果没有的话,这个,那个,就看下我的配图吧,哈哈

前言

iOS10测试版已经出来一个多月了,一直对这个东西很感兴趣。但是查了好多,基本都是用Sticker的那个方面直接放图片使用的。到现在我是没有搜索到用代码编写的iMessage的项目,所以用了一天把这个项目写出来(前几天看过几次概念)。不理解的就去官方api文档看。现在是把苹果说的功能大概写了出来。有不对之处,请大家指出。

正文

在这里引用一下别人写的iMessage的解释,感觉解释的很好,我也直接引用了:

在iOS中新增了两种iMessage的方式,1.内置表情包,2.iMessage应用

1.内置表情包(Sticker Packs)

可以通过在Xcode中新建Sticker Pack Application来创建。这种方式可以简单地通过添加图片来在iMessage中添加表情包。添加的贴纸需要满足一下条件

图片类型必须是 png、apng、gif或者jpeg
文件大小必须 小于500K
图片大小必须在 100100 到 206206 之间
需要注意的是:必须要永远提供 @3x 大小的图片(即 300300 到 618618)。系统可以根据当前设备通过 runtime 自动调整图片来呈现 @2x 和 @1x
系统能够自适应的展示贴纸,所以为了更好的展示贴纸,最好提供的贴纸是以下三种大小的类型

小型 100*100
中型 136*136
大型 206*206

2.iMessage应用

iMessage app使用完整的框架和Message app进行交互。使用iMessage app能够

在消息应用内呈现一个自定义的用户交互界面。 使用 MSMessagesAppViewController
创建一个自定义或者动态的表情包浏览器。使用 MSStickerBrowserViewController
添加文本、表情、或者媒体文件到消息应用的文本输入框。使用 MSConversation
创建带有特定的应用数据交互的消息。使用 MSMessage
更新可以相互影响的消息(例如,创建游戏或者可以合作的应用)。使用 MSSession

内置表情包我就不在说了,我看了看网上有很好的例子也很简单。
MSMessagesAppViewController说白了就是显示表情的vc,和普通的vc一样,只是他多了一些方法,和AppDelegate的差不多。
我用了一个collectionview去显示图片,但是有一点需要注意,如果你直接用UIImageView去直接显示图片也是可以的,运行在message也是显示的,但是你却无法进行正常的拖拽,点击操作,因为你只是给了一个UIImageView而已。在这里要用到一个专门为显示iMessage里面图片用的类MSStickerView,他是UIView的一个子类,里面的属性很少

@interface MSStickerView : UIView

/*!
 @method   initWithFrame:sticker:
 @abstract   Initializes a MSStickerView with a frame and a MSSticker conforming object to display.
 */
- (instancetype)initWithFrame:(CGRect)frame sticker:(nullable MSSticker *)sticker;

/*!
 @property   sticker
 @abstract   The MSSticker object to display.
 @discussion Set this property to nil to remove the current sticker. Setting the
 sticker property does not change the size of a MSStickerView. Call sizeToFit to
 adjust the size of the view to match the sticker.
 */
@property (nonatomic, strong, readwrite, nullable) MSSticker *sticker;

/*!
 @property   animationDuration
 @abstract   The amount of time it takes to go through one cycle of the sticker animation.
 */
@property(nonatomic, readonly) NSTimeInterval animationDuration;

/*!
 @method    startAnimating
 @abstract  Starts animating the sticker in the receiver.
 @discussion This method always starts the animation from the first frame.
 */
-(void) startAnimating;

/*!
 @method    stopAnimating
 @abstract  Stops animating the sticker in the receiver.
 */
-(void) stopAnimating;

/*!
 @method   isAnimating
 @abstract   Returns a Boolean value indicating whether the animation is running.
 */
- (BOOL)isAnimating;

一目了然,sticker我的理解就是特殊的数据,让MSStickerView根据这个数据进行相应的显示操作。至于stopAnimating那几个方法我暂时没有用到。
UICollectionView代码块:

#import "StickerCollectionView.h"
#import "StickerCell.h"
#import "CommonCell.h"
static NSString * const STICKERCELL = @"StickerCell";
static NSString * const COMMONCELL = @"CommonCell";
@interface StickerCollectionView()

@property(nonatomic,strong)NSMutableArray *stickerArray;//数据
@end

@implementation StickerCollectionView
- (instancetype)initWithCoder:(NSCoder *)aDecoder{

   self = [super initWithCoder:aDecoder];
   if (self) {
       [self registerNib:[UINib nibWithNibName:STICKERCELL bundle:nil] forCellWithReuseIdentifier:STICKERCELL];//注册单元格
       [self registerNib:[UINib nibWithNibName:COMMONCELL bundle:nil] forCellWithReuseIdentifier:COMMONCELL];
   }
   return self;
}

- (void)awakeFromNib{

   [super awakeFromNib];
   self.delegate = self;
   self.dataSource = self;
   
}
//数据加载
- (NSMutableArray *)stickerArray{

   if (!_stickerArray) {
       _stickerArray = [NSMutableArray array];
       NSArray *dataArray = @[@"1",@"2",@"3",@"4",@"5",@"6",@"7"];
       
       //把图片转换为mssticker类的形式,用于显示
       [dataArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
           NSURL *urlStr = [[NSBundle mainBundle]URLForResource:obj withExtension:@"jpg"];
           MSSticker *placeSticker = [[MSSticker alloc]initWithContentsOfFileURL:urlStr localizedDescription:obj error:nil];
           [_stickerArray addObject:placeSticker];
       }];
   }
   return _stickerArray;
}


#pragma mark ------九宫格代理

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{

   return self.stickerArray.count + 4;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{

   if (indexPath.row >= self.stickerArray.count) {
       CommonCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:COMMONCELL forIndexPath:indexPath];
       cell.imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"other_%lu",indexPath.row - self.stickerArray.count + 1]];
       return cell;
   }else{
   
       StickerCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:STICKERCELL forIndexPath:indexPath];
       cell.sticker.sticker = self.stickerArray[indexPath.row];//把mssticker类型的数据赋予msstickerview里面的sticker属性
       return cell;
   }
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{

   if (indexPath.row == self.stickerArray.count) {
       //文字
       if (self.event1Block) {
           self.event1Block();
       }
   }else if (indexPath.row == self.stickerArray.count + 1){
   
       if (self.event2Block) {
           self.event2Block();
       }
       //音频
   }else if (indexPath.row == self.stickerArray.count + 2){
   
       if (self.event3Block) {
           self.event3Block();
       }
       //详细图
   }else if (indexPath.row == self.stickerArray.count + 3){
   
       if (self.event4Block) {
           self.event4Block();
       }
       //多图
   }
   
}

在这里面你会发现数据数组不再是简单的图片名字数据了,是一个MSSticker类型的数据。

- (nullable instancetype)initWithContentsOfFileURL:(NSURL *)fileURL localizedDescription:(NSString *)localizedDescription error:(NSError * _Nullable *)error NS_DESIGNATED_INITIALIZER;

/*!
 @property   imageFileURL
 @abstract   The file URL to the Sticker was initialized with.
 */
@property (nonatomic, strong, readonly) NSURL *imageFileURL;

/*!
 @property   localizedDescription
 @abstract   A succinct localized string describing the sticker.
 */
@property (nonatomic, copy, readonly) NSString *localizedDescription;

这个类里面的东西更加的少了,一个创建方法,一个NSURL,一个NSString。而且后面两个都是只读类型的,所以你基本不用操作。直接使用它唯一的方法就行了,localizedDescription要尽量设置的不同(官方好像说的是一个iMessage App里面的每一个MSSticker 的localizedDescription定义好了就不变了,除非卸载这个App.我解决的这个不是很好,英语太差)
把我们的基本数据转换成这个类之后然后传给MSStickerView类进行擦操作就行。

 cell.sticker.sticker = self.stickerArray[indexPath.row];//把mssticker类型的数据赋予msstickerview里面的sticker属性

至于他是怎么显示出来的,这个不用你担心。
显示效果如下:

效果一

然后点击发送:

效果二
效果三

这是基本的显示效果,iMessage还可以显示规定的文字,音频,详细的图片...
这些效果的实现是在MSMessagesAppViewController里面,MSMessagesAppViewController有个属性activeConversation,属于MSConversation类

/*!
 @method     insertMessage:localizedChangeDescription:completionHandler:
 @abstract   Stages provided the MSMessage for sending.
 @discussion This method inserts a MSMessage object into the Messages input field,
 Subsequent calls to this method will replace any existing message on the input field. 
 If the message was successfully inserted on the input field, the completion handler
 will be called with a nil error parameter otherwise the error parameter will be
 populated with an NSError object describing the failure.
 @param      message            The MSMessage instance describing the message to be sent.
 @param      completionHandler  A completion handler called when the message has been staged or if there was an error.
 */
- (void)insertMessage:(MSMessage *)message completionHandler:(nullable void (^)(NSError * _Nullable))completionHandler;

/*!
 @method     insertSticker:completionHandler:
 @abstract   The sticker is inserted into the Messages.app input field.
 @param      sticker            The sticker to be inserted.
 @param      completionHandler  A completion handler called when the insert is complete.
 */
- (void)insertSticker:(MSSticker *)sticker completionHandler:(nullable void (^)(NSError * _Nullable))completionHandler;

/*!
 @method     insertText:completionHandler:
 @abstract   The NSString instance provided in the text parameter is inserted into the Messages.app input field.
 @param      text               The text to be inserted.
 @param      completionHandler  A completion handler called when the insert is complete.
 */
- (void)insertText:(NSString *)text completionHandler:(nullable void (^)(NSError * _Nullable))completionHandler;

/*!
 @method     insertAttachment:withAlternateFilename:completionHandler:
 @abstract   The NSURL instance provided in the URL parameter is inserted into the Messages.app
 input field. This must be a file URL.
 @param      URL                The URL to the media file to be inserted.
 @param      filename           If you supply a string here, the message UI uses it for the attachment. Use an alternate filename to better describe the attachment or to make the name more readable.
 @param      completionHandler  A completion handler called when the insert is complete.
 */
- (void)insertAttachment:(NSURL *)URL withAlternateFilename:(nullable NSString *)filename completionHandler:(nullable void (^)(NSError * _Nullable))completionHandler;

这是MSConversation类的四个方法,也是他的全部方法,用于插入各种对用的内容。

文字

self.collectionView.event1Block = ^{
    
        //发送文字
        [weakSelf.activeConversation insertText:@"Fuck the world if you are rich,otherwise fuck youself!" completionHandler:^(NSError * _Nullable error) {
            //使用的时候的回调,做自己想做的事情
        }];

效果:

文字

音频

 //发送音频
        NSString *path = [[NSBundle mainBundle]pathForResource:@"music" ofType:@"mp3"];
        NSURL *url = [NSURL fileURLWithPath:path];
        [self.activeConversation insertAttachment:url withAlternateFilename:@"音乐" completionHandler:^(NSError * _Nullable error) {
            //使用的时候的回调,做自己想做的事情
        }];

效果:

音频
音频

详细的MSMessage


        //详细图片
        MSMessage *message = [[MSMessage alloc]init];
        MSMessageTemplateLayout *layout = [[MSMessageTemplateLayout alloc]init];
        layout.imageTitle = @"蜡笔";
        layout.imageSubtitle = @"蜡笔小新的故事";
        layout.caption = @"蜡笔";
        layout.subcaption = @"蜡笔sub";
        layout.trailingCaption = @"蜡笔";
        layout.trailingSubcaption = @"蜡笔sub";
        NSString *path2 = [[NSBundle mainBundle]pathForResource:@"1" ofType:@"jpg"];
        layout.mediaFileURL = [NSURL fileURLWithPath:path2];
        message.layout = layout;
        [self.activeConversation insertMessage:message completionHandler:^(NSError * error) {
            //使用的时候的回调,做自己想做的事情
        }];

效果:

详细图片

这是我用UICollectionView实现的布局,其实iMessage也给了一个类似的可以实现的view和vc:MSStickerBrowserView,MSStickerBrowserViewController

@interface MSStickerBrowserViewController : UIViewController <MSStickerBrowserViewDataSource>

/*!
 @method   initWithStickerSize:
 @abstract Initializes a MSStickerBrowserViewController and configures it's MSStickerBrowserView with the provided sticker size class.
 */
- (instancetype)initWithStickerSize:(MSStickerSize)stickerSize NS_DESIGNATED_INITIALIZER;

/*!
 @property   stickerBrowserView
 @abstract   Returns the sticker browser view managed by the controller object.
 */
@property (nonatomic, strong, readonly) MSStickerBrowserView *stickerBrowserView;

/*!
 * @abstract Controls the size of the stickers are displayed at in the sticker browser view.
 */
@property (nonatomic, readonly) MSStickerSize stickerSize;

MSStickerBrowserViewController里面就这一点东西,一个构造,两个只读属性。
要创建MSStickerBrowserViewController一定要实现代理的两个方法,与表很像的两个代理:

/*!
 * @abstract Returns the number of Stickers that the sticker browser should show.
 * @param stickerBrowserView The sticker browser view .
 * @result The number of stickers.
 */
- (NSInteger)numberOfStickersInStickerBrowserView:(MSStickerBrowserView *)stickerBrowserView;

/*!
 * @abstract Returns the sticker that the sticker browser should show in the browser.
 * @param stickerBrowserView The sticker browser view.
 * @param index The index of the sticker to show.
 * @result A MSSticker object.
 */
- (MSSticker *)stickerBrowserView:(MSStickerBrowserView *)stickerBrowserView stickerAtIndex:(NSInteger)index;

就是个数和显示的内容,我实现的是两行显示,我不知道可以可以改变这个:

//数据加载
- (NSMutableArray *)browArray{
    
    if (!_browArray) {
        _browArray = [NSMutableArray array];
        NSArray *dataArray = @[@"dt_1",@"dt_2",@"dt_3",@"dt_4",@"dt_5",@"dt_6",@"dt_7",@"dt_8",@"dt_9",@"dt_10",@"dt_11",@"dt_12",@"dt_13"];
        
        //把图片转换为mssticker类的形式,用于显示
        [dataArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSURL *urlStr = [[NSBundle mainBundle]URLForResource:obj withExtension:@"gif"];
            MSSticker *placeSticker = [[MSSticker alloc]initWithContentsOfFileURL:urlStr localizedDescription:obj error:nil];
            [_browArray addObject:placeSticker];
        }];
    }
    return _browArray;
}

#pragma mark----代理

- (NSInteger)numberOfStickersInStickerBrowserView:(MSStickerBrowserView *)stickerBrowserView{
    
    return self.browArray.count;
}
- (MSSticker *)stickerBrowserView:(MSStickerBrowserView *)stickerBrowserView stickerAtIndex:(NSInteger)index{
    
    return self.browArray[index];
}

效果:

效果

结语

基本说完了,我没有详细的介绍每个类用法,里面什么属性方法,这个我感觉放到demo你一跑起来看下代码就应该明白。其实iMessage很简单,我写的demo只是简单的把它基本使用了,还有好多等待你的开发。如果你感觉对你有一点帮助,请给个github星星,谢了。

推荐阅读更多精彩内容

  • //我所经历的大数据平台发展史(三):互联网时代 • 上篇http://www.infoq.com/cn/arti...
    葡萄喃喃呓语阅读 48,226评论 4 197
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 161,625评论 24 692
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 7,970评论 4 42
  • 人为什么每天都要吃饭呢?真是麻烦死了,周润发有一次在他的专栏节目上也曾说:‘’我最大的愿望就是以后可以不用吃饭‘’...
    洛神婺女阅读 257评论 7 4
  • 看了部电影《完美陌生人》,每个人都有别人不为人知的秘密。其实不懂的,我也不怎么喜欢看电影。只是觉得,每个人多多少少...
    阳光与沙滩上阅读 94评论 0 0
  • 最近追了《欢乐颂》,电视追完第一季,找来电子版追完第二三季。不喜欢被吊的感觉,早完早了事。 1 安迪 这个人物设定...
    郑红阅读 254评论 4 1
  • “真正的陪伴是在暖暖的冬日午后醒来,看你微醺着躺在地板上,阳光一厘一厘打亮你如芒果般的轮廓。” 前一...
    Viola__阅读 360评论 0 4