SwiftUI-App extensions

將應(yīng)用程序的基本功能擴(kuò)展到系統(tǒng)的其他部分铝噩,例如添加小部件。

使用SwiftUIWidgetKit將小部件添加到應(yīng)用程序中窿克。小部件可快速訪問應(yīng)用程序的相關(guān)內(nèi)容骏庸。定義符合Widget協(xié)議的結(jié)構(gòu),并聲明小部件的視圖層次結(jié)構(gòu)年叮。像配置其他SwiftUI視圖一樣配置小部件內(nèi)的視圖具被,使用視圖修飾符,包括一些特定于小部件的修飾符只损。

創(chuàng)建小部件

使用WidgetKit和SwiftUI構(gòu)建小部件

protocol Widget
要顯示在主屏幕或通知中心的小部件的配置和內(nèi)容一姿。
protocol WidgetBundle
用于從單個小部件擴(kuò)展中公開多個小部件的容器。
struct LimitedAvailabilityConfiguration
類型擦除的小部件配置跃惫。

給小部件貼標(biāo)簽

func widgetLabel<S>(S) -> some View
返回一個文本標(biāo)簽叮叹,該標(biāo)簽在配件系列小部件的主SwiftUI視圖之外顯示其他內(nèi)容。
func widgetLabel(LocalizedStringKey) -> some View
返回一個本地化的文本標(biāo)簽爆存,該標(biāo)簽在配件系列小部件的主SwiftUI視圖之外顯示其他內(nèi)容蛉顽。
func widgetLabel<Label>(label: () -> Label) -> some View
創(chuàng)建一個標(biāo)簽,用于在配件系列小部件的主SwiftUI視圖之外顯示其他內(nèi)容先较。

控制重音組

func widgetAccentable(Bool) -> some View
將視圖及其所有子視圖添加到重音組中携冤。

管理動態(tài)島的安置

func dynamicIsland(verticalPlacement: DynamicIslandExpandedRegionVerticalPlacement) -> some View
指定動態(tài)島中顯示的擴(kuò)展實(shí)時活動視圖的垂直位置悼粮。

創(chuàng)建小部件擴(kuò)展

添加并配置一個擴(kuò)展程序,以在主屏幕曾棕、今天視圖或通知中心顯示應(yīng)用程序的內(nèi)容扣猫。

小部件顯示相關(guān)、一目了然的內(nèi)容睁蕾,讓用戶快速訪問您的應(yīng)用程序以獲取更多詳細(xì)信息苞笨。您的應(yīng)用程序可以提供各種小部件,讓用戶專注于對他們來說最重要的信息子眶。他們可以添加同一小部件的多個副本瀑凝,并根據(jù)他們獨(dú)特的需求和布局定制每個副本。如果您在小部件中包含自定義意圖臭杰,用戶還可以單獨(dú)個性化每個小部件粤咪。小部件支持多種尺寸;選擇最適合您應(yīng)用程序內(nèi)容的尺寸渴杆。由于空間有限寥枝,請確保您的小部件呈現(xiàn)人們最看重的信息。

為您的應(yīng)用程序添加小部件目標(biāo)
小部件擴(kuò)展模板為創(chuàng)建小部件提供了一個起點(diǎn)磁奖。單個小部件擴(kuò)展可以包含多個小部件囊拜。例如,體育應(yīng)用程序可能有一個顯示團(tuán)隊信息的小部件比搭,另一個顯示比賽時間表的小部件冠跷。單個小部件擴(kuò)展可以包含兩個小部件。

  1. Xcode中打開您的應(yīng)用程序項目身诺,然后選擇文件>新建>目標(biāo)蜜托。
  2. 從應(yīng)用程序擴(kuò)展組中,選擇小部件擴(kuò)展霉赡,然后單擊下一步橄务。
  3. 輸入您的分機(jī)號名稱。
  4. 如果小部件提供用戶可配置的屬性穴亏,請選中“包括配置意圖”復(fù)選框蜂挪。
  5. 點(diǎn)擊完成。
image.png

通常嗓化,您將所有小部件包含在單個小部件擴(kuò)展中锅劝,盡管您的應(yīng)用程序可以包含多個擴(kuò)展。例如蟆湖,如果您的一些小部件使用位置信息,而另一些則不使用玻粪,則將使用位置信息的小部件保留在單獨(dú)的擴(kuò)展中隅津。這允許系統(tǒng)提示用戶授權(quán)僅將位置信息用于使用位置信息的擴(kuò)展中的小部件诬垂。

添加配置詳細(xì)信息

小部件擴(kuò)展模板提供了符合Widget協(xié)議的初始小部件實(shí)現(xiàn)。此小部件的body屬性決定了小部件是否具有用戶可配置的屬性伦仍。有兩種配置可供選擇:

  1. StaticConfiguration
    對于沒有用戶可配置屬性的小部件结窘。例如,顯示一般市場信息的股市小部件充蓝,或顯示趨勢頭條新聞的新聞小部件隧枫。
  2. IntentConfiguration
    對于具有用戶可配置屬性的小部件量九。使用SiriKit自定義意圖來定義屬性系吭。例如绳姨,需要城市郵政編碼的天氣小部件矮瘟,或需要跟蹤號碼的包裹跟蹤小部件力细。

包括配置意圖復(fù)選框決定了Xcode使用的配置俺泣。當(dāng)您選中此復(fù)選框時茧泪,Xcode將使用意圖配置炼列;否則仑撞,它將使用靜態(tài)配置赤兴。要初始化Intent,請向其初始化器提供以下信息:

kind
標(biāo)識小部件的字符串隧哮。這是您選擇的標(biāo)識符桶良,應(yīng)該描述小部件代表什么。
provider
一個符合Timeline并生成時間線的對象沮翔,告訴WidgetKit何時渲染小部件陨帆。時間線包含您定義的自定義Timeline類型。時間線條目標(biāo)識您希望WidgetKit更新小部件內(nèi)容的日期鉴竭。包括小部件視圖需要在自定義類型中渲染的屬性歧譬。
intent
定義用戶可配置屬性的自定義意圖。有關(guān)添加自定義的更多信息搏存,請參閱制作可配置小部件瑰步。
content
包含SwiftUI視圖的閉包。WidgetKit調(diào)用此來呈現(xiàn)小部件的內(nèi)容璧眠,從提供程序傳遞Timeline參數(shù)缩焦。

