iOS 3D Touch 长按添加交互按钮 和 展示小组件样式

前言:本文档主要实现2个功能

  • 1.3D Touch 长按添加快捷键入口。竖的红框内
  • 2.3D Touch 长按展示小组件样式。横的红框内

直接上图看一下效果:

041a5f78f0660a20359c42f979faf854.jpg
一. 我们先来实现上面第一个功能
  • 我们现在info.plist文件添加keyHome Screen Shortcut Items
<key>UIApplicationShortcutItems</key>
    <array>
        <dict>
            <key>UIApplicationShortcutItemIconFile</key>
            <string>fy_goHdNew</string>
            <key>UIApplicationShortcutItemType</key>
            <string>activity</string>
            <key>UIApplicationShortcutItemTitle</key>
            <string>去领券</string>
        </dict>
        <dict>
            <key>UIApplicationShortcutItemIconFile</key>
            <string>fy_dyGoZmNew</string>
            <key>UIApplicationShortcutItemType</key>
            <string>subscribe</string>
            <key>UIApplicationShortcutItemTitle</key>
            <string>去订阅</string>
        </dict>
        <dict>
            <key>UIApplicationShortcutItemIconFile</key>
            <string>fy_xcGoZmNew</string>
            <key>UIApplicationShortcutItemType</key>
            <string>itinerary</string>
            <key>UIApplicationShortcutItemTitle</key>
            <string>看行程</string>
        </dict>
    </array>
我们来解释一下字段含义:

UIApplicationShortcutItemIconFile 用来设置左侧图标展示
UIApplicationShortcutItemTitle 用来设置右侧名称展示
UIApplicationShortcutItemType用来判断跳转类型(页面)
备注:上面是通过pilt文件进行配置,也可以使用纯代码进行编写

接下来我们来写逻辑:

点击按钮时候分为热启动和冷启动,热启动就是app正在运行,回到桌面长按,冷启动就是app没有正在运行。我们直接上代码:

先来实现热启动:

在AppDelegate.h

@property (nonatomic, strong) UIApplicationShortcutItem *smallShortcutItem;

在AppDelegate.m处理点击逻辑

-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
    BOOL ok = [self handleShortcutItem:shortcutItem];
    if (completionHandler) completionHandler(ok);
}
- (void)handlePendingShortcutIfNeeded {
    if (!self.pendingShortcutItem) return;
    UIApplicationShortcutItem *item = self.pendingShortcutItem;
    BOOL isXf = [self handleShortcutItem:item];
    if (isXf == YES) {
        self.pendingShortcutItem = nil;
    }
}
- (BOOL)handleShortcutItem:(UIApplicationShortcutItem *)item {
    //获取根视图 根据你们自己的逻辑进行跳转
    BaseTabBar *tab = (BaseTabBar
                       *)self.window.rootViewController;
    if (![tab isKindOfClass:[BaseTabBar class]]) return NO;
   //activity  subscribe itinerary 就是你在info.plist文件设置的type:UIApplicationShortcutItemType
    if ([item.type isEqualToString:@"activity"]) {
        //跳转到领券
        return YES;
    } else if ([item.type isEqualToString:@"subscribe"]) {
        //跳转到订阅
        return YES;
    } else if ([item.type isEqualToString:@"itinerary"]) {
        //跳转到行程
        return YES;
    }
    else if ([item.type isEqualToString:@"gorent"]) {
         //跳转到首页
        return YES;
    }
    return NO;
}
我们来实现冷启动:

AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIApplicationShortcutItem *item =
    launchOptions[UIApplicationLaunchOptionsShortcutItemKey];
    if (item) {
        self.pendingShortcutItem = item;
        // 冷启动场景:稍后手动处理
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self handlePendingShortcutIfNeeded];
        });
    }
}
二. 我们先来实现上面第一个功能 (接下来我们来实现:3D Touch 长按展示小组件样式。横的红框内
1.需要我们新建一个target,我的命名为:MapleTripSamllWidget

点击xcode导航栏File->New->Target 如下图:

a2d444786166b718a23b223a9ec68249.png

这个时候您的项目文件结构会多出一个文件夹,如下图:
5ea9f89fd86ee46017f1d4521daab6c2.png
我们需要配置一下Scheme URL, 在info.plist设置如下:
<key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>tripWidgetquick</string>
            </array>
            <key>CFBundleURLName</key>
            <string>tripWidgetquick</string>
        </dict>
     <array>
截图如下:
291556d25ad12bb8129e913b01d81d1e.png
在文件MapleTripSamllWidgetBundle.swift代码如下:
import WidgetKit
import SwiftUI

@main
struct MapleTripSamllWidgetBundle: WidgetBundle {
    var body: some Widget {
        MapleTripSamllWidget()
    }
}
在文件MapleTripSamllWidget.swift代码如下:
import WidgetKit
import SwiftUI

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> Entry { Entry(date: Date()) }
    func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) { completion(Entry(date: Date())) }
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        completion(Timeline(entries: [Entry(date: Date())], policy: .after(Date().addingTimeInterval(1800))))
    }
}

struct Entry: TimelineEntry { let date: Date }

enum WidgetRoute: String, CaseIterable {
    case coupon = "coupon"
    case subscribe = "subscribe"
    case trip = "trip"
    case order = "order"

    var url: URL { URL(string: "tripWidgetquick://\(rawValue)")! }
}

private struct QuickItem {
    let title: String
    let icon: String
    let route: WidgetRoute
}

private let quickItems: [QuickItem] = [
    QuickItem(title: "去领券", icon: "trip_lingquan", route: .coupon),
    QuickItem(title: "去租车", icon: "trip_zuche", route: .order),
    QuickItem(title: "去订阅", icon: "trip_dingyue", route: .subscribe),
    QuickItem(title: "看行程", icon: "trip_stroke", route: .trip)
]

private struct TaskCardView: View {
    var body: some View {
        HStack(spacing: 10) {
            leftPanel
            rightGrid
        }
        .padding(10)
        .containerBackground(for: .widget) {
            LinearGradient(
                colors: [Color(red: 0.98, green: 0.86, blue: 0.78), Color(red: 0.96, green: 0.93, blue: 0.87)],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
        }
    }

    private var leftPanel: some View {
        VStack(alignment: .center, spacing: 6) {
            HStack(spacing: 4) {
                Image("trip_title")
                    .font(.system(size: 11, weight: .semibold))
                    .cornerRadius(4)
                    .foregroundColor(.clear)
                Text("可以是你app的名字")
                    .font(.system(size: 12, weight: .semibold))
                    .foregroundColor(Color(red: 0.25, green: 0.2, blue: 0.2))
            }
            .frame(width: 110, height: 12)
            
            ZStack {
                Text("可以写一下描述!")
                    .font(.system(size: 10, weight: .regular))
                    .foregroundColor(Color(.black))
                    .frame(maxWidth: .infinity, alignment: .center)
                    .padding(.vertical, 8)
            }
            .frame(width: 110, height: 10)
            
            ZStack {
                Image("trip_actHd")
                    .resizable()
                    .foregroundStyle(.white, .clear)
                    .scaledToFit()
                    .frame(width: 80, height: 80, alignment: .center)
            }
            .frame(width: 80, height: 80)
            Text("天天好礼送不停~")
                .font(.system(size: 10, weight: .bold))
                .foregroundColor(Color(.red))
                .frame(maxWidth: .infinity, alignment: .center)
        }
        .frame(maxWidth: .infinity, alignment: .center)
    }

    private var rightGrid: some View {
        VStack(spacing: 6) {
            HStack(spacing: 6) {
                quickCell(item: quickItems[0])
                quickCell(item: quickItems[1])
            }
            HStack(spacing: 6) {
                quickCell(item: quickItems[2])
                quickCell(item: quickItems[3])
            }
        }
        .frame(width: 125)
    }

    private func quickCell(item: QuickItem) -> some View {
        Link(destination: item.route.url) {
            VStack(spacing: 4) {
                Image(item.icon)
                    .resizable()
                    .scaledToFit()
                    .font(.system(size: 17, weight: .medium))
                    .foregroundColor(Color(red: 0.2, green: 0.2, blue: 0.2))
                Text(item.title)
                    .font(.system(size: 12, weight: .bold))
                    .foregroundColor(Color(red: 0.2, green: 0.2, blue: 0.2))
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .padding(.vertical, 8)
            .background(
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .fill(Color.white.opacity(0.55))
            )
        }
        .buttonStyle(.plain)
    }
}

@available(iOS 17.0, *)
struct MapleTripSamllWidgetEntryView: View {
    var body: some View {
        TaskCardView()
    }
}

@available(iOS 17.0, *)
struct MapleTripSamllWidget: Widget {
    let kind: String = "tripWidgetquick"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { _ in
            MapleTripSamllWidgetEntryView()
        }
        .configurationDisplayName("快捷入口")
        .description("任务卡片组件")
        .supportedFamilies([.systemMedium])
    }
}

MapleTripSamllWidget.swift这个文件是使用SwiftUI来进行布局展示的,那我来解释一下,这里面的重点代码:
  • 第一个
enum WidgetRoute: String, CaseIterable {
    case coupon = "coupon"
    case subscribe = "subscribe"
    case trip = "trip"
    case order = "order"

