iOS 3D Touch 長按添加交互按鈕 和 展示小組件樣式

前言:本文檔主要實現(xiàn)2個功能

  • 1.3D Touch 長按添加快捷鍵入口。豎的紅框內(nèi)
  • 2.3D Touch 長按展示小組件樣式。橫的紅框內(nèi)

直接上圖看一下效果:

041a5f78f0660a20359c42f979faf854.jpg
一. 我們先來實現(xiàn)上面第一個功能
  • 我們現(xiàn)在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>去領(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;
}

大功告成~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容