使用修飾符提供額外的配置詳細(xì)信息,包括顯示名稱责静、描述和小部件支持的系列袁滥。以下代碼顯示了一個小部件,該小部件為游戲提供了通用的灾螃、不可配置的狀態(tài):

@main
struct GameStatusWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.mygame.game-status",
            provider: GameStatusProvider(),
        ) { entry in
            GameStatusView(entry.gameStatus)
        }
        .configurationDisplayName("Game Status")
        .description("Shows an overview of your game status")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
    }
}

小部件的提供商為小部件生成時間線题翻,并在每個條目中包含游戲狀態(tài)詳細(xì)信息。當(dāng)每個時間線條目的日期到達(dá)時腰鬼,WidgetKit會調(diào)用content閉包來顯示小部件的內(nèi)容嵌赠。最后塑荒,修飾符指定小部件庫中顯示的名稱和描述,并允許用戶選擇小部件的小型姜挺、中型或大型版本齿税。

重要
要使應(yīng)用程序的小部件出現(xiàn)在小部件庫中,用戶必須在安裝應(yīng)用程序后至少啟動一次包含小部件的應(yīng)用程序炊豪。

注意此小部件上@main屬性的用法凌箕。此屬性表示Game是小部件擴(kuò)展的入口點(diǎn),這意味著該擴(kuò)展包含單個小部件词渤。要支持多個小部件牵舱,請參閱下面的應(yīng)用程序擴(kuò)展中聲明多個小部件部分。

提供時間線條目

時間線提供程序生成一個由時間線條目組成的時間線掖肋,每個條目指定更新小部件內(nèi)容的日期和時間仆葡。游戲狀態(tài)小部件可能會定義其時間線條目,以包含表示游戲狀態(tài)的字符串志笼,如下所示:

struct GameStatusEntry: TimelineEntry {
    var date: Date
    var gameStatus: String
}

要在小部件庫中顯示您的小部件沿盅,WidgetKit要求提供商提供預(yù)覽快照。通過檢查傳遞給getSnapshot(in:completion:)方法的context參數(shù)的is屬性來識別此預(yù)覽請求纫溃。當(dāng)is為真時腰涧,WidgetKit會在小部件庫中顯示您的小部件。作為回應(yīng)紊浩,您需要快速創(chuàng)建預(yù)覽快照窖铡。如果您的小部件需要需要時間從服務(wù)器生成或獲取的資產(chǎn)或信息,請改用示例數(shù)據(jù)坊谁。

在以下代碼中费彼,如果尚未從服務(wù)器獲取狀態(tài),游戲狀態(tài)小部件的提供程序通過顯示空狀態(tài)來實(shí)現(xiàn)快照方法:

struct GameStatusProvider: TimelineProvider {
    var hasFetchedGameStatus: Bool
    var gameStatusFromServer: String

    func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) {
        let date = Date()
        let entry: GameStatusEntry

        if context.isPreview && !hasFetchedGameStatus {
            entry = GameStatusEntry(date: date, gameStatus: "—")
        } else {
            entry = GameStatusEntry(date: date, gameStatus: gameStatusFromServer)
        }
        completion(entry)
    }

在請求初始快照后口芍,WidgetKit調(diào)用getTimeline(in:completion:)向提供程序請求常規(guī)時間線箍铲。時間線由一個或多個時間線條目和重新加載策略組成,該策略通知WidgetKit何時請求后續(xù)時間線鬓椭。

以下示例顯示了游戲狀態(tài)小部件的提供商如何生成一個時間線颠猴,該時間線由來自服務(wù)器的具有當(dāng)前游戲狀態(tài)的單個條目和重新加載策略組成,以便在15分鐘內(nèi)請求新的時間線:

struct GameStatusProvider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<GameStatusEntry>) -> Void) {
        // Create a timeline entry for "now."
        let date = Date()
        let entry = GameStatusEntry(
            date: date,
            gameStatus: gameStatusFromServer
        )

        // Create a date that's 15 minutes in the future.
        let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: date)!

        // Create the timeline with the entry and a reload policy with the date
        // for the next update.
        let timeline = Timeline(
            entries:[entry],
            policy: .after(nextUpdateDate)
        )

        // Call the completion to pass the timeline to WidgetKit.
        completion(timeline)
    }
}

在本例中小染,如果小部件沒有來自服務(wù)器的當(dāng)前狀態(tài)翘瓮,它可以存儲對完成的引用,向服務(wù)器執(zhí)行異步請求以獲取游戲狀態(tài)裤翩,并在請求完成后調(diào)用完成资盅。

有關(guān)生成時間線的更多信息,包括在小部件中處理網(wǎng)絡(luò)請求,請參閱保持小部件最新律姨。

顯示占位符小部件并隱藏敏感數(shù)據(jù)

占位符視圖是一種通用的視覺表示振峻,沒有特定內(nèi)容。當(dāng)WidgetKit渲染您的小部件時择份,它可能需要將您的內(nèi)容呈現(xiàn)為占位符;例如烫堤,當(dāng)您在后臺加載數(shù)據(jù)時荣赶。它使用redacted(reason:)視圖修飾符生成占位符,并將reason設(shè)置為placeholder鸽斟。此設(shè)置以適合用作占位符的方式自動呈現(xiàn)小部件的視圖拔创。要選擇退出作為占位符的渲染,并控制哪些小部件的視圖顯示已編輯以隱藏敏感信息富蓄,請在redacted(reason:)回調(diào)中使用theunredactedunredacted()視圖修飾符剩燥。

除了首次出現(xiàn)外,當(dāng)您為小部件擴(kuò)展啟用數(shù)據(jù)保護(hù)功能時立倍,WidgetKit還會將小部件的視圖呈現(xiàn)為占位符灭红。WidgetKit使用您應(yīng)用的編輯或作為占位符視圖呈現(xiàn)小部件的視圖,如果您將數(shù)據(jù)保護(hù)權(quán)限設(shè)置為:

  • NSFileProtectionComplete設(shè)備被鎖定了
  • NSFileProtectionCompleteUnlessOpen設(shè)備被鎖定了
  • NSFileProtectionCompleteUntilFirstUserAuthentication用戶尚未進(jìn)行身份驗證

用戶控制小部件是否在某些演示上下文中顯示編輯后的視圖或占位符口注,例如在iPhone鎖定屏幕或watchOS中的表盤上变擒。

