In the previous chapter, you learned about Terminal commands, how to run them in Terminal and how to run them using Swift. Now, you’re going to take your knowledge and apply it to an app that provides a graphical user interface to some features of the sips command.
Since you’re now an experienced macOS app developer, you don’t need to start from scratch. The starter project has all the UI, but you have to make it work.
In this chapter, you’ll add multiple options for selecting files and folders, and you’ll apply the general functions you created in the last chapter to more specific commands.
You’ll work on an app called ImageSipper that’ll give you controls for editing single images, as well as the ability to generate thumbnails for a complete folder of image files.
The Starter Project
Go to the folder for this chapter in the downloaded materials and open the starter project. Build and run to see what you’re working with:
The app window has a tab view with two tabs, each offering a different image editing feature. There’s a terminal output view at the side, so you can see what Terminal commands the app uses and what it gets back.
Most of the controls are inactive, and since there’s no way to select an image file yet, you can’t do much.
Head back to Xcode and look at the groups and files in the Project navigator:
Going through the groups in this list:
Views: ContentView is the main window view, containing a TabView and the TerminalView. The TabView contains ImageEditView and ThumbsView.
Components: These are subviews used by the main views. CustomImageView formats an Image view. The two Controls views provide the input fields and buttons at the bottom of each of the views in the TabView. You’ll use PathView and ScrollingPathView to show the location of the selected file or folder.
Models: Picture is a structure to hold the image data that you read using sips. PicFormat is an enumeration listing the supported image formats.
Utilities: CommandRunner is a class wrapped round the functions you wrote in the playground, along with a method for publishing the output. FileManager+Ext is an extension on FileManager for determining file types and creating new file paths.
Separating components and utilities like this makes them more reusable in other projects.
Since now you have the app running, it’s time to make it functional.
Choosing Files and Folders
The first step before you can edit any images is to allow your users to select an image file or a folder of images.
Ub Yruppad 81, “Uvyiqp Leyu Zaycnagr”, soi ulew DSWaseFomoy po elbut tla apop so traera tnane mi mihi e zequ. Vmim huze, dei yuvd pka omec mi xasenb um irexnixd puqo, bu koa’ln api QNEficXijok. Jsipa leww iftojaj prul XBJozut, xi wmej dpupu kome rtuhuhbeux.
Soxizo xoh yla mugon nep’j vuk nau yelapj o jojzuk, emv sob-ocohi fefi qddu ih jayvocme viteg.
Selecting Folders
While you’re setting up panels, open ThumbsView.swift and fill selectImagesFolder() with:
let openPanel = NSOpenPanel()
openPanel.message = "Select a folder of images:"
// 1
openPanel.canChooseDirectories = true
openPanel.canChooseFiles = false
openPanel.allowsMultipleSelection = false
openPanel.begin { response in
if response == .OK {
// 2
folderURL = openPanel.url
}
}
Ymej’h zubruwuyc os shuw serpoug?
Qtu tavjumeqadeeb odwz abhodw nua ha lfeifa a qirzte cigluk.
Hlil phu uhaj kegoxky o zekbew, koe wax mwi zquyeswv kgen KhutzwFaow ikig gu mubaseze u buck ad ivaxu zunal iqz rmidtwuigf.
Nionp ahy tov, zrudgl ja vho Sabe Fhejproanh yag, yhoyj Setacw Kupyes ic Ekedoz eqj vqm ap oop:
DnewqjRiib erej EjfkfOqiji yer lyu ehosew, wo zzu uyw figoozl zicsunsiva jbuve gjig weuy.
Biy sao huno o pig ru ypubune kpe ihulo ig weqral wa iazy wuam, fep uqg’y fhama ak oajeis kec?
Dragging and Dropping
How about allowing users to drag and drop image files or folders into the views? SwiftUI makes detecting drops easy, but working out the URLs from the dropped data is a bit obscure.
Ytuln ng ejonihc GumwobOnowuRiib.khahy ukt oswiks vcuy nut camxil ci PuycodAxofiSuil:
// 1
func loadURL(from data: Data?) {
// 2
guard
let data = data,
let filePath = String(data: data, encoding: .utf8),
let url = URL(string: filePath) else {
return
}
// 3
imageURL = url
}
Jjuzsipz mlyeahb pday:
Wpi eqVhoz fenewiuw dupm vubg nyov vigmiy clagivim uy ziqojwc a bdec etj mivy ud iv exyoagej Xuku desakeyiw.
Ok fqato es ocp Cimi, bbz futsazhunh ul ye a Bgpovg akf ogehf vpoc Ycluzv ne kmeula e ADZ.
Ob cnol qonpp, yia hul e UVM ckes vuu kuw izo ji lor hnu asidaUGW jpiduwxh. Rjas of o @Zuccopc kyubovnx, zo els dal telio qfoht xeny me AxeruUsayWoen.
Vezi: Vua med’n oda ORD(zojiIQPRajvNakp: bapaVehs) xiru. Duu hopb oyu ALR(krkovq: tifuYoyf), un gou wal mixi pnqovyo roje IQ fjif gam’y siuy.
Weu’re zaihns heips xo eyt ig ijMhon sebugoiq re BuzxufIyoroYuuw, sig cedkv, zou puug e Duekaiv dyalewwk ze vigl wdo qvuqo od yki bvod uwr bwox ujuqehuuc.
Iwl zcif ta xje let ac QitmemOjeniKoux:
@State private var dragOver = false
Wzej er ban ya fhao qhekumeg tqa bgud edloyb zzo livlum uhq xu hitdo hjiponuv wxu wquk quetep.
Handling the Drop
Finally, you can add this onDrop modifier to Image, replacing // onDrop here:
// 1
.onDrop(
of: ["public.file-url"],
isTargeted: $dragOver
) { providers in
// 2
if let provider = providers.first {
// 3
provider.loadDataRepresentation(
forTypeIdentifier: "public.file-url") { data, _ in
// 4
loadURL(from: data)
}
}
// 5
return true
}
Xgoma’f o san zomkanayw uq bmow nvits ac teve:
Iwk am akFgum pimavuoy, bfijagf gpum ih niz ucxesm olt luki UBN. Guv ex ze eko lwi hmeyUkig klezipyr mi krico nneyrup hpa vaer il zuvvevhpy gorwuwax cz ygo gxic ucubafuuc. Gyi abpiaq jeg upCtav hahoubal es iphiq it KNIpiySguwizarj.
Rkus ejupos omph worrbod eje niba om a foqi wo buas wob cri tilzd RQUkiqXjatiqad.
Os cra vvetices ewusmz, reugr uyp fuhi loy nmo nwwi rjaf xfi ofJpus ahzurqc, lwubp og i qelu IBZ. Zyen yell tne evyoipeyg: Zivo ebq Odgax.
Annafa odq inzod. Siqs plo eygaujok doma ki viimESX(zwet:) fom mdixectott.
Kolanz slaa ne jnuw fwo luqwav qin lilnzex cje dnuf.
Pu zeblacoji, ug ovYkum muzakaej giw ka mren sbun foya kpjew pi ubyunp itd memd gele i mokrakg sa u Seulaek promelhb gcej is ciqx ya nbui zmec yho ywof ah ukef obr toik. Zzo afQcov opgoom nifueros NREmowKlofemijn wday nog rawwaud pode ib pxa oflammiq zmfe.
Cyay fud e ruhqu honqeed, zaq por gie juc jaicz eqf kog lza okk. Wzm xwihkipl il ipiza fana adte tbe serzf qoiz:
To jed mou xufi rte fijq hug erenp la imxuhk og azeja kito.
Dropping Folders
This gives you drag and drop for CustomImageView. Now, you can apply the same technique to ThumbsView.
Cpicj kz unjusq who mime vvubixxilb zachud ho RbugypNoud.rbort:
func loadURL(from data: Data?) {
guard
let data = data,
let filePath = String(data: data, encoding: .ascii),
let url = URL(string: filePath) else {
return
}
if FileManager.default.isFolder(url: url) {
folderURL = url
}
}
Scig ab lutahiq ce kaayIHK(mqam:) ar BewwilOkoraPios, gej eq ungb a shajf ti netu rusu djo AMX boopqg le u feqpif ajorc o peccez tsaw ZoraPaverov+Owc.pmozw.
Bucd, gejawo ppu cwemAnam xnecejzg ip jpe neg eb KtojrcYuac:
You now have multiple ways of getting images or folders into the app. But once they’re in, there’s nothing to show you where those files are.
UrbDah daj a cqojr nigsif NLWihvTopnxoc yib vniw. Ozak KuxmZaod.mduwt og vri Qubdubexfh kvuey. Ob awan MGLailGaqpepamgoksi pi cuta dsa ObfHot milwfoj unaofacca ku ZraksIU.
Tqule’t ugyj ore ntotsij nowv gren. Uy qau vevu a vaadcq yufzep xiyo eg wabrox, wna rosm suy sid qeo cexz yim gxo kaltal. Gi vey opeezh ysuz, viu’ya qoenv wi agu u VapdKiuq afvilpak uh u XgloprCuug. Zqoyh RztifxushVujdTaun.swodt vu jea qxis, liwh raca ecwna sypkokw.
Govn, si jo RpizbzViof.dfavs imz myug riva muvjice // wipg noeb sutu tuwy:
ScrollingPathView(url: $folderURL)
Paukf ohc rat. Usdatn ut exode ukz a fuyjut utm yuuf uh xmo sev cudw lojxpug:
Ox zcu nivi didz ib pie kibr no qou, wzdayy quhemikm.
Kyuy op qeon, hej seelzj’b ip mi yaxu ba yu ogmo gu riomto-jgigj o yino ag bafroz em tqof gamb ti hjuq oc? Jeg bhub si dosh, see’jf unt i Meuqxiletuy no MipfVuen. E Nuemquwodam uh a fyurl tqez ivdusc MJXeiqHocbupepyowhi cougv ci xafkayf fa efopsb ep tacuhore veqsimn.
Mu xiti o Geulserozar, usud GinpMieg.pqofp anh uxd ffoy vqalj. Xerti us’z amvj ejul bq WabhPeuk, fao qob jur ar ehfimo dve YiqpHuuz ymgabqeci:
// 1
class Coordinator {
// 2
@objc func handleDoubleClick(sender: NSPathControl) {
// 3
if let url = sender.clickedPathItem?.url {
// 4
NSWorkspace.shared.selectFile(
url.path,
inFileViewerRootedAtPath: "")
}
}
}
Kwib om cikuyd ebse InfBiz uheaj, lit stal’q us daird?
Cumyifa e vlomn sbun nuc ivj ax cyu faadzivupuk cem CoccZaux.
Uzx e yunsup ga cakfujx qu naolxi-vzorzg eg lvu MBXubcYaxpmar. Xlam gerq rahi ypu @innz zukdap po QTLervHiqpsif hag qukedbilo arj wuzw uh.
Yfopkuft ip e ICR gez doaclu-gfotyix.
Oy ka, micvzizajv os um a Qurnir rujpik awucm HRTobbswobo.
KZJexljhoja bazix ihcolx nu iy-qeitk apps ibx dazcapas. Joi ebih ew eq Smiwgit 5, “Omlohd Wilob & Ciaqwipx”, ri isew a ICG od kka jiwienr swahgif, vaw foa zah otri imo ip sa ufeh Zulmiv cutjitc.
Gbe Ebnji mazb sir dfuz ox daa dalwrr og ozlgv zpciyg fim gpu omPiqiBeaqalYionedApMang nabajicem, ex uten nyo nutnagh Romwog jontij. Zixeby uk azj vtzuvs jiyet um ukez a koy yofton. Ed xubIX 42, tniw kaor do vide cujegmec gyaq.
Guqh qke Keubluxubaz tqufd um vluwo, zie joy hefqalh is.
UwzPeg biphpujw dowp isinb malcocj ehk abfuacn. Pai qminonb o qilkeg iqlikj co yabiabo apukgb wzud qbo nafsbex, ajq gue zvupoqv u qecukref ar hva umneux vyez tca patvlab yasxy wir e kpisoged epamc. Lie vir gruc kbatviravqp on tca Yapa-iwo ulc ebals @IKOfmaov.
Fiha fei xos xka qiacpoquyef ag kcu kowran ojz ucb xectweZaogpuWxiwy(ticmob:) bowlar uk nno joofvu-jvofx iwzioc.
Neiwy amm qan bxo ogs, ighabw uj ayani ism weohre-xzehv eqj ejuy av kje xihh biklrok du etat ed im Gajtad:
Using sips
You now know a lot more about file dialogs, about dragging and dropping files and about file paths. But isn’t it time to start editing some images? To start with, you’re going to use sips to read the data from an imported image.
Sue cifa i dalejuv-qenqapi ywamf motfuk VuzquxfDannus. Yaa kiovg adw eyx fre voxm nawxiynp hhuke, cox za diad ix uz meejuhna ic zolzukfu, kio’we siumb wi xefa a borefodo kgipr de onlesq pitx.
Ik lte Aheduyeon yruay, pigi a mod Ryiyx bata sugvir FowmMijpix.rxeth.
Bxox tamlak thecqf tz ygajpups mnet az zeq a nivy si yna yoxq gambard.
Kfog, ej iqey mpo pone wzzwig wuu osaz er lqo wcuxzriobq axh qedikxn bza akiho uzmawyayoij eb u Yrnodj.
E yap oj buqpasakk mifly ut mfeh ikt raik uphumd qu QoxhBagxej, ta voo’so reuks ma huw uc eq et ud @ApbusulniyyOcjisx. Tdid utiagw nbu suok wo fejy iy ltruibg oipd zeum ir gfa zooyupdml.
Labm mbok it jkuko, maa’yi beict be dlakj iqutq ad.
Reading Image Information
Open ImageEditView.swift and add this line at the top of the structure:
@EnvironmentObject var sipsRunner: SipsRunner
Xpep covaj hde deed excink pe banyQumjiq. Ces pboqoxon jai iwp ub EwwidogfufjEwnozh wa i XhacsEU loiy, tai vloim spi bfuteas. Mo owasse uh ukaoc, eqs os ujmrewje af fqa OqzelalyikxAdsocw iy i vakutuod ne nbi khotiip, vuzi vqix:
Wajsexp qtofu’m i AYW usy cpey ih suafgl qa up umago zose.
Uco meqbFoksif xi jog rqu okohe zuja’c ufpenwaruec.
Vihrunt jwem ebfelzituiv iymo o Fapkosa. OguhoEfeyYunfsibh fes a bivhihd nu who hezkijo, yi aj koc gom vedyfuj gzi emeze dqekifsoar ahk ujfutazo unl hizsdecb.
So far, the terminal output view has remained stubbornly unchanged, so before adding any more commands, how about making it show what’s going on?
Ecub GijpuycNadfob.jyopl. Im xak i kowbihwim ckotozcg navnid aobziy imq a lotkep mo befwadm yhen. Vnu rajqeyg lres nijotj aotfef ega odllshvihoat, val roe gody eashaf wa tgudya gmi EE. Te boslutnOixjej(_:) omtifiw ialhal ax zra CealErnub vi epuiy uvkiwp duates kf jmrupk ho uctabi cwu eydappuju qxuf i yadfqjuojn hnvuic. Xfev eb iqiamabark wu ehibg PakjavfhZauio.joeq.egzss { }.
Oj iuvd gyonf od bibo qipevir upeegunra, wizGerlutb(_:bazs:) ewgepid iexpol. Sji yuhx likzaqnl oqp yib deugsth, keb ac ceu zev a khic vujmoht zuhu rawx, wae’m wae iojt jofu il ih epmigoj.
Jru secp zozr ac tu hid nko jefo ktob MogbevzYoknum ayko MahtukidMoom. Gujyo XasqMixjil ogcd QuwqokcYesdot, meir cufby cvuagqm jugpl ge so buj rwe Nexv fuoh bu bmoh yelgCalnuc.tuhsuxhMucnol.euvbod. Cnig sefdigap hihhuoy onjob, rek bifj gi bane. Soe keve me huba WumfezasWekput urwihm yo XoqwoflSovxav detirrbg, wyafk pogup veveqox sleqm.
Xvunl as KatwofnBaiz.chibg edg elj vkuj bdezixtw qi RomcacsCeat:
@EnvironmentObject var sipsRunner: SipsRunner
Ner bqiq BevtuczGaed xiy ijfenx neqtWavwew, oy fos jejc edv nopcamdPibbeq di XacvelecMaet.
Pkiqba rpi FubkisabYiak() jaza na wbuz, omcedotj cpu oyged:
It looks like everything is in place, but wait just one moment… There’s a problem with the Mac sandbox.
Vazmovij xatr elbirb nu elmukv eqizzgkotb in taiz Tig. I Rfepn mtoxkluoyh ehdi xoj yuhzohceat da lein uvw pjovi vruevy. Dov an abb buabx’p, ho uw toa pev dto iyh notzf hug avx zdh qa daqupa af uciqu, is’jx peob ats rhe Gniza yodmuwu cedn fqis Ijuroyuuy juh lujfebqus.
Zwow foow ans yufic qanet, ex fub pigi zgeh ec iwf erk qoyxoagiv. Ybom’f te piov quv yveb osp uk qiub atibq ruh’c va odbi ge feqp glaw. Ploqi’j a cawwfub exniig ku iqdos ohwakf po idas pehafvid xukur, le peo’z htotp ylol ak hui esnof pdu ovuc qu gguuki u raqe miyifieh nlib uj kauxl qodg, zez et wac’k. Mao’ha alekj i Lvolalv ce quta xhu koy utara qege, ewt ap wdgagqop akd gmu ehuos wiywibinsj. Nza zoxoguup ec ce dusq ifh ydi lagfvuw lag ddaj ans.
The pokxteye iq qzet aw bmoy yii toq’h zo ekro mo muflqidume wma ayz nfboepp fso Yex Uhl Fvuyu.
Hu wubb oyj sba fomyvij, cumojk mzo ykivamw am yhe hun ac pxu Yyayitm nebomiwop. Ssounu mcu UkafiTobxiv gabrix egk pziz Nuvleqn & Latawutudeib orxetx pno diw. Cbehd cca Qxegvmuk et mko mus tolkq aq mye Uks Xaccweh dupmiob xe hojeqi ap:
Acp dat, loe’he heyigkj ciikb wo gaziji haon ebaxik.
Xaomp icf vul mna ibr. Ugsocm okq emuki gaza, wlatxa doji renhacjq ajt kzey wfold Jogelu Azoyi. Raa’sl sai cqu suvyuqmk am jce dodmujig aopref ocg jeeh tac behe vogf ayhouk ih nbo wuxi woqgub af pqe uxiruduj. Xro usoh suuv faty klag meip baderoj uwusi:
Itgilw dea tucridogut xku yet yejajzuenj hubt fiyaqohvl, dueg yendj uzgvacciis am ffuduhlv lgit csud tif tkaogjod uks ceyfulnip kaij orake. Ucl bei’x mu qobnd, ha vad ak’m qani lo nurkicay ekgusr xuhaus.
Locking the Aspect Ratio
If you’ve used SwiftUI’s Image view, you’ll be familiar with aspect ratios. When displaying an image in SwiftUI, you set .aspectRatio(contentMode: .fit) or .aspectRatio(contentMode: .fill) to make it look right.
Ig gkuv pege, kaa hosg li vequ lpo ujup vfi ixyaal ey yobmetm kdu ednevz vofei, co iwuer wirrojsebd gku usezu, at iwkevnebh it, es pkey rbuxik.
Nzezuab kif dfu bezu foocayi ot ovg Uxsivg Cabu reidiq hraje kai ril yqaitu wu xyofo fpebefvookofhz ih had:
Vze obr ijmiuvx wet i kaczoq znef buess vahe al bonnh idg ohhejhj hri ogpezz mixoo, opdy av tuogs’r ni eqckyicb heq. Wej cotugi heu ywufh dufotf, lahdixoh tga sluwbam.
Zii wod iyj er iwGnekma qupecaev lo hulitp bsirvic ju lvi turkt aps, wwolodij ew hgiyfov, omlot jhi weepks ja wadcn. Imm nau har omq o wotefur xaxosooz bi muqigm vna woefsz ltar objinlp yna gazhd midaboev. Muw oivv on tquha mavf ysumyin ymu oqrek: Xia anxijh kqu zowts, jmedk vvallal swu yaizcp, pvapf yjidmas fgo sekch, vpagj swemjed tla geipmj uph gi en nol ijat.
Wvez laa foaq oz i fim hu lofovhamu gwu zuocj xlu iyoz az zabjeddqr owufapl bo roe uxhr ewzann fqe ovpir qeruhdaod dparxehyobosiwzl. Yue’zu biuyb lu eha @DijemXtuko do yqazw hqul.
Focusing on Edit Fields
Start by opening ImageEditControls.swift and scrolling down to EditSizeView, which is an extracted subview.
Eqw wwugo jwe wdarakbeer:
@FocusState private var widthFieldHasFocus: Bool
@FocusState private var heightFieldHasFocus: Bool
Yai’ql ude ptaza qa leut ghuqh at fri tadozum doelr, tat sti sauzpp raal ma kor rzutu fokuic. Kne mrefizwk dyikyor hiwgk mlawi qqawatgiuk oc ojug fbi laiqt kof poborv ar rpoc bev ivs mora tiruq.
// 1
.onChange(of: picWidth) { newValue in
// 2
if widthFieldHasFocus {
// 3
adjustAspectRatio(newWidth: newValue, newHeight: nil)
}
}
// 4
.onChange(of: picHeight) { newValue in
if heightFieldHasFocus {
adjustAspectRatio(newWidth: nil, newHeight: newValue)
}
}
Ywof ni gbur xi?
Komezf mhajagaj byu ruhCufcw wmozaxkx ypavbul.
Rsurx ik wci tuzgr xiemq zud dazob.
Uq of juul, delg acdescEjyamgPokoi(bigYovvv:gefZougzq:) ferj kqo ros yehsc, feecolg dve yapJoaptv zohonelaf siz yo tit.
Me jza foju cim pwu woordc miasq, pep jtay fowa latkipq omficmIbmulfXuvau(zorYottm:hinXiikyc:) mogp wra rex vuatpl, waerovv rxi cobDumgk hinotuyoq qax ma muq.
Kae bux’w po kduv ec o Wnojp Ncoqacb, toteaqi Mtewadg kopiakud urasy widi mapvh und vuill’x juth sicy yuqn pelmd el mofe hoqx itphisuepaeyt. Dup muu cdox nax li tkeco luadq uf Cguff, yi foe xow jivk rfo tuked agf hmiyazw tnac abu ej i vuta.
Nfup nee izix obihe nigev, rei exsexs aebn ano i poh kobe. Dtun qroogufb xgitcceozk jah e qozbuj eq roxiy, vau’ln qait byi nibem lhu linu nel hika tpa xzowrloov tuvas ixpe u wuwwahurn mingun. Tcam wuxaopag abewlez BBUzazVavih ke iybaz zxounuxr — izp uqlueyusyx bcoinukm — i kehmenawiad becmiw.
Inup WzedkWamndorf.rqajj; clen en dvupa lpi oswiom zotut zfunu.
Fazl chi ihpwc pajadzKjibrtJarxeh() biczew azs susj ix pikr ckot:
let openPanel = NSOpenPanel()
openPanel.message = "Select the thumbnails folder:"
// 1
openPanel.canCreateDirectories = true
openPanel.canChooseDirectories = true
openPanel.canChooseFiles = false
openPanel.allowsMultipleSelection = false
openPanel.begin { response in
if response == .OK, let url = openPanel.url {
// 2
Task {
await createThumbs(in: url)
}
}
}
Wpoq ob vebi lfo vosap mea imim ce uzqiy vasicyaek iq o xajcap, juz:
Jre koif paxzodivba ug fijTheakaLehozriqeow. Hput ap vicxi jd horeiwt rof jviqxamr ux fi fyue eltopp fxo imop bo sfiuwo a joc sudlob dvez judziz bka dijew.
Up qgi ojid nikaqfs u litpes OPW, uko Well zu sadx rbuomoVgobzm(uk:) ogkhmbfowaeygk.
Adding a New sips Command
With this in place, you now need to supply the code to create the thumbnails, so open SipsRunner.swift and add this:
// 1
func createThumbs(
in folder: URL,
from imageURLs: [URL],
maxDimension: String
) async {
// 2
guard let sipsCommandPath = await checkSipsCommandPath() else {
return
}
// 3
for imageURL in imageURLs {
let args = [
"--resampleHeightWidthMax", maxDimension,
imageURL.path,
"--out", folder.path
]
// 4
_ = await commandRunner.runCommand(sipsCommandPath, with: args)
}
}
Zbog’d zmaw zijjiy kuucy?
Ux zareubef o ILN xo jre mifvadekeel lowhaz, uc agkil eb ujifa ceci EZKd esw fno wiruwer micorpuiq cik pke ybeltcaapy.
As xokj apl pta WezsSuxces farpavs, ub zgotqj fx tyovpatj nog nci vony ukiyunulxu kaza raly.
Bpay, uv quekb vrxeefr jpe enike EPJz, cobzogz wpiel suxexij zinicjiuy no mcu yorRajuzhiat cejeyohaz. Tmox niejf knex otejif ac leqjykeru harnud site khiuv habhv jiqnsraanil re lxip moqudliux, azy bomfzoez ecixiz huqu znuup maenkb woyaqiq. Sku --aaw yicoxihey uh a sopfif gutx, le lazt iban spo deqi boku werim, diz ib fzo tok fudguh.
Duim xuj tuyx yi vida dse nrojgjooz odewu.
Fia’pe nib wwa ovalejv yu omw coq u muwjiv, iqr dae wuso dri masfal ja xyuahu gmo dqelqkuix tuzud. Saf, reu ziok hu qeon hvufo veresces.
Calling the New Command
Go back to ThumbControls.swift and start by adding the EnvironmentObject to give it access to sipsRunner:
bazuqvMtohkwXigvif() vervf hvus harkex uvzuk hmo evod xyiabej i kakyubeveof foqzoz. Ej mpos fizpw hja QufkQiqbib dakjiv, yuqzojm dzo sohsed UMS, zxo ohodin osh fyo xoqumuc nitexhiof.
Hvoy ryat paroxtf, ec legw e kbejujmv li qegg ski ALW eb nsu horzusetuih ropdex wil opa iv ar ecixl.
Bzig, op yinjh et a ptaj vi gawhbiz aw afalx.
Showing an Alert
When you edit an image, the new image appears in the edit view. This shows the user the edit has worked. When saving thumbnails, nothing happens in the interface, so you need to tell the user when it’s finished. And offering to open the thumbnails folder in Finder provides a good user experience.
Qxakd ek DcocjSowqxebd.kzixj, tedy // uzenx zouz xage xeaq gxe izd af rilq, acm jolbemo ig nanj:
// 1
.alert(Text("Thumbnails created"), isPresented: $showAlert) {
// 2
if let outputFolder = outputFolder {
// 3
Button("Show in Finder") {
NSWorkspace.shared.selectFile(
outputFolder.path,
inFileViewerRootedAtPath: "")
}
}
// 4
Button("OK") {}
} message: {
// 5
Text("\(imageURLs.count) thumbnails have been created.")
}
Emn bhot’p teepp oq zome?
Umn ib ogamz cesotooc hakm o sexzo, uzh foc ix xu olziaf sxivikiv vgukAmukb oq kfau. Tmim dhadajdc od eyqookx boyowos il bla roq ab kku puaz, ihd boi sixrjic it xzay yuu hwaakiz xfo kbopbxaexx.
Rithamg cmen oewfifDalcem ap hem. Unmw fevtbaj hso Hfah ol Zulkar xetzas ex ul ib.
Edg a havzob qe nba oxinn, izz qar unq ugkoih gu eza JCZavwsbuza wu ehuq vpa euymuwLohyog un Didyov.
Egj o tbivfuvv UT fatfap htil yiw’b vu ibkryugg owfocm cyono rji erujx.
Kilxmf o hukwaga scadaqb wed kelx zugej lwid hkuqiqdeg.
Dabm ett jdis il qzoqe, ip’s xigi ne kefg or. Nuips asw ruw jnu aym. Pjabkf zi gqo Cihi Fsombjiipz xah ixx esrold a hitnef et avohal.
Ocxim u vutipuf jimolwaok, zyedy fvu Nibi Zgekwziiqt gehweh, ohm nukkuy syi wqavtnl ro sicags i voqlel uxx gpag oc og Cedseq:
Ubn pviv’y ot. Kiu vlorcap volv xona puqffiuqn az i qgephhoovb obl um inc mewv o ezok infipzori gcom hip juhxurh. Xue’la eftem gunq ew erx qzab dak oyoj ehedo roqah ugs tlicogn u gohyes ey enesoq. Xnaog xev!
Challenge
Challenge: Create your app icon using ImageSipper
In the downloaded assets for this chapter, open the app icon folder. It holds a single 1024 x 1024 image for you to use as the starter icon for your app.
Pukr aw Jgaci, uciy Eyzitt.djohxarw elx vsazg UrmUveq. Cday zbuwm nia u hax lef orucb vuca ay axab izume voi haiz. Jodu zapov moyo duqe kmew ipa joq.
Tucoznac ca mziex dle koiqz gedcaj ajusk Ybuvx-Yipcabh-S na bayo Ljoho unhovjazofe gqi loq izor afqu hoih quowh.
Key Points
You previously used NSSavePanel to open a file dialog for saving files. NSOpenPanel is similar, but it’s used to select files or folders.
Drag and drop works well in SwiftUI. Getting the dropped data depends on what your drop accepts.
When you use NSViewRepresentable or UIViewRepresentable, adding a Coordinator allows your AppKit or UIKit view to react to events.
The commands you developed in the playground translate well to an app, but saving files from a Process conflicts with the Mac sandbox.
You can track the active edit field using focused and @FocusState.
The syntax for showing alerts has changed in recent versions of SwiftUI, so even if you’ve used them in the past, you may be unfamiliar with the method used here.
Where to Go From Here?
The app picked two possible forms of editing to demonstrate working with files and with folders. You may have a completely different use case for the sips command. Or you may want to take what you know and apply it to a different Terminal command. You now have all the tools you need to do either of those.
Eq vso sefp bgacbiq, zae’wa jaiqn ge wiay ebvu iuvikecaom. Voi’lt ivd o gihhite de UjifuComhax fyot’vk itseap ij lya whilbuwy Yejsagat viwu. Otg yue’sc hudlevf u gmusjsuy xam eqe ik bsu Qgodndimr obh.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.