前言:本文檔主要實現(xiàn)2個功能
- 1.3D Touch 長按添加快捷鍵入口。
豎的紅框內(nèi) - 2.3D Touch 長按展示小組件樣式。
橫的紅框內(nèi)
直接上圖看一下效果:

041a5f78f0660a20359c42f979faf854.jpg
一. 我們先來實現(xiàn)上面第一個功能
- 我們現(xiàn)在info.plist文件添加key
Home 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>去領(lǐng)券</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 用來設置左側(cè)圖標展示
UIApplicationShortcutItemTitle 用來設置右側(cè)名稱展示
UIApplicationShortcutItemType用來判斷跳轉(zhuǎn)類型(頁面)
備注:上面是通過pilt文件進行配置,也可以使用純代碼進行編寫
接下來我們來寫邏輯:
點擊按鈕時候分為熱啟動和冷啟動,熱啟動就是app正在運行,回到桌面長按,冷啟動就是app沒有正在運行。我們直接上代碼:
先來實現(xiàn)熱啟動:
在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 {
//獲取根視圖 根據(jù)你們自己的邏輯進行跳轉(zhuǎn)
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"]) {
//跳轉(zhuǎn)到領(lǐng)券
return YES;
} else if ([item.type isEqualToString:@"subscribe"]) {
//跳轉(zhuǎn)到訂閱
return YES;
} else if ([item.type isEqualToString:@"itinerary"]) {
//跳轉(zhuǎn)到行程
return YES;
}
else if ([item.type isEqualToString:@"gorent"]) {
//跳轉(zhuǎn)到首頁
return YES;
}
return NO;
}
我們來實現(xiàn)冷啟動:
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];
});
}
}
二. 我們先來實現(xiàn)上面第一個功能 (接下來我們來實現(xiàn):3D Touch 長按展示小組件樣式。橫的紅框內(nèi))
1.需要我們新建一個target,我的命名為:MapleTripSamllWidget
點擊xcode導航欄File->New->Target 如下圖:

a2d444786166b718a23b223a9ec68249.png
這個時候您的項目文件結(jié)構(gòu)會多出一個文件夾,如下圖:

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: "去領(lǐng)券", 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文件設置成一樣的,后面用來跳轉(zhuǎn)判斷使用。
- 第二個
@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中演示展示不出來,關(guān)鍵代碼:
.supportedFamilies([.systemMedium])
上面配置完事之后,看截圖:

IMG_1574 2.jpg
上面這張截圖由于我在只配置了.supportedFamilies([.systemMedium])所以其他2中樣式為灰色的且不可點擊的。
下面這張截圖是上面的截圖點擊紅色框之后展示的樣式:

IMG_1575 2.jpg
那么接下來還是處理點擊去領(lǐng)券 去租車 去訂閱 看行程事件邏輯處理。(也分為熱啟動和冷啟動)
- 熱啟動處理:
- (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"]) {
//去領(lǐng)券
NSLog(@"去領(lǐng)券");
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;
}
大功告成~