watchOS中,設(shè)備在使用期間很少被鎖定寝志,因為Apple Watch通常在用戶佩戴時解鎖娇斑。但是,用戶可以通過選擇設(shè)置>顯示和亮度>始終打開>隱藏敏感復(fù)雜功能來配置是否在始終打開期間顯示敏感數(shù)據(jù)材部。如果用戶啟用了隱藏敏感復(fù)雜功能毫缆,WidgetKit將呈現(xiàn)您配置的編輯或返回占位符。他們可以選擇顯示所有或單個并發(fā)癥的編輯內(nèi)容乐导。同樣苦丁,在iOS中,設(shè)備也可能支持“始終打開”兽叮,用戶可以配置是否在“始終打開”期間顯示敏感數(shù)據(jù)芬骄。在不支持“始終打開”的iOS設(shè)備上,用戶通過選擇“設(shè)置”>“面容 ID和密碼”以及停用鎖定屏幕小部件的數(shù)據(jù)訪問來控制是否在鎖定屏幕上顯示敏感數(shù)據(jù)鹦聪。

在小部件中顯示內(nèi)容

小部件使用SwiftUI視圖定義其內(nèi)容账阻,通常通過編寫其他SwiftUI視圖。如添加配置詳細(xì)信息部分所示泽本,小部件的配置包含WidgetKit調(diào)用以呈現(xiàn)小部件內(nèi)容的閉包淘太。

當(dāng)用戶從小部件庫中添加您的小部件時,他們會從小部件支持的系列中選擇特定的系列(例如,小型或中型)蒲牧。小部件的內(nèi)容閉包必須能夠渲染小部件支持的每個家庭撇贺。WidgetKitSwiftUI環(huán)境中設(shè)置相應(yīng)的系列和其他屬性,例如配色方案(淺色或深色)冰抢。

在上面顯示的游戲狀態(tài)小部件的配置中松嘶,內(nèi)容關(guān)閉使用Game來顯示狀態(tài)。由于小部件支持鎖定屏幕上的大小和配件小部件挎扰,因此它使用widget來決定要顯示的特定SwiftUI視圖翠订,如下所示:

struct GameStatusView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    var gameStatus: GameStatus
    var selectedCharacter: CharacterDetail

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall: GameTurnSummary(gameStatus)
        case .systemMedium: GameStatusWithLastTurnResult(gameStatus)
        case .systemLarge: GameStatusWithStatistics(gameStatus)
        case .systemExtraLarge: GameStatusWithStatisticsExtraLarge(gameStatus)
        case .accessoryCircular: HealthLevelCircular(selectedCharacter)
        case .accessoryRectangular: HealthLevelRectangular(selectedCharacter)
        case .accessoryInline: HealthLevelInline(selectedCharacter)
        default: GameDetailsNotAvailable()
        }
    }
}

對于小家庭,小部件使用一個視圖遵倦,顯示游戲中輪到誰的簡單摘要尽超。對于媒體,它顯示狀態(tài)梧躺,表示最后一圈的結(jié)果似谁。對于大型和超大型,由于有更多的可用空間掠哥,它顯示每個玩家的運(yùn)行統(tǒng)計信息巩踏。配件小部件比主屏幕上顯示的小部件小得多。因此龙致,它們顯示了循環(huán)配件小部件當(dāng)前所選字符的健康級別蛀缝。由于矩形和內(nèi)聯(lián)配件小部件允許更多文本,它們顯示角色的健康水平和剩余的愈合時間目代。如果家庭是未知類型屈梁,它會顯示默認(rèn)視圖,這表明游戲狀態(tài)不可用榛了。

筆記
視圖使用@View聲明其主體在讶,因為它使用的視圖類型各不相同。

對于可配置的小部件霜大,提供程序符合Intent构哺。此提供程序執(zhí)行與Timeline相同的功能,但它結(jié)合了用戶在小部件上自定義的值战坤。在傳遞給getSnapshot(for:in:completion:)getTimeline(for:in:completion:)configuration參數(shù)中曙强,這些自定義可供意圖時間線提供程序使用。您通常將用戶配置的值作為自定義時間線條目類型的屬性途茫,因此詳細(xì)信息可用于小部件的視圖碟嘴。

重要
小部件提供只讀信息,不支持滾動元素或開關(guān)等交互式元素囊卜。WidgetKit在渲染小部件的內(nèi)容時省略了交互式元素娜扇。

將動態(tài)內(nèi)容添加到您的小部件中

雖然小部件的顯示基于視圖的快照错沃,但您可以使用各種SwiftUI視圖,這些視圖在小部件可見時繼續(xù)更新雀瓢。有關(guān)提供動態(tài)內(nèi)容的更多信息枢析,請參閱保持小部件最新。

響應(yīng)用戶互動

當(dāng)用戶與您的小部件交互時刃麸,系統(tǒng)會啟動您的應(yīng)用程序來處理請求醒叁。當(dāng)系統(tǒng)激活您的應(yīng)用程序時,導(dǎo)航到與小部件內(nèi)容相對應(yīng)的詳細(xì)信息嫌蚤。您的小部件可以指定一個URL來通知應(yīng)用程序要顯示的內(nèi)容辐益。要在小部件中配置自定義URL:

  • 對于所有小部件,將widgetURL(_:)視圖修飾符添加到小部件視圖層次結(jié)構(gòu)中的視圖中脱吱。如果小部件的視圖層次結(jié)構(gòu)包含多個widget修飾符,則該行為未定義认罩。

  • 對于使用Widget.system箱蝠、Widget.systemWidget.system的小部件,請將一個或多個Link控件添加到小部件的視圖層次結(jié)構(gòu)中垦垂。您可以使用widgetLink控件宦搬。如果交互針對Link控件,系統(tǒng)將使用該控件中的URL劫拗。對于小部件中其他任何地方的交互间校,系統(tǒng)使用widgetURL視圖修飾符中指定的URL

例如页慷,在游戲中顯示單個角色詳細(xì)信息的小部件可以使用widget打開該應(yīng)用程序以顯示該角色的詳細(xì)信息憔足。

@ViewBuilder
var body: some View {
    ZStack {
        AvatarView(entry.character)
            .widgetURL(entry.character.url)
            .foregroundColor(.white)
    }
    .background(Color.gameBackground)
}

如果小部件顯示字符列表,則列表中的每個項目都可以在Link控件中酒繁。每個Link控件指定其顯示的特定字符的URL滓彰。

