欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Today Extension

程序员文章站 2022-05-30 17:37:47
...

Today Extension(也叫Widget)
Today Extension

Today Extension创建步骤

开始之前先要创建一个iOS项目,因为Extension不能脱离containing app而存在。本项目实例名为,TodayExtensionDemo,项目创建完后
具体步骤如下:

File -> New -> Target 选择Today Extension,点击继续。
Today Extension
Today Extension

添加后,设置Active Scheme为刚创建的Extension,如下图
Today Extension
点击运行,点击运行后会出现一个选择框,选择Today就可以了
Today Extension

然后下拉通知栏,点击今天,点击编辑,就看到可以添加刚刚自己创建的扩展了,扩展显示的名字可以在刚刚的info.plist中修改。
Today Extension
调试扩展的方式有两种:
1、选择这个扩展的scheme直接运行
2、先运行容器App,然后下拉通知栏打开今日扩展,点击Xcode菜单的Debug->Attach To Process ,选择Likly Targets中需要调试的扩展

代码编写

接下来的工作就是自定义这个展示页面了,如果你习惯使用Storyboard直接在MainInterface.storyboard上修改即可,如果你习惯自己Coding,你需要先修改一下info.plist
修改方法:

// 删除
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>

// 添加:
<key>NSExtensionPrincipalClass</key>
<string>TodayViewController</string>

//修改CFBundleDisplayName,调整Widget显示的名称
<key>CFBundleDisplayName</key>
<string>显示的名称</string>

1.调整Widget的高度

-(void)awakeFromNib {
    [super awakeFromNib];
    [self setPreferredContentSize:CGSizeMake(0, 240)];
}

取消widget默认的inset,让应用靠左

- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
    return UIEdgeInsetsZero;
}

2.如果你要访问http站点链接,iOS9之后因为苹果App Transport Security (ATS)新特性,无法直接访问http数据,你也需要在Extension的plist中添加如下代码:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

3.因为Extension和containing app无法进行数据和文件共享,所以你还需要在Extenison中再添加一遍需要的文件。

4.在widget想要点击页面打开containing app。需要采用Open URL的方式打开containing app。

首先,在containing app的info.plist添加如下代码:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <!--这个一定要唯一-->
        <string>com.wildcat.TodayExtensionDemo</string>  
        <key>CFBundleURLSchemes</key>
        <array>
            <!--调转URL的host,例如:TodayDemo:// --->
            <string>TodayDemo</string>                    
        </array>
    </dict>
</array>

在today extension中实现:

-(void)openURLContainingAPP
{
    [self.extensionContext openURL:[NSURL URLWithString:@"lecoding://action=GotoHomePage"]
                 completionHandler:^(BOOL success) {
                     NSLog(@"open url result:%d",success);
                 }];

}

在 containing app appdelegate中添加如代码,接收跳转:

-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options{
    NSString* prefix = @"TodayDemo://";
    if ([[url absoluteString] rangeOfString:prefix].location != NSNotFound) {
        NSString* action = [[url absoluteString] substringFromIndex:prefix.length];
        if ([action isEqualToString:@"GotoHomePage"]) {

        }

        else if([action isEqualToString:@"GotoOtherPage"]) {

         }
    }
    return YES;
}

如何使用containing app中的图片
虽然Extention无法和containing app 公用库文件,但是可以公用图片,方法就是:

左侧选中containing app的Assets.xcassets,在右侧File Inspector中的Target Membership勾选Extension项目名。如下图:

Today Extension

在应用和扩展间共享数据 - App Groups

对 iOS 开发者来说,沙盒限制了我们在设备上随意读取和写入。但是对于应用和其对应的扩展来说,Apple 在 iOS 8 +中为我们提供了一种可能性,那就是 App Groups。App Groups 为同一个开发商的应用或者扩展定义了一组域,在这个域中同一个 group 可以共享一些资源。

首先我们需要开启 App Groups。选择主 Target,打开它的 Capabilities 选项卡,找到 App Groups 并打开开关,然后添加一个你能记得的 group 名字,比如group.myWidget。接下来你还需要为xxxWidget这个 Target 进行同样的配置,只不过不再需要新建 group,而是勾选刚才创建的 group 就行。
Today Extension
Today Extension


//读写共享的数据
- (NSUserDefaults *)loadGroupData
{
    NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.myWidget"];// SuiteName必须和上面Capabilities配置填写的一致
    return userDefault;
}

- (void)setVolume:(uint32_t)volume
{
    [[self loadGroupData] setObject:@(volume) forKey:@"volume"];
    [[self loadGroupData] synchronize];
}

- (uint32_t)getVolume:(uint32_t)volume
{
    NSNumber *volumeNumber = [[self loadGroupData] objectForKey:@"volume"];
    if (volumeNumber && [volumeNumber isKindOfClass:[NSNumber class]]) {
        return volumeNumber.unsignedIntValue;
    } else {
        return kDefaultVolume;
    }
}

widget向containing app 发送数据

//1、容器App设置定时器去轮询共享数据,一旦发生变化,就相应地改变音量。
//2、扩展发送请求到服务器,服务器再通知容器App。

//3.它就是 CFNotificationCenterGetDarwinNotifyCenter!这是CoreFoundation库中一个系统级的通知中心,苹果的系统自己也在用它,看清了“Darwin””了没有?哈哈!看了下CFNotificationCenter相关的API,跟NSNotificationCenter有点像。需要用到Toll-Bridge的知识与CoreFoundation相关的类进行桥接,这虽不常用但也不难。还需要注意下个别参数的使用。
// 接受不到数据,扩展发送通知前先存储数据,容器App接收到通知后,再读取共享数据那就好了
Containing App:

// 添加监听
- (void)addObserver
{
    CFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter ();
    CFNotificationCenterAddObserver(notification, (__bridge const void *)(self), observerMethod, CFSTR("通知名"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
}

// 监听widget改变
void observerMethod (CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
    //监听到notification后要做的处理
}

// 移除监听
- (void)removeObserver
{
    CFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter ();
    CFNotificationCenterRemoveObserver(notification, (__bridge const void *)(self), CFSTR("通知名"), NULL);
}

Widget:

// 发送通知
- (void)postNotificaiton
{
    CFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter ();
    CFNotificationCenterPostNotification(notification, CFSTR("通知名"), NULL, NULL, YES);
}

BTW,如果想实现更复杂的功能,推荐使用MMWormhole这个开源库,它专门用于在Container app 与 Extension间传递消息,苹果婊 Watch OS 也适用~
最后分享一个Podfile多个target引用部分相同pod库的编写方法:

def host_pods
pod 'SSKeychain', '~> 0.1.4'
pod 'INAppStoreWindow', :head
pod 'AFNetworking', '1.1.0'
end

def shared_pods
pod 'MMWormhole','~> 2.0.0'
end

target 'HostApp' do
shared_pods
host_pods
end

target 'Extension' do
shared_pods
end

代码期待中:https://github.com/JolieYa/BYTodayExtension