    var url: URL { URL(string: "tripWidgetquick://\(rawValue)")! }
}
//tripWidgetquick 这个值配置的要和info.plist文件设置成一样的,后面用来跳转判断使用。
  • 第二个
@available(iOS 17.0, *)
struct MapleTripSamllWidget: Widget {
    let kind: String = "tripWidgetquick"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { _ in
            MapleTripSamllWidgetEntryView()
        }
        .configurationDisplayName("快捷入口")
        .description("任务卡片组件")
        .supportedFamilies([.systemMedium])
    }
}
我们要知道,他有3种布局分别为systemSmall 小systemMedium 中以及systemLarge 大,默认是支持3种布局,这样就需要对3种布局写3个样式,我这里面配置的是systemMedium,其他2中演示展示不出来,关键代码:
 .supportedFamilies([.systemMedium])
上面配置完事之后,看截图:
IMG_1574 2.jpg
上面这张截图由于我在只配置了.supportedFamilies([.systemMedium])所以其他2中样式为灰色的且不可点击的。
下面这张截图是上面的截图点击红色框之后展示的样式:
IMG_1575 2.jpg
那么接下来还是处理点击去领券 去租车 去订阅 看行程事件逻辑处理。(也分为热启动冷启动
  • 热启动处理:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
    NSString *string =[url absoluteString];
    if ([string hasPrefix:@"tripWidgetquick://"]) {
        return [self handleDeepLink:url];
    }
}
- (BOOL)handleDeepLink:(NSURL *)url {
    if (![[url.scheme lowercaseString] isEqualToString:@"tripwidgetquick"]) return NO;

    NSString *route = url.host.lowercaseString ?: @"";
    
    if ([route isEqualToString:@"coupon"]) {
        //去领券
        NSLog(@"去领券");
        UIApplicationShortcutItem *itemShortcut = [[UIApplicationShortcutItem alloc] initWithType:@"activity" localizedTitle:@""];
        [self handleShortcutItem:itemShortcut];
    } else if ([route isEqualToString:@"subscribe"]) {
        //去订阅
        NSLog(@"去订阅");
        UIApplicationShortcutItem *itemShortcut = [[UIApplicationShortcutItem alloc] initWithType:@"subscribe" localizedTitle:@""];
        [self handleShortcutItem:itemShortcut];
    } else if ([route isEqualToString:@"order"]) {
        //去租车
        NSLog(@"去租车");
        UIApplicationShortcutItem *itemShortcut = [[UIApplicationShortcutItem alloc] initWithType:@"gorent" localizedTitle:@""];
        [self handleShortcutItem:itemShortcut];
    } else if ([route isEqualToString:@"trip"]) {
        //看行程
        NSLog(@"看行程");
        UIApplicationShortcutItem *itemShortcut = [[UIApplicationShortcutItem alloc] initWithType:@"itinerary" localizedTitle:@""];
        [self handleShortcutItem:itemShortcut];
    }
    return YES;
}
//handleShortcutItem 这个方法上面有。
  • 冷启动处理:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSURL *launchURL = launchOptions[UIApplicationLaunchOptionsURLKey];
    if (launchURL) {
        // 冷启动场景:稍后手动处理
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self handleDeepLink:launchURL];
        });
    }
    return YES;
}

大功告成~

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容