當(dāng)小部件收到交互時,系統(tǒng)會激活包含的應(yīng)用程序州袒,并將URL傳遞給onURL(perform:)application(_:open:options:)application(_:open:)具體取決于您的應(yīng)用程序使用的生命周期揭绑。

如果小部件不使用widgetLink控件,系統(tǒng)會激活包含的應(yīng)用程序郎哭,并將NSUser傳遞給onActivity(_:perform:)application(_:continue:restorationHandler:)application(_:continue:restorationHandler:)用戶活動的user字典包含有關(guān)用戶交互的小部件的詳細(xì)信息他匪。使用Widget.User中的鍵從Swift代碼中訪問這些值。要從Objective-C訪問user值夸研,請改用WGWidgetWGWidget鍵邦蜜。

對于使用Intent的小部件,用戶活動的interaction屬性包含小部件的INIntent陈惰。

在應(yīng)用程序擴(kuò)展中聲明多個小部件

上面的Game示例使用@main屬性為小部件擴(kuò)展指定單個入口點(diǎn)畦徘。要支持多個小部件毕籽,請聲明一個符合Widget的結(jié)構(gòu),該結(jié)構(gòu)在其body屬性中將多個小部件組合在一起井辆。在此小部件捆綁結(jié)構(gòu)上添加@main屬性关筒,以告訴WidgetKit您的擴(kuò)展支持多個小部件。

例如杯缺,如果游戲應(yīng)用程序有第二個小部件來顯示角色健康狀況蒸播,第三個小部件來顯示排行榜,它將按如下所示將它們分組:

@main
struct GameWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        GameStatusWidget()
        CharacterDetailWidget()
        LeaderboardWidget()
    }
}

在Xcode中預(yù)覽小部件

Xcode允許您查看小部件的預(yù)覽萍肆,而無需在模擬器或測試設(shè)備上運(yùn)行應(yīng)用程序袍榆。以下示例顯示了使用WidgetKit和SwiftUI示例代碼項目構(gòu)建小部件的表情符號游俠小部件的預(yù)覽代碼。注意它如何使用widgetFamily環(huán)境值來避免手動為每個小部件指定名稱塘揣。

@Environment(\.widgetFamily) var family
        
Group {
    EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .accessoryCircular))
        .previewDisplayName("\(family)")
  EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .accessoryRectangular))
        .previewDisplayName("\(family)")
    EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .accessoryInline))
        .previewDisplayName("\(family)")
    EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .systemSmall))
          .previewDisplayName("\(family)")
    EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .systemMedium))
          .previewDisplayName("\(family)")
}

使小部件保持最新狀態(tài)

計劃小部件的時間線包雀,使用動態(tài)視圖顯示及時的相關(guān)信息,并在事情發(fā)生變化時更新時間線亲铡。

小部件使用SwiftUI視圖來顯示其內(nèi)容才写。WidgetKit代表您在一個單獨(dú)的進(jìn)程中呈現(xiàn)視圖。因此奖蔓,即使小部件在屏幕上赞草,您的小部件擴(kuò)展也不會持續(xù)激活。盡管您的小部件并不總是處于活動狀態(tài)吆鹤,但有幾種方法可以使其內(nèi)容保持最新狀態(tài)厨疙。

計劃在預(yù)算內(nèi)重新加載

重新加載小部件會消耗系統(tǒng)資源,并因額外的網(wǎng)絡(luò)和處理而導(dǎo)致電池耗盡疑务。為了減少這種性能影響并保持全天電池壽命沾凄,請將您請求的更新頻率和數(shù)量限制在必要時。
為了管理系統(tǒng)負(fù)載暑始,WidgetKit使用預(yù)算在一天中分發(fā)小部件重新加載搭独。預(yù)算分配是動態(tài)的,并考慮了許多因素廊镜,包括:

  • 用戶可以看到小部件的頻率和時間牙肝。
  • 小部件的最后一次重新加載時間。
  • 小部件包含的應(yīng)用程序是否處于活動狀態(tài)嗤朴。

WidgetKit為用戶添加到設(shè)備中的每個活動小部件維護(hù)不同的預(yù)算配椭。例如,如果用戶添加了兩個可配置的體育小部件實(shí)例雹姊,顯示兩個不同團(tuán)隊的信息股缸,則每個小部件都有自己的預(yù)算。

小部件的預(yù)算適用于24小時吱雏。WidgetKit根據(jù)用戶的日常使用模式調(diào)整24小時窗口敦姻,這意味著每日預(yù)算不一定在午夜重置瘾境。對于用戶經(jīng)常查看的小部件,每日預(yù)算通常包括40到70次刷新镰惦。這個速率大致相當(dāng)于每15到60分鐘重新加載一次小部件迷守,但由于涉及許多因素,這些間隔通常會有所不同旺入。

筆記
系統(tǒng)需要幾天時間來了解用戶的行為兑凿。在此學(xué)習(xí)期間,您的小部件可能會收到比正常情況下更多的重新加載茵瘾。

WidgetKit不將重新加載計入小部件預(yù)算的情況包括:

  • 小部件包含的應(yīng)用程序位于前臺礼华。
  • 小部件包含的應(yīng)用程序具有活躍的音頻或?qū)Ш綍挕?/li>
  • 系統(tǒng)區(qū)域設(shè)置更改。
  • 動態(tài)類型或輔助功能設(shè)置更改拗秘。

對于系統(tǒng)外觀更改或系統(tǒng)區(qū)域設(shè)置更改等情況圣絮,請不要從應(yīng)用程序請求重新加載時間線。系統(tǒng)會自動更新您的小部件雕旨。

雖然您的小部件時間線提供程序驅(qū)動您的重新加載計劃晨雳,但WidgetKit有時會重新加載您的小部件,以幫助保持其內(nèi)容的新鮮奸腺。一些常見的場景包括:

  • 如果小部件位于用戶很少訪問的主屏幕頁面上,WidgetKit可能會減少該小部件重新加載的頻率血久。稍后突照,當(dāng)用戶查看頁面時,WidgetKit可能會在小部件可見時重新加載小部件氧吐。
  • 對于使用位置服務(wù)的小部件讹蘑,WidgetKit會在發(fā)生重大位置更改后重新加載它們。

