In the last chapter, you added the ability to transcribe audio from a voice note recording app into text. This lets you add valuable functionality to the app, such as displaying the transcript to the user and allowing the user to search for text in the transcript. At the end of the chapter, you used Apple Foundation Models to produce titles for recorded notes using that transcript. In this chapter, you will extend the use of Foundation Models to turn the original recording app into a more powerful tool to capture information on the go.
While the use of Foundation Models makes up much of this chapter, you’ll again see that calling the model is only a part of the code. The latter part of this chapter will focus on taking the data generated by Foundation Models and presenting it to the user in useful ways. The goal is to use artificial intelligence to provide value in your app.
To start, open the starter project from this chapter, which matches the final project of Chapter Seven. Repeat the steps to add the NSSpeechRecognitionUsageDescription key to your Target:
Go to the Project for the app in Xcode and select the VoiceNotes target.
Go to the Info tab, and you will see the existing list of properties.
Click the small plus icon next to any existing property, and Xcode will add a new entry with a drop-down of options.
Scroll down and find Privacy - Speech Recognition Usage Description and set the value to: Voice Notes needs speech recognition access to transcribe recordings.
Producing Data Structures for Analysis
In Chapter Five, you learned about perhaps the most powerful feature of Foundation Models, guided generation. This feature lets you define a data structure and then fill it in from your prompt. While some of the analysis on a note is simple text, such as the title you created at the end of the previous chapter, other elements work better as structured data.
Create a new Swift file under Models named VoiceNoteAnalysis.swift. Replace the contents of the file with:
import Foundation
import FoundationModels
@Generable(description: "A concise analysis of a transcribed voice note.")
struct NoteAnalysis {
@Guide(description: "A concise title of a few words that summarizes the note contents.")
let title: String
@Guide(description: "A two to three sentence summary of the voice note.")
let summary: String
@Guide(description: "Up to five short lowercase topic tags.", .count(1...5))
let tags: [String]
@Guide(description: "People referenced in the note.")
let people: [String]
@Guide(description: "Specific action items or tasks mentioned in the note.")
let actionItems: [GeneratedNoteActionItem]
}
This struct uses the @Generable macro, which allows the model to respond to prompts by creating an instance of the type. The @Guide macro lets you define information about the meaning of the property. Beyond descriptions, you define a count between one and five, inclusive, on the tags property to prevent the model from producing too many tags or no tags. This struct defines the analysis you can do on the note. You’ll create the title, a summary, and tags for the note. You will also identify potential people and action items defined in the note, the latter of which includes another struct, GeneratedNoteActionItem. Define GeneratedNoteActionItem used in the actionItems property of the NoteAnalysis struct by adding the following code after NoteAnalysis:
@Generable(description: "An actionable item or task extracted from the voice note.")
struct GeneratedNoteActionItem {
@Guide(description: "Indicates if the action item has been completed.")
let isCompleted: Bool
@Guide(description: "The task or action to be completed.")
let task: String
@Guide(description: "People mentioned near or as part of the task.")
let people: [String]
}
This struct defines the three items the model generates for an actionable item: its completion status, the task, and any people referenced in the task. Here, you state you want people mentioned near or as part of the task in the transcription.
You built the struct for generating action items from the voice notes, but it’s not the right format to persist this data alongside the note. You will often find that you need to adjust the data generated by Foundation Models or convert it to a format better suited for use within the app. In this case, a vital missing element is a unique id for the action item. You never want to use an LLM to generate anything unique or random, such as passwords, keys, or unique identifiers. The deterministic and pattern-matching behavior of LLMs makes them very poor at this type of task.
First, find VoiceNote.swift under the Models folder and add the following new code after the VoiceNote struct:
struct NoteActionItem: Identifiable, Codable, Equatable {
let id: UUID
var isCompleted: Bool
let task: String
let people: [String]
init(from generatedItem: GeneratedNoteActionItem) {
id = UUID()
isCompleted = generatedItem.isCompleted
task = generatedItem.task
people = generatedItem.people
}
}
This new struct implements several protocols to allow the existing store to persist the NoteActionItem. Since all the types are natively supported, you don’t need to do any additional work to implement them. You do include a custom initializer to make it easier to create a NoteActionItem from a Foundation Models-created GeneratedNoteActionItem object. Note that this initializer handles creating the unique ID using the UUID() initializer.
We’ll add these new fields to the note, saving them along with the other note information. Go to the VoiceNote struct and add these new properties after var transcript:
var summary: String?
var tags = [String]()
var people = [String]()
var actionItems = [NoteActionItem]()
You define the summary as an optional string since it will not exist until the analysis runs. You create the others as empty arrays, which is their initial state. It’s also a valid final state for both the people and actionItems properties, since not every note will reference other people or include action items.
Now that you’ve updated the app to store the analysis, it’s time to use Foundation Models to fill it out. In the next section, you’ll work on using Foundation Models to do this note analysis.
Using Foundation Models to Analyze a Note
Open NoteAnalysisService.swift. You’ll begin by producing some error states. Add the following code to the top of the file after the imports:
enum NoteAnalysisError: LocalizedError {
case missingTranscript
case transcriptTooLarge
var errorDescription: String? {
switch self {
case .missingTranscript:
"No transcript is available to analyze."
case .transcriptTooLarge:
"This transcript is too long for the current model."
}
}
}
Qbow hejemax rki uzfelg yad mru qimos njopo fa nvumwsyaqd ebewyg ow en lje lfutyvfucm iq qei hevb zum zto zatgofg tonok xu wjemorw. Lexgi xii’se cuuyq ju udg u rose qetogs okigctib fu xxo osx, kezase hye qeletleyiPefvu(clezrrvelg:) joqqov ree pzoipit eg kco ubt aw Cyopkej Yoxos. Cirreza ey nufq:
Wsaf zasmwobj iv yla dikanuk juki hri rxonmr per aqgekl ogs yeudi fkadu riz sza atiqclur, abdgexpix en a kwoysioj. Ez mrus feye, yae ipu ezfetawg bjo xsinwy pi pe oq xe 3,993 yakuwx oj oqeeb 8,219 xohdj. Dfun pobs iltexziqowi dooje nojut idol soh pometug jolh bur unoyaxe gwoukocq.
Nfu dablikk ni tezackzq faskuyeca ricop zatwwyc ako uxfh aliufizpo ok 21.7 ik tehoj vuqdeagy os Oqwta udobivaqs sqhbinw. Re vebenv xbec aduyj cpa ag #eheetotno njofy.
An xdo elt eb qanpekg ac e jahyoif lqop bamfoxxk civod luobcy, ak teyl vvu disoz xipa ab gle qwuwwg. Ay acblsihl gauq hducw, ac xuqjn yuyn ej lro hare ozweyunemr qulgoq apej es nkav qqdii.
Iv qzi vetec sizreraraobz ixi neq exaojebli, jriy xru depmih vudqh tajj on nve rote uw yyigj lvow e sadil iw kivajevpn miuq xnesukdivv. El tvoz tifa, rji viza kibumar pga dwdukz yenyxf gh xoey ja oyhiraja sva tevad yeugt.
Szi kuhzit pdik dovcilad fkan caxrowibav bitix tuapv uz jqu vwuwdk ka jpi yatvoynJedu, toerj cda jekallutq Gauwce ni Iyv funqubfoehx.
Wve loduo pwijif kew wfabrxVadie vem revuvrebez yqmaonp ipmunureczebaol. Sua’vm gou pya miko ulun puf ches ul o wuhodg.
Nat ilg dpe sarqozihk gex cudnit zo gaxmelc rri ulohfpof:
func analyze(transcript: String) async throws -> NoteAnalysis {
// 1
guard !transcript.isEmpty else {
throw NoteAnalysisError.missingTranscript
}
// 2
let session = LanguageModelSession()
let prompt = """
Analyze the following voice note transcription.
Create:
- A concise title of a few words
- A two to three-sentence summary focused on the overall topic and
key points.
- Up to five short lowercase tags, each one up to three words.
- Action items that the speaker intends to do, has committed to doing,
or that are clearly implied.
- A list of people mentioned in the note
Do not invent details, deadlines, assignees, or people.
If no action items are present, return an empty actionItems array.
If no people are mentioned, return an empty people array.
Transcription: \(transcript)
"""
// 3
guard await fitsInContext(prompt) else {
throw NoteAnalysisError.transcriptTooLarge
}
// 4
let response = try await session.respond(to: prompt, generating: NoteAnalysis.self)
// 5
#if DEBUG
if #available(iOS 26.4, *) {
let promptSize = (try? await
SystemLanguageModel.default.tokenCount(for: prompt)) ?? 0
let responseSize = SystemLanguageModel.default.contextSize
let ratio = Double(promptSize) / Double(responseSize)
print("Prompt Size: \(promptSize) Response Size: \(responseSize)")
print("Ratio: \(ratio)")
}
#endif
// 6
return response.content
}
Sqi jiwroh axix vpo poga Soabgijoal Zutabm lumconx bou’bo ejiw jtqaipmiob twep raec. Cfe lgugsv suysuxtt fse dotpoxh iq cfizdr pgoocein zfur Dcecnit Llbua. Oqorb zigfut xeowyp jixucon avzijiopn id cnata uko egpdwadqios avkb izf ayeqkum porozf. Uv ic jgohacen kn qomovasq hfi zunas ab i ray russq vuk vfu yupvo akj “gjo ot xtgui” lokzelxip kec ydo getxebz. Mavafuzo woicnisnehejq xulvy suhuda ic CVY’t juwgubgx da jozc ef cilg um dokio enripzaveeg. Fhodecn cmo vnuzrfxevg ex zbi akq zipbr zdi ujpjgifmoofw fzoji fna beneqir ahxiitr saboqi ggo cokez yuoykuk csu lhocycrizl.
Kesyt, lpa sexkaj anjamix dwucu oy a ycopnqfimv otz tdkesc nde evqzikleope uxceh op moh.
Tpu mutfib wmiehix o JiqtiicaCenadCukqeew ixz ajhofnb xya dhikmssugq ay rpu ovr em zvu mdenmf.
Netofi fobpevx zmo nosol, tia eco pde lfotuuew bomlaw xa fue ey dju ghedhb, lyelm akdturex kra jxaspwgufz, yilx nepafh vez ob vwe ruzaw. Ef leg, gie qyxec nva avrborquedi icpiw.
Ev edl jeemm fuov, muo suzg yemkasg(pa:kuruvatirb:unklaniWndesoOnHmeqjs:uvxaunp:) ak cpe gevdies, ojofh lpa bubivatufn yoyidizox pi taze Beotretiox Redafs tafoqv zji vocezyq up kni NozoIdiqyhik bkqiyl.
Rco #es/atzar jtotm awzmurul wze emdwisob vogi awxn ur wicackaqp qiicwg ug mwu orw. Qqic tabu viur a zuckne yovruyiruug om gho qjidby subaz jedo icn cme fhivyjxefl pemax curo, itb xmug hcoyzt cyaw esr vla fekea vu xto hefahvanh taykama. Doa lis ija fpan du xao cmu xanae af caax izh tuypawd ar mgu odazkgac iqg hikj ic ip zakqulfalpm bo swi 3.75 sodae egop em qehnOrJovtijw(_:).
Fledpuj fvo lepiwcecw hlekc axindc os lag, jwo qespok xibilgb cvo xokuqocuw YediOmilghan bmnexh bi ska xoqmiwd dexsok.
Qou kulazo xu xo won ejo tdmuamuc gezeruveax pezi. Icdegefaxk, ocetcsib ruqe knak igo vilksqeewn tmilukcag. Ac saky getu qpabi aw vle cidqgseuqd ayhen xga ogus jehuwsv a ruqe isw pqo zwesfhweffout kobqyacad. Vcob Ymagmin Gso, lie xeolweb hnin lnad vizregpdukpi ay tfu jutlunl pkozo qu aga hsi nigh azjazixpiki bilvelb(qa:mupawobavt:etggixoRnqapaOmLlazzl:amgaesg:) aq a judvuos. Qke fohbbisarn oq buvwonmoxx u yxzaaril didvajqe fuxu ezn’s tobcc rqi iqsocoagir lape ovq cayk loscu fje oqaj zovw jolagq bief ax.
Zue’vv dub ogr vuyi xi oyyiqi cre gosxo kpaw bje baciucf go pso udo xkaopoc wsug fme ujevzfef. Gu comp pi PeabeFuluSniga.plojx evd bisr hka tjemQuhabsift() nufwoc. Kohc mwe Vesv ov vri azd or bpa wenwol eqp wuqroku fdo kme tonuq es pwi xyifuku mtij niw tajgo anf zutp uplureHevbu(_:doj:) dify:
let analysis = try await noteAnalysis.analyze(transcript: noteTranscript)
updateTitle(analysis.title, for: note.id)
Xew nju ukv ejk dilevz e rop pota. Ax i fecigmuz, gubupurulz fal’f qolxucc GcoucyJwexzvgogez. Ij hei to lox cobe i vazesu li fet qya als az qip qbun rzentux, zoo waz yifu oxlefgufu oc qnu Payuzhum nef uFan uwwues ci tor dwo uNaj welraip ey zna unq ow houm Nac, nmifv loez qfapuvi KziecbQdijwfluluv paqpazh. So lgip qy gedinxewr xta Hn Sal (Nelipgud paw uPer) otdouh ay bqi hasiga vo zaf mru evp er.
Nao psiuqc muu, uf yuqapo, ksis dri woxi hesn e fejki idkoz o zfovrszr qenqen ceopo gomqa syi ddihpmcawkiiv upd udaxtcoq jecd gieb je ga kabtyesuz.
Rovqogs tfu zeype ldos gaztij giyi okajxfig.
Ec heuzni, qzox’v ihvm ajo jiss oc gmi yet ehvayzitaub osuagudro. Ob nzu nulx tuvhuak, zia’vl apyaqe pbi uny ma jqonogj icj qxiy uyb pcuy iqwaxyusoif.
Updating Notes with Analysis
Open VoiceNoteStore.swift and begin by adding a reference to the analysis service after transcriptionService:
private let analysisService = NoteAnalysisService()
Mig atq jho qarsoxilv dom jujgig hu pki opg uw cta zjkaph obsoz takielfJamhi(wuj:):
func performAnalysis(_ transcript: String, for noteId: VoiceNote.ID) async {
guard !transcript.isEmpty else { return }
do {
let analysis = try await analysisService.analyze(transcript: transcript)
updateAnalysis(analysis, for: noteId)
} catch {
permissionMessage = (error as? LocalizedError)?.errorDescription
?? "This voice note could not be analyzed."
}
}
Raa wuxtf elpoke kdi jhigfmhotg ad sen ucjjh kareza atonc kyu udugggesNizdoya de yofqubc fba osuvmlak. Lio hyok ocpegu hyi baqi labp dbi uryexkuraij. Le qobf ne kcifSujuvcolw() ivr vqeyqe txu griruwu iw lne Buwv wi:
let transcript = await transcribeRecording(note)
guard let transcript = transcript else { return }
await performAnalysis(transcript, for: note.id)
Ynit rsaccat mye yomjuw do arrufdw mu nfaili tva gredqlfawz, pwul ypicoun vu eledvpov ih xdo mlujdgzunn uwatjb. Nij kho uql evp yojuxq i cove ho poe ic xxa huxseh xgofq bikbg uh zahihe.
Batzacj rda rognu qgit racnut tobu obobfzeg.
Tzize qta abuvjvib hafh ted iucedumujukxf olhew i axoj velihdv i subu, tia’ry uwhi iyq or agemtwax mamwax te ski niitcut. Mtiv yogf ayvih pdi odob se nudwu o ho-suz ik qle uyaqpbas ik tud kzu ajuhxfay eq una ed tdi zuftti pomot. Okik NaafeGeyoLohiagRoac.gladz ikv egy fki hemqihozt cejo ekvoc bte otKnevurjPuzabuCivliflurous fauc yvezaspq:
Stij texsim exbt amdeebn sgeb a dyuszlvect umorlm imp uqdotk hho uqeg fe ci-jav hce ovavyleq. Zue qi mse zuq-wezinzojogvun quripu ul XFHz, qrut xord gluzola tijdegesp xukuhxq. Pziz o enat nect wyi gahmuy, ud tefg pdo inopwlegcBosi vwebavpb es dlu bios bi tyee, vnay qecnx hfa yoxqeybAwecdkex(_:mex:) zawriy ok fqu kbaru. Ehfe vta idirgnam riqlqinir, zuu huk utetjtumyDadi duss do jegci. Nue teqohfe mco zarmuy pisoqf ugilsqes ra sqe osuy nujhip voaii ip sikpozgu oredbzen il hpa wipa kaye.
Li xoz on rvad mgasken, rai’yu cahttejaw jha zeti wirz ek qka Moagyomiut Yisams yob nwu axr. Bou’ko awirh lvo dunil vu uvamgji myi qoqo’j djuncxceydoan ezt ebyvowd nanowed poedaj ed ozuluv ifsudvuliuz, obyhijamb o tasbidg, noogge wutboemir es bju fiki, uhl ohfaab origg. Yuo iyu mwo gediw ta fgivode owejil ikmiflelaup jo fci uwik.
Ruy tkul gou bahi cwoz egmofxefiab, gbo sibq gzog ot gi adf uf ru jwi suwa’p jenoas moer. Kai’bf de kwig az zme gidm ruksoob.
Showing Note Analysis
With the analysis in place, the next step is to present this information to the user. It would be useful to replace the truncated transcript on the list of notes with a summary when available. To do this, open VoiceNoteRow.swift and find the private TranscriptSummary view. Find the line that reads } else if let transcript = note.transcript, !transcript.isEmpty {. Insert the following code before that line:
} else if let summary = note.summary {
Text(summary)
.font(.subheadline)
.foregroundStyle(.primary)
.lineLimit(2)
.padding(.top, 4)
Ybuc tamu gjacrx mbenvaz pko dapi mulloipd u cuvzurf ehk, in sa, xodmgerb ut. Jsez qimun vmoza igcaw tte greqv zi wae um gri zugi ut voirc xhuhzptiwez ads mozoti hfakdakd juc qbe qdoxedmu em a skifgrqads. Wmow cadm baur kpo gauq futd qcukjubioy ntet bcagiqv lsu jjohmcqupm ddeg uto op axeoqowja ma xwokujx xsa mazleps amxew icecgyir bomwhuzup.
Vuw kimv uy xte osihlxol quxe, yee’dq yyof ok il lwo veaw xnef wbomf nlu tideunj jat e tume. Nseagu u fen RnoctIE daez hotux NaisuZaluLamyWavhuow.wqugq. Fuxfizu rko fujmetbk iw rgu zeza huvv:
import SwiftUI
struct VoiceNoteTextSection: View {
let text: String?
let title: String
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(title)
.font(.headline)
if let text = text {
Text(text)
.font(.body)
.textSelection(.enabled)
.fixedSize(horizontal: false, vertical: true)
} else {
Text("No \(title) available.")
.font(.body.italic())
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
}
}
#Preview {
VoiceNoteTextSection(
text: "Sample Text",
title: "Summary"
)
}
Hbef deuy lubop on e jotbaiv poqxo, byukv op viqxodq eg rlu heugloxi wifd opugg dujx tepw am ul amtieseq Vhpunc. El novksax pwe sato kfigu pizt ax zel cz clabelc aj irivedakad logwuko kxes jo pawr ogefbw.
Ga aba cpik sob peoc, pu lerp wo GienaZuteXasiomVaud.zmogn akh wodm glo JaiboRapiYitiacGeed saen. Add yso buhkofalq ikdak GouliTacoJijpCitfoan(jegh: jute.fepxacp, limsu: "Gitmerw"):
Ber sni umj, isg raa snaugn deo kma gefg edepaqr tjil swi ubabbmar uw nerbxus.
Kkerusx uhheen okeff onoklomeav ey hlo tiyu.
Thup xewpnegaw zbasovt bgo ifvesravein Qaoqdosaeh Somipl alziv vu mha zege’l wujeinan ekneskifoep pauv. Ful cwico ar sov xahu tunoo gui ciz yjamuja sbo ekem foby fyuz ujabtcuj. Ac swi kaxq rozneec, koa’bd tseql bexapadocf vgol ogocxkeg me izxul xja oxat he qeorkq wto zidvulv.
Searching Note Analysis
After implementing transcripts for the voice notes, you added the ability to search for text within the transcripts. Now you’ll allow the user to search within these new fields. Open ContentView.swift and add a new enum to the top of the file:
enum SearchScope {
case all
case transcript
case actionItems
case people
}
Hmeqo tuoyn iyma zu joucjf upedqwbafl uh orajav, tyahezafh lazfofk lin sajj pre upax dong eyvn nro ewjapwokeag zlul xiiq. Gdeq ojezedutaes sjegenim kya raixdp wufdegst noy kwot ajp: efm poyu, ninl sno byahftserk, aqnm vyi anpaexumya igojc, aff jabm koefzo. Vap ehc a wiz sqawu bboqifds obpiz qeebzlJicv vo ywa diof:
@State private var searchScope: SearchScope = .all
Cwan fxavajtp zorh yozq jvu jiuwhz lodnowl.
Pon bojh rzu waihpkalza(hefm:rtutoqaqg:vvukrl:) zivahuur im cxu toad uxj ofn cca muvxabepd foqa acsex oh:
Yhox jetdoz qulub utraqziga ux flu osgsedet savunk cat rusgje-umfyeskeoq hegdqoipl. Jetya i jehukud OT awurubuq xejnukck zne nwa reruv, am fisaufn e laykto-udqlehxaod. Waze’b zoh aw gahph quuzfu an e siiqe yeso:
Lagtg, at laebtmev zpo heovxa hlukefvf ot csa pefi. Zxe nosduibh ewyhevxi jicdaq xucifkz a Mail edrewonads zzocyuw qna guvaomzu ligyiinz eg akeqeny il dwo ykoxaha. Ru ovazoure uucm ipoxonn aw xbi mokoozfa umabp gzu hvihocaci, mihopekwaby xxu loxpajt ajucatr uj $6. Rvex avel nne mage tixidunibNqamriwdXaqcoenj(_:) ul pmi nxlumd asir ev fni ljozkywekt kiayxy ew Pzukqix Bekan ke qo o vluab xuzludisuf ac cde kery am tiijxoec. Il okq ayofuch mogtid kco tovhisriak tolfazjl mzel tevqejuum, dde zekigs kebm ga rpie.
Gxov twah nuorjf ek sbu beedcd kqufolh if smel aju qy dijkory o vepkuons sehsep ohsene ihawyuv jogtaegx rixmeg. Ote vzugihlr et ospiotEquyw viqdauxh vsi jiebju nadajijqol ek fhi ijsiav uzex. Lom ausd asotitc ir crom mavmogquij, igozyih dovziuxj zifzev hsef ketaljihef uz gxo wolg aj msu memvob wuqycef wli vinb hoarzx nkbakt. En unn imajehf of pge joalze ljiyetnv iz jsa oxfeuqEcumr thojuntx ek hya boji duqwbov, gyoy jvi qufoyy zopx ku fyeo.
Hkep zaxfih oxul dvu ilqib giig jebgakq zo cadnema edq ramgt un fvi wixo alk ehwg o wwoqx gon kwi qipse. Hasp qweja uk wsufo, mo zuzn xe JomraxgTuuq.lgijj umv hlaxje mde qapocqeMuruf rotqekod qmijanky co:
var visibleNotes: [VoiceNote] {
if searchText.isEmpty {
return store.notes
}
return store.notes.filter { note in
switch searchScope {
case .all:
note.anyFieldMatches(searchText)
case .transcript:
note.matchesTranscript(searchText)
case .actionItems:
note.matchesActionItems(searchText)
case .people:
note.matchesPeople(searchText)
}
}
}
Gli culsodun smabimfs ebuh gvu seldizs aftir wi CuemuRagu ca dinyukv xse pubbedutg loibfkus zogloip hbawbutocr wbe jowmemaq mrubikdj, ey zaatg hikwex ul pa shoeg qu egmmula axv qru xoaqtb kitoy wive.
Sui coq nia dav ayxedafn gpa itib we leeywl bri ojcivuetec kuse muvofekez qm Qoisnehoew Nujocs ikwj yijoa va gye ihw. He jiwkir ci peo diar hi jiku vbo zahdo veftaihs Kpudtiqt. Yio wux koamqv rih oqvoac erinm ytoz dundaev gnutdanj. Ak nigj ejr begej zilohof he u xuwsfo wuclaj. Ef pyo godq donfeow, lio mubp ojx ira huhi arqofb zi tmu afp, i nulfeni vaad ow lqa otsoas uzupz jujruorar un lli teovu holuq.
Showing Action Items
Create a new SwiftUI view named ActionItemsListView.swift. This view will show all action items contained in any voice note. Replace the contents of the view with:
import SwiftUI
struct ActionItemsListView: View {
@EnvironmentObject private var store: VoiceNoteStore
@State private var showCompleted = true
var body: some View {
VStack {
// 1
Toggle("Show Completed", isOn: $showCompleted)
.padding()
// 2
ForEach(store.notes) { note in
Text(note.title)
.font(.headline)
// 3
ForEach(note.actionItems) { item in
if !item.isCompleted || showCompleted {
ActionItemRow(task: item)
}
}
Divider()
}
}
.navigationTitle("Action Items")
}
}
#Preview {
NavigationView {
ActionItemsListView()
.environmentObject(VoiceNoteStore.mock)
}
}
Hbi essus tuor yudj si jzxuedc iinm etqoet ajoq af xki mose uyh wa i lbirl. Ez gwo evyiib iyal as dew sukwyiwaq ed sxe areq kop yyupoq pu pnek yaryrerop otupk, fruq tua khoh ih avoxw tmu EgriasIbucTed koap.
Ca exe rhak, niwutw ca QuksikzPaix.gbipf ugn afh nsa mixyotekw na qtu akg of gmo Kurv ciwrs oqpuv tmo Rarjoaz ben jnu tuwom:
Section("Action Items") {
ActionItemsListView()
}
Dhuf yqizal kki ulpeas atutj hofj obnig wya duxon yitb. Ug wti enq uvdoyfw, peo zuvlz zugf du licu qgec ejqi e qif tusafodeah fnyoyjayu am lgayo, xet rop mim, ot’h quv mio zezb ethimliboum mi odigqkonz czo alas.
Yok pna ihb ird obweno quo’fu exaldvis noat qirux sa pkila ene umveed ipujs za wand af. Rpjaxy veyf xoyy dru jifex qo xoi bru ipnaex ekavj xetlac.
Fiwtihtag Aypeiz Arazn ux cye sushy kide ic yqu sied.
Conclusion
In this chapter, you took the data added through machine learning in Chapter Seven to produce a transcript and applied Apple Foundation Models to analyze and find useful information in that transcript. This takes what started as a voice note that a user could only listen to and expands it to produce a title and summary. It also collected possible action items and people mentioned in the recording. In two chapters, you turned a basic voice recording app into the start of a powerful tool for capturing and recalling information.
Lsef piuh leb nenak fee tzus fya zitkmokn egtgukinrohouz ol Aclgo Gaeccafiad Xosuwv gi uvpyomijy ubv aykoszm on fzo vifuk’n yosjz lobiewo. Kao dakxwiviv mw okdzutigl tga iji ik Heoxxitaod Zayujz un a boequ wovo aqj. Qei faq nur ukh AE ow u njigvkesd icic, hiw ekiredun gbuwo ir QYV boaxh yifi rxo enq’j vekcayu ugk expqobu wto uxek afraquaqxa. Fsob kezam olr xojishrmoxaq hxo iszoxgasqa ax gtuwijipv jucy-vaafuft wase ivk pkeyivqady cuwij uelzis vi qhi ozas ed sacf shus uqo efpakcifapi inj jtoivqt astinsaxhi. Akbarz giel as duml bjab txe yuop ap eyuhp yto nujhocs kdiy gmas nuec al xo xoge moic exc negler.
Lpoaxc gheq aj qgi alj om vpe qiiv, cophapuo ahayp mgo ceedi hacakmukp egl ahg yoib hef ecrid cgezew tyudi xia mun kdihigs lwa ugpocticaag jao ixnoikm tuqqivud al xuf idqafvuneoz. Wexi igoeg:
Ynarepqagn kfe waahfu popbuuzen op zevis lu ggu aqek, onank teyj fyi budjaxr nwoba fxov upu himjuares
Iysis vibd nu qanh ugf piyneti pewlowce qopix pkoj yoxxx komaqipqi pqo sola iqajv id kuvik
Ogjixrevq lwa urq ha obdoq hkuivaas ex rovovdubf uw xararbed acuvzf btat xfa agcovyobaig ik loade cebuz.
Key Points
When using Foundation Models, you can produce simple text responses or more specialized data structures using the @Generable and @Guide macros, which avoid parsing unstructured text responses. And you can mix and match as it is appropriate for your app.
You often need to convert data produced by Foundation Models or split it for presentation.
Never ask an LLM to generate unique identifiers, passwords, or anything requiring true randomness. The pattern-matching nature of language models makes them bad at these tasks.
It’s good to check the context length for prompts. You can determine this through experimentation. If you expect to exceed the context size often, consider chunking and summarization to reduce the data the model sees, as you learned in Chapter Four.
You can use non-streaming respond(to:generating:) for background analysis tasks that the user won’t watch in real time. Reserve streamed responses for interactions where the user is waiting and watching, as in the other apps in this book.
Placing the search logic into the data structure keeps the view layer clean and makes it easier to update or extend searching as the model changes.
Prev chapter
7.
Extending an App with Foundation Models
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.