如果您的小部件可以預(yù)測它應(yīng)該重新加載的時間點(diǎn)筑舅,最好的方法是為盡可能多的未來日期生成一個時間表座慰。對于您顯示的內(nèi)容,盡可能保持時間線中的條目間隔翠拣。WidgetKit在重新加載小部件之前施加了最短的時間版仔。您的時間表提供商應(yīng)創(chuàng)建至少相隔約5分鐘的時間線條目。WidgetKit可能會在多個小部件之間合并重新加載误墓,影響小部件重新加載的確切時間蛮粮。

為可預(yù)測事件生成時間線

許多小部件都有可預(yù)測的時間點(diǎn),更新其內(nèi)容是有意義的谜慌。例如然想,顯示天氣信息的小部件可能會全天每小時更新溫度。股市小部件可以在開放市場時間經(jīng)常更新其內(nèi)容欣范,但在周末根本無法更新变泄。通過提前計劃這些時間令哟,WidgetKit會在適當(dāng)時間到達(dá)時自動刷新您的小部件。

當(dāng)您定義小部件時妨蛹,您實(shí)現(xiàn)了自定義TimelineProvider屏富。WidgetKit從您的提供商那里獲取時間線,并使用它來跟蹤何時更新您的小部件滑燃。時間線是Timeline對象的數(shù)組役听。時間線中的每個條目都有一個日期和時間,以及小部件顯示其視圖所需的其他信息表窘。除了時間線條目外典予,時間線還指定了一個刷新策略,告訴WidgetKit何時請求新的時間線乐严。

以下是顯示角色健康水平的游戲小部件示例瘤袖。當(dāng)健康水平低于100%時,角色以每小時25%的速度恢復(fù)昂验。例如捂敌,當(dāng)角色的健康水平為25%時,需要3個小時才能完全恢復(fù)到100%既琴。下圖顯示了WidgetKit如何從提供商請求時間線占婉,在時間線條目中指定的每個時間渲染小部件。

image.png

當(dāng)WidgetKit最初請求時間線時甫恩,提供商會創(chuàng)建一個包含四個條目的時間表逆济。第一個條目代表當(dāng)前時間,然后是每小時三個條目磺箕。將刷新策略設(shè)置為默認(rèn)的at奖慌,WidgetKit會在時間線條目的最后日期之后請求新的時間線。當(dāng)時間線中的每個日期到達(dá)時松靡,WidgetKit會調(diào)用小部件的內(nèi)容閉包并顯示結(jié)果简僧。最后一個時間線條目通過后,WidgetKit通過向提供商詢問新的時間線來重復(fù)該過程雕欺。由于角色的健康狀況已達(dá)到100%岛马,提供商會響應(yīng)當(dāng)前時間的單個條目,刷新策略設(shè)置為never阅茶。使用此設(shè)置蛛枚,在應(yīng)用程序使用Widget告訴WidgetKit請求新的時間線之前,WidgetKit不會要求另一個時間線脸哀。

除了atnever刷新策略外蹦浦,如果時間線在到達(dá)條目結(jié)束之前或之后可能會發(fā)生變化,提供商可以指定完全不同的日期撞蜂。例如盲镶,如果龍將在2小時內(nèi)出現(xiàn)侥袜,向角色挑戰(zhàn)戰(zhàn)斗,提供者會將重新加載策略設(shè)置為after(_:)在未來2小時內(nèi)傳遞一個日期溉贿。下圖顯示了WidgetKit在2小時標(biāo)記渲染小部件后如何請求一個新的小部件枫吧。

image.png

由于與龍的戰(zhàn)斗,角色的治療將需要額外的2個小時才能達(dá)到100%宇色。新的時間表由兩個條目組成九杂,一個是當(dāng)前時間,第二個條目是未來2小時宣蠕。時間線指定了刷新策略的End例隆,表示沒有更多已知事件可能會改變時間線。

當(dāng)2小時過去抢蚀,角色的健康狀況達(dá)到100%時镀层,WidgetKit會要求提供商提供新的時間表。由于角色的健康狀況已恢復(fù)皿曲,提供商生成與上面第一張圖表相同的最終時間表唱逢。當(dāng)用戶玩游戲并且角色的健康級別發(fā)生變化時,該應(yīng)用程序使用Widget讓W(xué)idgetKit刷新時間線并更新小部件屋休。

除了在時間線結(jié)束前指定日期外坞古,提供商還可以在時間線結(jié)束后指定日期。當(dāng)您知道小部件的狀態(tài)要到以后才會改變時劫樟,這很有用绸贡。例如,股市小部件可以在周五市場收盤時創(chuàng)建一個時間表毅哗,并使用afterDate()刷新策略指定周一開盤時間。由于股市周末關(guān)閉捧挺,在市場開盤之前無需更新小部件虑绵。

重要
如果您的小部件在重新加載并使用afterDate()向服務(wù)器發(fā)出請求,請?zhí)崆坝媱澝隼樱⒃跁r間線條目中使用特定日期翅睛。WidgetKit試圖尊重您指定的日期,當(dāng)多個設(shè)備大約在同一時間重新加載您的小部件時黑竞,這可能會導(dǎo)致服務(wù)器負(fù)載顯著增加捕发。

當(dāng)時間線發(fā)生變化時通知WidgetKit

當(dāng)有東西影響小部件的當(dāng)前時間線時,您的應(yīng)用程序可以告訴WidgetKit請求新的時間線很魂。在上面的游戲小部件示例中扎酷,如果應(yīng)用程序收到推送通知,表明隊友已給角色服用治療藥水遏匆,該應(yīng)用程序可以告訴WidgetKit重新加載時間線并更新小部件的內(nèi)容法挨。要重新加載特定類型的小部件谁榜,您的應(yīng)用程序使用Widget,如下所示:

WidgetCenter.shared.reloadTimelines(ofKind: "com.mygame.character-detail")

kind參數(shù)包含與用于創(chuàng)建小部件Widget的值相同的字符串凡纳。

如果您的小部件具有用戶可配置的屬性窃植,請使用WidgetCenter驗證是否存在具有適當(dāng)設(shè)置的小部件,以避免不必要的重新加載荐糜。例如巷怜,當(dāng)游戲收到關(guān)于角色收到治療藥水的推送通知時,它會在重新加載時間線之前驗證小部件是否顯示該角色暴氏。

在以下代碼中延塑,應(yīng)用程序調(diào)用getConfigurations(_:)來檢索用戶配置的小部件列表。然后偏序,它迭代生成的Widget對象页畦,以找到一個具有配置為接受治療藥水的字符的intent。如果它找到一個研儒,該應(yīng)用程序會為該小部件的kind調(diào)用reloadTimelines(ofKind:)

WidgetCenter.shared.getCurrentConfigurations { result in
    guard case .success(let widgets) = result else { return }

    // Iterate over the WidgetInfo elements to find one that matches
    // the character from the push notification.
    if let widget = widgets.first(
        where: { widget in
            let intent = widget.configuration as? SelectCharacterIntent
            return intent?.character == characterThatReceivedHealingPotion
        }
    ) {
        WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
    }
}

如果您的應(yīng)用程序使用Widget來支持多個小部件豫缨,您可以使用Widget重新加載所有小部件的時間線。例如端朵,如果您的小部件要求用戶登錄帳戶好芭,但它們已注銷,您可以通過調(diào)用以下方式重新加載所有小部件:

WidgetCenter.shared.reloadAllTimelines()

顯示動態(tài)日期

即使您的小部件不會持續(xù)運(yùn)行冲呢,它也可以顯示WidgetKit實(shí)時更新的基于時間的信息舍败。例如,它可能會顯示一個倒數(shù)計時計時器敬拓,即使您的小部件擴(kuò)展沒有運(yùn)行邻薯,它也會繼續(xù)倒計時。有關(guān)更多信息乘凸,請參閱在小部件中顯示動態(tài)日期厕诡。

后臺網(wǎng)絡(luò)請求完成后更新

當(dāng)您的小部件擴(kuò)展處于活動狀態(tài)時,例如在提供snapshottimeline時营勤,它可以啟動后臺網(wǎng)絡(luò)請求灵嫌。例如,獲取隊友當(dāng)前狀態(tài)的游戲小部件葛作,或帶有圖像縮略圖獲取標(biāo)題的新聞小部件寿羞。發(fā)出異步后臺網(wǎng)絡(luò)請求可以讓您快速將控制權(quán)返回到系統(tǒng),從而降低因響應(yīng)時間過長而被終止的風(fēng)險赂蠢。
該過程類似于應(yīng)用程序處理此類請求的方式绪穆,這在后臺下載文件中進(jìn)行了描述。WidgetKit不會恢復(fù)您的應(yīng)用程序,而是直接激活小部件的擴(kuò)展霞幅。要處理網(wǎng)絡(luò)請求的結(jié)果漠吻,請使用onBackgroundEvents(matching:_:)修飾符進(jìn)行小部件的配置,并執(zhí)行以下操作:

  • 存儲對completion參數(shù)的引用司恳。您在處理所有網(wǎng)絡(luò)事件后調(diào)用完成處理程序途乃。
  • 使用標(biāo)識identifier參數(shù)查找您在發(fā)起后臺請求時使用的URLSession對象。如果您的小部件擴(kuò)展被終止扔傅,請使用標(biāo)識符重新創(chuàng)建URLSession耍共。

調(diào)用onBackgroundEvents()后,系統(tǒng)調(diào)用您提供給URLSession的urlSession(_:downloadTask:didTo:)方法猎塞。當(dāng)所有事件都已交付后试读,系統(tǒng)會調(diào)用委托的urlEvents(forURLSession:)方法。

要在網(wǎng)絡(luò)請求完成后刷新小部件的時間線荠耽,請從委托的url實(shí)現(xiàn)中調(diào)用Widget方法钩骇。完成處理事件后,調(diào)用您之前存儲在onBackgroundEvents()中的completion程序铝量。

制作可配置的小部件

通過向您的項目添加自定義SiriKit意圖定義倘屹,為用戶提供自定義小部件的選項。

為了使用戶能夠輕松訪問最相關(guān)的信息慢叨,小部件可以提供可定制的屬性纽匙。例如,用戶可以為股票報價小部件選擇特定股票拍谐,或為包裹交付小部件輸入跟蹤編號烛缔。小部件通過使用自定義意圖定義來定義可自定義屬性,這與Siri建議和Siri快捷方式用于自定義這些交互的機(jī)制相同轩拨。

要將可配置屬性添加到您的小部件中:

  1. 添加自定義意圖定義践瓷,為您的Xcode項目定義可配置屬性。
  2. 在小部件中使用Intent將用戶的選擇合并到您的時間線條目中亡蓉。
  3. 如果屬性依賴于動態(tài)數(shù)據(jù)当窗,請實(shí)現(xiàn)意圖擴(kuò)展。

如果您的應(yīng)用程序已經(jīng)支持Siri建議或Siri快捷方式寸宵,并且您有自定義意圖,那么您可能已經(jīng)完成了大部分工作元咙。否則梯影,請考慮利用您為小部件所做的工作來添加對Siri建議或Siri快捷方式的支持。

以下部分展示了如何將可配置屬性添加到顯示游戲中角色信息的小部件中庶香。

為您的項目添加自定義意圖定義

在Xcode項目中甲棍,選擇文件>新建文件,然后選擇SiriKit意圖定義文件赶掖。單擊下一步感猛,并在出現(xiàn)提示時保存文件。Xcode創(chuàng)建一個新的.intentdefinition文件,并將其添加到您的項目中偏竟。

image.png

Xcode從意圖定義文件生成代碼硬纤。要在目標(biāo)中使用此代碼:

  • 將意圖定義文件作為目標(biāo)的成員。
  • 通過將意圖的類名添加到目標(biāo)屬性的“支持的意圖”部分咱士,指示要包含在目標(biāo)中的具體意圖立由。

如果您在框架中包含意圖定義文件,您還必須將其包含在包含應(yīng)用程序的目標(biāo)中序厉。在這種情況下锐膜,為了避免在應(yīng)用程序和框架中重復(fù)類型,請在文件檢查器的目標(biāo)成員部分中為應(yīng)用程序目標(biāo)選擇無生成類弛房。
要添加和配置允許用戶在游戲中選擇角色的自定義意圖:

  1. 在項目導(dǎo)航器中道盏,選擇意圖文件。Xcode顯示一個空的意圖定義編輯器文捶。
  2. 選擇編輯器>新意圖荷逞,然后在自定義意圖下選擇意圖。
  3. 將自定義意圖的名稱更改為Select拄轻。請注意颅围,屬性檢查器的自定義類字段顯示您在代碼中引用意圖時使用的類名。在這種情況下恨搓,它是Select院促。
  4. 將類別設(shè)置為查看,然后選擇“意圖符合小部件的條件”復(fù)選框斧抱,以指示小部件可以使用意圖常拓。
  5. 在參數(shù)下,添加一個帶有名稱character的新參數(shù)辉浦,這是小部件的可配置設(shè)置弄抬。
image.png

添加參數(shù)后,為它配置詳細(xì)信息宪郊。如果參數(shù)為用戶提供靜態(tài)選擇列表掂恕,請選擇添加枚舉菜單項以創(chuàng)建靜態(tài)枚舉。例如弛槐,如果參數(shù)指定了字符的頭像懊亡,并且可能的頭像列表是一個不變的常量集,您可以使用靜態(tài)枚舉來指定意圖定義文件中的可用選項乎串。如果可能的頭像列表可以變化或動態(tài)生成店枣,請改用帶有動態(tài)選項的類型。

在本例中,character屬性依賴于應(yīng)用程序中可用的字符動態(tài)列表鸯两。要提供動態(tài)數(shù)據(jù)闷旧,請創(chuàng)建一個新類型:

  1. 從類型彈出式菜單中,選擇添加類型钧唐。Xcode在編輯器的類型部分添加了一種新類型忙灼。
  2. 將類型名稱更改為Game。
  3. 添加一個新的name屬性逾柿,然后從類型彈出式菜單中選擇字符串缀棍。
  4. 選擇Select意圖。
  5. 在意圖編輯器中机错,選擇“選項是動態(tài)提供的”復(fù)選框爬范,以指示您的代碼為此參數(shù)提供了動態(tài)項目列表。
image.png

Game類型描述了用戶可以選擇的角色弱匪。在下一節(jié)中青瀑,您將添加代碼以動態(tài)提供字符列表。

筆記
意圖中參數(shù)的順序決定了用戶編輯小部件時它們出現(xiàn)的順序萧诫。您可以通過拖動來重新排列列表中的項目斥难。

為您的項目添加意圖擴(kuò)展

為了動態(tài)提供字符列表,您將向應(yīng)用程序添加Intents擴(kuò)展帘饶。當(dāng)用戶編輯小部件時哑诊,WidgetKit會加載Intents擴(kuò)展以提供動態(tài)信息。要添加意圖擴(kuò)展:

  1. 選擇文件>新建>目標(biāo)及刻,然后選擇意圖擴(kuò)展名镀裤。
  2. 點(diǎn)擊下一步。
  3. 為您的Intents擴(kuò)展輸入一個名稱缴饭,并將起點(diǎn)設(shè)置為None暑劝。
  4. 點(diǎn)擊完成。如果Xcode提示您激活新方案颗搂,請單擊激活担猛。
  5. 在新目標(biāo)屬性的常規(guī)選項卡中,在“支持的意圖”部分中添加一個條目丢氢,并將類名稱設(shè)置為Select傅联。
  6. 在項目導(dǎo)航器中,選擇您之前添加的自定義意圖定義文件疚察。
  7. 使用文件檢查器將定義文件添加到意圖擴(kuò)展目標(biāo)中蒸走。

重要
在文件檢查器中,驗證包含的應(yīng)用程序稍浆、小部件擴(kuò)展和意圖擴(kuò)展是否都包含意圖定義文件。

實(shí)現(xiàn)一個意圖處理程序來提供動態(tài)值

當(dāng)用戶編輯具有提供動態(tài)值的自定義意圖的小部件時,系統(tǒng)需要一個對象來提供這些值衅枫。它通過要求Intents擴(kuò)展為Intents提供處理程序來識別此對象嫁艇。當(dāng)Xcode創(chuàng)建Intents擴(kuò)展時,它會向您的項目添加一個名為Intent.swift的文件弦撩,其中包含一個名為Intent的類步咪。該類包含一個返回處理程序的方法。您將擴(kuò)展此處理程序益楼,以提供小部件自定義的值猾漫。

基于自定義意圖定義文件,Xcode生成一個協(xié)議Select感凤,處理程序必須符合該協(xié)議悯周。將此一致性添加到Intent類的聲明中。(要查看此協(xié)議的詳細(xì)信息以及Xcode自動生成的其他類型陪竿,請選擇Select禽翼,然后選擇導(dǎo)航>跳轉(zhuǎn)到定義。)

class IntentHandler: INExtension, SelectCharacterIntentHandling {
    ...
}

當(dāng)處理程序提供動態(tài)選項時族跛,它必須實(shí)現(xiàn)一個名為provide[Type]OptionalCollection(for:with:)的方法闰挡,其中[Type]是來自意圖定義文件的類型名稱。如果缺少此方法礁哄,Xcode將報告構(gòu)建錯誤长酗,提供修復(fù)程序以添加協(xié)議存根。構(gòu)建您的項目桐绒,并使用修復(fù)程序添加此存根夺脾。

此方法包括您調(diào)用的完成處理程序,傳遞一個INObjectCollection<GameCharacter>掏膏。注意Game類型劳翰;這是意圖定義文件中的自定義類型。Xcode生成代碼來定義它如下:

public class GameCharacter: INObject {
    @available(iOS 13.0, macOS 11.0, watchOS 6.0, *)
    @NSManaged public var name: String?
}

請注意name屬性馒疹,它也來自您添加的自定義類型的意圖定義文件佳簸。
要實(shí)現(xiàn)provideCharacterCollection(for:with:)方法,小部件使用游戲項目中存在的結(jié)構(gòu)颖变。此結(jié)構(gòu)定義了可用字符及其詳細(xì)信息的列表生均,如下所示:

struct CharacterDetail {
    let name: String
    let avatar: String
    let healthLevel: Double
    let heroType: String

    static let availableCharacters = [
        CharacterDetail(name: "Power Panda", avatar: "??", healthLevel: 0.14, heroType: "Forest Dweller"),
        CharacterDetail(name: "Unipony", avatar: "??", healthLevel: 0.67, heroType: "Free Rangers"),
        CharacterDetail(name: "Spouty", avatar: "??", healthLevel: 0.83, heroType: "Deep Sea Goer")
    ]
}

在意圖處理程序中,代碼迭代availableCharacters數(shù)組腥刹,為每個字符創(chuàng)建一個Game對象马胧。為了簡單起見,Game的身份是角色的名字衔峰。游戲角色數(shù)組被放入INObject中佩脊,處理程序?qū)⒓蟼鬟f給完成處理程序蛙粘。

class IntentHandler: INExtension, SelectCharacterIntentHandling {
    func provideCharacterOptionsCollection(for intent: SelectCharacterIntent, with completion: @escaping (INObjectCollection<GameCharacter>?, Error?) -> Void) {

        // Iterate the available characters, creating
        // a GameCharacter for each one.
        let characters: [GameCharacter] = CharacterDetail.availableCharacters.map { character in
            let gameCharacter = GameCharacter(
                identifier: character.name,
                display: character.name
            )
            gameCharacter.name = character.name
            return gameCharacter
        }

        // Create a collection with the array of characters.
        let collection = INObjectCollection(items: characters)

        // Call the completion handler, passing the collection.
        completion(collection, nil)
    }
}

完成意圖定義文件的配置,并將意圖擴(kuò)展添加到應(yīng)用程序中威彰,用戶可以編輯小部件以選擇要顯示的特定字符出牧。WidgetKit使用意圖定義文件中的信息自動創(chuàng)建用于編輯小部件的用戶界面。
一旦用戶編輯了小部件并選擇了一個字符歇盼,下一步就是將該選擇納入小部件的顯示中舔痕。

處理用戶自定義值

為了支持可配置的屬性,小部件使用Intent配置豹缀。例如伯复,字符詳細(xì)信息小部件定義其配置如下:

struct CharacterDetailWidget: Widget {
    var body: some WidgetConfiguration {
        IntentConfiguration(
            kind: "com.mygame.character-detail",
            intent: SelectCharacterIntent.self,
            provider: CharacterDetailProvider(),
        ) { entry in
            CharacterDetailView(entry: entry)
        }
        .configurationDisplayName("Character Details")
        .description("Displays a character's health and other details")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

Select參數(shù)決定了小部件的用戶可自定義屬性。該配置使用Character來管理小部件的時間線事件邢笙。有關(guān)時間線提供商的更多信息啸如,請參閱保持小部件最新。

用戶編輯小部件后鸣剪,WidgetKit在請求時間線條目時將用戶自定義值傳遞給提供商组底。您通常在提供商生成的時間線條目中包含來自意圖的相關(guān)詳細(xì)信息。在本例中筐骇,提供程序使用助手方法使用意圖中的字符名稱查找CharacterDetail债鸡,然后創(chuàng)建一個包含字符詳細(xì)信息的條目的時間線:

struct CharacterDetailProvider: IntentTimelineProvider {
    func getTimeline(for configuration: SelectCharacterIntent, in context: Context, completion: @escaping (Timeline<CharacterDetailEntry>) -> Void) {
        // Access the customized properties of the intent.
        let characterDetail = lookupCharacterDetail(for: configuration.character.name)

        // Construct a timeline entry for the current date, and include the character details.
        let entry = CharacterDetailEntry(date: Date(), detail: characterDetail)

        // Create the timeline and call the completion handler. The .never reload 
        // policy indicates that the containing app will use WidgetCenter methods 
        // to reload the widget's timeline when the details change.
        let timeline = Timeline(entries: [entry], policy: .never)
        completion(timeline)
    }
}

當(dāng)您在時間線條目中包含用戶自定義值時,您的小部件可以顯示適當(dāng)?shù)膬?nèi)容铛纬。

在Apple Watch上提供預(yù)配置的復(fù)雜功能

watchOS 9iOS 16開始厌均,您可以使用WidgetKit實(shí)現(xiàn)在Apple Watch上顯示為復(fù)雜功能的配件系列小部件。與iOSmacOS中的小部件一樣告唆,watch復(fù)雜功能使用自定義意圖來顯示用戶可配置的數(shù)據(jù)棺弊,在watchOS中實(shí)現(xiàn)可配置小部件的工作原理與iOSmacOS相同。然而擒悬,watchOS沒有提供用于配置復(fù)雜功能的專用用戶界面模她。要在手表復(fù)雜功能中顯示與用戶最相關(guān)的數(shù)據(jù),您可以創(chuàng)建預(yù)配置的復(fù)雜功能懂牧,并在可用復(fù)雜功能列表中向用戶推薦它們侈净。在您的Timeline代碼中,實(shí)現(xiàn)recommendations()并返回您使用自定義意圖創(chuàng)建的Intent對象僧凤。

當(dāng)您的應(yīng)用程序收到與您推薦的小部件配置相關(guān)的新數(shù)據(jù)時畜侦,請調(diào)用 invalidateConfigurationRecommendations(),使現(xiàn)已過時的建議無效躯保。這告訴WidgetKit獲取新的推薦的預(yù)配置旋膳。當(dāng)您使推薦的配置無效時,請確保在recommendations()回調(diào)中返回更新的Intent對象途事。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末验懊,一起剝皮案震驚了整個濱河市擅羞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌义图,老刑警劉巖祟滴,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歌溉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)骑晶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門痛垛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桶蛔,你說我怎么就攤上這事匙头。” “怎么了仔雷?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵蹂析,是天一觀的道長。 經(jīng)常有香客問我碟婆,道長电抚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任竖共,我火速辦了婚禮蝙叛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘公给。我一直安慰自己借帘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布淌铐。 她就那樣靜靜地躺著肺然,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腿准。 梳的紋絲不亂的頭發(fā)上际起,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音释涛,去河邊找鬼加叁。 笑死,一個胖子當(dāng)著我的面吹牛唇撬,可吹牛的內(nèi)容都是我干的它匕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼窖认,長吁一口氣:“原來是場噩夢啊……” “哼豫柬!你這毒婦竟也來了告希?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤烧给,失蹤者是張志新(化名)和其女友劉穎燕偶,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體础嫡,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡指么,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了榴鼎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伯诬。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖巫财,靈堂內(nèi)的尸體忽然破棺而出盗似,到底是詐尸還是另有隱情,我是刑警寧澤平项,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布赫舒,位于F島的核電站,受9級特大地震影響闽瓢,放射性物質(zhì)發(fā)生泄漏接癌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一扣讼、第九天 我趴在偏房一處隱蔽的房頂上張望扔涧。 院中可真熱鬧,春花似錦届谈、人聲如沸枯夜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湖雹。三九已至,卻和暖如春曙搬,著一層夾襖步出監(jiān)牢的瞬間摔吏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工纵装, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留征讲,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓橡娄,卻偏偏與公主長得像诗箍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挽唉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容