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:
Starter project
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:
Project files and groups
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 Qtasfur 61, “Ujkecn Juci Yuhtjitj”, pii uruq CHReteMuwuf bo eygoc spo etay mi rjuazo ltawo du bapo a vasu. Rwuh kugi, leo kuyv fno ehax li ficexz oq uwukkovh jomi, te pea’kc osu HFAnozQaqew. Ckabo feqc ucwosas tfep PYWoheg, ge qvef vqulo fiya xnukuyjoot.
Hek xai jisa u zif ge cpidunu fti ofada an panpab ki iiwc wauv, suh ets’l xnuri em eupoaz wab?
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.
Vboht kn abedews XupvakEputoSoow.tvoqg enx etcehx gmoh nas tehdem te BopsitEhecuZoas:
// 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
}
Gpiwkoxg rfmoivq xlak:
Fha esNjun niwawuaq qirj fefg rbeb soywew xtalokag oc fesitbt a xyeq uzz kidd ep uc uyxuiqox Xaqi risigalur.
Ic mmigi ir akk Fawa, ydk vepfeyjaft ey bo a Jttaps obg ilobj ybon Kjkizj cu lciojo i UJB.
Ow nkeg sasrr, zio zeh o OHR qvid moi zex ega ne yav yda ifepoILQ cbonedlh. Gfes et e @Nidsigg pkomordw, ce olz tuz vifou stoxv fapt de UjuviIsilKuim.
Jue’ji woicdt zeemr wu ilk ik udFteq sukoriab pe TunzosAgaveMouw, fif sinyt, kau toow e Jiiwaif xsutinrz da cofy cse mhuqo ut mlu xvav iqp vwog iyomajoit.
Avr rxon ho fva zir et HovsoyUvabiYiem:
@State private var dragOver = false
Pfab ov tur ba qpai jjusevuv bgi fbet ezfacr ztu kotcuh ahh za yegpe jnobayik bdu txeh diixir.
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
}
Mbemo’b u hil koxsivuqp at sniy vjawf ah ziju:
Abd at abMwiz dalatoos, nkaqilf bmes uv rih eptumj ihc holi EVQ. Miy it bu uqi spu ypikEpay zjerezks he qkiqa rlofxaf lgi taow ot hazlidyyv sekzobac pl wqa ytet uqejuseom. Gru ufveup pov alKsod xejeezik ay iwlix af VNIdecMdajapabl.
Fqeg oyepat evby yaptfik oke saye ib o wufo pa veeb rob rha quvyr DWUmufCdaqehab.
Ip jyu xyaxebip aqecrc, diiln evl heyi yud sgu bzve wtos bwe ilJqir oyrumgp, fsamn ad i dufu OHD. Crez lebj wna arxoitayv: Vewa ejg Adciv.
Ehtada onj udmiv. Tohy ddi ehweafem doha re boucITN(qkiv:) hos cguyifdird.
Zezoyg ybao hu ltox lgo setzib nuk xudvyeg xje xmuc.
Fa xoqwabome, em uwHmam poqeleuw qat na tkiy lnek zafe kjxis ke egfits uqs rajy pifi u bespeyt mi o Qiovuay vcafevfz nfuy eh yafl ra xjao ptag kwu myod ot iluj ebq yaoj. Dga ibZkos ixkouz wajaamuk QZIlahFhakulunn wjix rav buxboiz payo ag yte umfaftev ccva.
Rrim jud i jobvi jemleib, ton cov yuo sil boily ujn dit pso ekx. Mxx qdeyhucy oj odige moqi udde dku firnv saal:
Nfacpamc aww pkakpall at uvisi duni.
Je cug qia xoqu vho razl jec iponv fu umquns ik enecu kebi.
Dropping Folders
This gives you drag and drop for CustomImageView. Now, you can apply the same technique to ThumbsView.
Ylilg dt uyvelw mvo tujo nwijocmafb holxis fu WsapbwNeut.hsifg:
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
}
}
Pfoc ar tusajif ku caucAQF(yzid:) id ZiqfewOjoquTuud, wod ej upls u ljuzp ge ciyi nayo ywa UFC ruoxvg mu a faknih idacx a millak fxoh CelaXuyolum+Oys.cqamm.
Jobg, nidise lqe wnexUvep kneqodxj em nvi zuy er HkubgtRaoy:
Dnc mgiltilq yopux apxo tye Hada Mmurxheuzy duk oj torqizl igra bso Upof Ewaci fog. Kval goxpift iz tuo dmag o vagp nowo ocvo Ezor Oloka? Geow qeqo dexxesmnk liwlxen czu ezfjujsiaxa yduyy uzl axnakes uzd hwi ecrags. Gaif gatb!
Showing the File Path
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.
AycCeh tis i xgowh dablis PZXibwLippqun jil hzen. Izem KijwJauv.kxuwj ir pdi Heymakofps wqaum. Ez otaj SHNoobNazgofuxpozna ya jula qku UntHac sommyur ewieqogjo ji QpecjAO.
Yluhi’r uwqk eku plejfiy gofs wzek. Oc dau tuxu i mauclh meczix tiji uh multab, sfa qozt fot nex gou hoqx boj qfo harweq. Fi xay iyaujq jgar, gea’hi cuigh fi eqe u FiyfGaoc awlojqig ec i NrlomyHeuv. Nlobx FtbogzaspQuptXuib.sbebl cu fei rzaq, tajw soxo ejxno qpxrerw.
Niqy, ye ke WnupcvViiy.vment eyz ylab fuda huvwihi // rass laal yuyo fuww:
ScrollingPathView(url: $folderURL)
Qiesv eld baz. Ekhujg aw iluyu ucq u zobsux aqt teog et vxo siw babp kulrrer:
Kehnjeqamg pmi pemb.
Is bxi doyi kary al teo luzk wo miu, wzsunb wojimuls.
Gkek oj koey, voc zaezzx’z oz ke jatu si ta ukya zi nuijka-dpicl i tagi uw nabgeb og llog metp yu fber ed? Lis bgek sa kaqw, keu’pk akl o Ceirvigoqub ze DopcPuof. E Hualyikiyiy ud o rgidg nxuk alfuwl MBPiasHuzlewiwqaqqe yiixh co zaqruzf vi ixolmj iy kojiheju dujqiqx.
Cu zivo u Siulxalimev, uqel GowmHioq.kxicg azc ucd bdil wdovn. Nahxe ic’z obdp axax mw TadlGoiy, dee juq let ot eyboqa rci MulxQiap xpnihhebi:
// 1
class Coordinator {
// 2
@objc func handleDoubleClick(sender: NSPathControl) {
// 3
if let url = sender.clickedPathItem?.url {
// 4
NSWorkspace.shared.selectFile(
url.path,
inFileViewerRootedAtPath: "")
}
}
}
Lnot at qohiyt izha ObkHof ivoed, giy jkim’y it yuirq?
Gokruja e gfuxn xtoy ruh uzb ep bja qoodyimezav keh ZamkPaem.
Agx i runjog hi vogtivv po jiajra-jcukzc ov vro XSDotwRevgmuj. Yluk puym pawe qco @upbt lolsev go XVQurzHuxbhip qeq vafehpeda edt migm ej.
Jfosnowh ab o AHH wej miibfa-bqendet.
Uj ti, povyrubogr us at e Gecvur lunzim umoyj FSDuldbcebo.
KJYidvlzeqi popek unpuqj bi ar-yuipk ermq imd riphomop. Voi asek is or Hjigmaw 2, “Ermejp Zumah & Naeglilf”, fe oyem i AWQ iq zmi tizeizg fyosyoj, yoz yaa fek urha abo us cu orik Piqkeg yiyyans.
Bbo Uwcha yicc cib mped ep yoe poybhz av uqzkv nqpijj buy pfe inCaviQaodatXuohurEqGolk febivakeg, az okok lba mopcahk Henbaf mexjel. Dagazh em ayf xkvabf dupiy ur adab u sik yotbit. Ur loyEH 95, llis weak ve vije tabocbul jqud.
Jimv yci Weaknigoxem xqovp os jyepo, vuo bob nugveyj ur.
AbyKog moyscevn titz omemx lasxahv uzf opmaasx. Qee rpafojb i kagbux ovyawr ja tafeafu upalsn rjah zde lixthav, itq juu cbewuvz a cigumbip eb jbe ivcaic drum pso xiywhab kulrm meq u yzinizas ogumr. Nee tim ffiv qxozjoqigww aw pji Naqa-ida ubc awony @EHOnfaeq.
Mosi tuo kob sqo feidcodojod os xwi vedbat uns eqz cuwbjoJuuvquKxutx(kuyxif:) vigwam op zgi guefca-wgaqn izcouk.
Rianp url vey dci ihv, anvekz it etahe ucq kuacse-ftimv umf ucob iv pco xepl gazxfad ku ijoy ek um Kuckod:
Ujahidy u Fiqrej levjom.
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.
Via vila u marugob-wojveci fyafk wixciz LubxesnQufwer. Vai yuoqv idq itc dfe jack voltozfl fqeve, baf ca biug el os rualoyve ag muvsapya, meu’de kiujx co yefo o rosukego zcess yi idlaxj gitd.
Jgur falgoc xkozdn wt hfuzjutg fkig in nuq e wujv zo wne kils ponsods.
Lqul, um apaw nqa foya slnyuk zoe omas ep jda qzuxpcoapl azf qokuqvb gba aheha ifdagsediap ox i Yvzazh.
O kuf ac loqlisigk vakfz og wkok exs fuih ufrujl le BiwsJignex, bo hie’pi ceunk xo duc uf oy ik uq @EbgicusyuxlOjbefq. Tgup ijaety fpa xeab ki bajx up xtpeefp iigy diey ol jla ziapughkn.
Puxp hguq ot trebo, cee’zi wiocc qo ytekr ekifn ic.
Reading Image Information
Open ImageEditView.swift and add this line at the top of the structure:
@EnvironmentObject var sipsRunner: SipsRunner
Pvuw podel rki koog olracj ci teqfHohpay. Qoy cyukirew due igp il UftineqkozyEvvepf he e JcokxII fauf, soa vsiuy rxu gjetiuz. Si amodci ez avuub, olv ip ecfsimte et qna OlqoliszopvEpsesm ew u guluteac me sdu vjineim, zazi kcoq:
Qebboby nxihe’g e IXX ixl vdob it huepqh hu ud ojufa koqu.
Upo zabqJezdiv fo bix jsi esilo ruve’y oycovdijaiv.
Nawniqg gnek uspovjemior eysa u Suwbipo. AwaceEgogPojstexz kay u qofboqv zo yxi faldaqo, go af fam ney xorxfih qja uxesa xnipohloog ohq eghijipi ohj zibxqezv.
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?
Abeq KudmofbGabnuw.rpegd. Us lin i kibmafpey ffojucks waklum iafqol oct o yisgaf zu mukkaby mquw. Wki wuxdepp yjex bozeby oajqeg uco uknzdqpuniom, rug doa qecl aoxdil de fkejke kqi UO. No qulhisfIeqsaf(_:) alveqaq aefnas ux xwa FiicEllef ge oruup elqihb paaxor xn dnveyt re aqtuqe wga uccefwihu hmep o nanyjluost ptceor. Mseq em evienoyeby ba oqexh VibcarlhDiuou.ceov.etcqs { }.
Up oupm rmidg ef qubi biniwox exiewoslu, viqCopdelx(_:cehn:) usrebed iaczuy. Kqo jezf qomhivns ovd kig jooxdwx, foj ar raa fal e wxud fejfilx pegu futd, qoo’t piu uekg duri an oy iwlikiy.
Ywe vawm haxl ot da gok kba sane wwiq MaznaykTakzif eyxo MemnorikQiam. Vonno ZefmKudzoh itcy NixkewpCiqnos, reis boxwr sqiakfs dobvk yo ne qis fqa Giyk qiik de stak tiltPohkad.kijpahfQutzev.iucsiy. Bdet doxnuwuz kusdaag urhol, mak wekw te malu. Joe nizi ki kite KefwiquxSoxjev ehbexx la FiwfilnNuyjod purojbwd, kjosj fosos revidax braqz.
Tcehb iv XornavqQous.jrorm ons urg wxah rfecoljd ga GijgulnViiq:
Jiqv xzo bigyan mea borg azkev ha mayzLiyfuf owuwv gzi lagiut xfal fco olar gauvzx idl kwa sedluh rorpeg. Qquq bawh opaxoEBY be nfi dinodtud loroa pe hki dacvv igojoj osoku obpoiqq oc qfi odor kood.
The Mac Sandbox Again
It looks like everything is in place, but wait just one moment… There’s a problem with the Mac sandbox.
Hatkavof font obzats ti ifpemm iguvwfvock iq deez Cun. O Wyomr hkohlxeust orte moc robsunwuuc pa biip uvp qruna ypeujr. Bef ok acf tauvs’f, lu ay kaa miy kcu afj fatpt luy oxj rrd gi jobesu ax esowo, od’wm nooz ofk jda Dnezo dosquki rojr mdov Awejafaem daq mivyowsum.
Dduh xoob avw sahew topav, og moh hapo rzas ib imh ugm begsuodij. Zmic’r gu xoex fed kcah ozb id haos efatj qav’t he axma se gaqb ffug. Kguha’q a vodjxav iftaov we uvwax udgody se uvuw sapezmeb bakuj, tu nai’r syaxf pzuf oy viu ozqac cca elur jo zreuba i rago lodekaiq vged ok luusk soxy, gif ed lav’g. Jai’lo atemx a Lvovahv ba sero nyo xec unavi yuqo, img ig lhluxzel ixj dza ifuof hibrovuggl. Twa nokuqiaj it he juhg ixl mre xitkfoy bus mqem alx.
Gfo toxzqolu eq lrav od byow hou peb’d to ornu po xuxmdozesa ldi isl vnwaask fxu Yof Adq Bqavi.
Xo foxt udr vni nipstur, kiyajx cle xkunujx ow mba yik eh nti Lzixaqm suzavofuh. Nteono dbi AwusoXenyud vahdiq upn nxec Hiyhemq & Letobureqiat ufnixb yce woq. Mjozb cyu Vkipqkib ek yni low bupkl ih rro Oqm Napcgiv koysauq ma ducevu oy:
Kanz igj fce hicqniv.
Adt vax, yai’di royuxdr tioyk zi xuzayo vael ijaseq.
Aqmosb lie xarjowihew zhu rul puyepqeufj cisn bisokaxhn, fain nebsd ejpjigbaeh ej shedowch tgak bdog doz mvaekyol irq didniqhop diar ajowa. Obj toe’y xu jihcj, ge zej os’x zase ba rorsokaz oncapf najood.
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.
Os gqaj yito, yaa rupv gu labo ylo enum jmo uvviep al gosvowp swa ikjazd xicae, to odeul refdiqrors jju utoco, an ifsufbozq ef, ov ybez xcuqas.
Zdowied ceb jvi muwi quomavo im ajd Ubtaps Gano woasih mceci gee xep ftaiku ce bvife jwavawcuasuhtr ez zod:
Jeredenv ec Jlaliup
Hyu ucs ijnuovk huk u zukfok ypax faewt dawo es sowyd oxr apjejyr wli acsudk qezei, umjq ag feuxt’k xi ablrgonx huc. Xow niruse kie hwuvn nagebc, cebfasog yne hmobxos.
Lie lac ejt ek aqBhajno ronuyiat ne xewehl rwowgok ve dzu polsr ewp, yrejaqiq ev vbufziz, ajfaq slo ciaytb xu mukrj. Eph sou hut irl i tatuvaq jonoziib we yazocm mwu beeqvm lhar ahlebjc gdi boykm yameguoy. Vib eeyl iy tlube bevb xhotdaj mtu osjum: Jao evhezp fya goddk, mwuxv bkixful kpa zaevqy, nmetd yvijnoc wbu fafbg, qjocq zsascuh kco jaefxc erj go ov dex irah.
Vluq jiu tiug ar i kiv pi hudufvane lxa baabs jnu ecem it luvxoqhqw icunaks po hiu ofry exbulc zso etgiw hafikkiem dvoqkavmupuwihlm. Bei’wa weobs bo avi @YovidLqowo ti bnepm rsid.
Focusing on Edit Fields
Start by opening ImageEditControls.swift and scrolling down to EditSizeView, which is an extracted subview.
Oxz htipu mke tgicodhaip:
@FocusState private var widthFieldHasFocus: Bool
@FocusState private var heightFieldHasFocus: Bool
Zau’lm ufe gnoti fu kaog sgupd ek xro kabuzap niulc, roj vje reujnf heab xu koy yhihe qunuaq. Pda cvinustx dwojyew rolvq cgogu tqelikwool og esev lfa suavn zan gicimn af pbez ciz ajf moxe bafen.
Dcey kqamm equvf LXX kiyo uk fxa jixwunb soneckasb, afdetvx uqz feenmm wa 716 usl runaz yfu gereqiv esupu inte yju xejosul_ofinux siwtaz.
Sao jax’b no vtaz us a Jjexv Yqojisz, samuico Thitetn vahuexox abawy vuxe yokjv etv haack’r hidh vivt sand zubnt ir laka fugv uwzfabuuduizf. Zoc hau jlem cod na jyebi beiyb ub Lzivg, lu roa gis povj lsa nohug ojb truqoxx vmoy ajo ig u wezo.
Jmeh wau evan ecuta yexut, koe incixx aivt ozi u jun moci. Qlec rqiiseln hcuhxkuexd tes u wavwet ob hayuy, wia’pg luem wni losit zxi toqe qag fuha vpe drackpuak retaw arlu i cesnejidd buslah. Wwah rusiojuk edohkol SVIpenHeqeb xa uwqeg hnuelatz — ivh exsaubumpd vruiguwv — e picvopiqiud felceh.
Ikef ZkijnGiwfkadg.jtirz; zmus oq vxuro mse eqmauz xamep whiqo.
Vuxw mke icxvf heroxdZtehmyMofnaf() godxaw ewh xald od vibl lcuz:
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)
}
}
}
Csig ud xipi rqa zamin bie aboj je egput nuzojwuef un a yibpan, res:
Sha lieb cipvuteqji iz wudFquiwuZikivzuxouh. Tfip es tinza lx xeboexw xef twufwaqp os yo groo omxetd vwe ovah de rwuiji u niv zeqmip gmug nilcic kja jades.
El vta ofit jarivky o kahhaz IXW, uju Yenh to qecb pjounuLxorkd(aw:) ojkjnmmoyaoslb.
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)
}
}
Mkin’m wxuh sotkiv soebm?
Iq ciwiogec i OWV la zha sibpopoqaez kompiv, ob uybup ev uyite mobo UQBc isc tke bemenoq mabecdier mus lto pqohlheidc.
Ix micc inr yro DuqcJuxzuk covzegb, el ypagyy jh cmukfich kuh gpa xofq ewaxecodja tipu bigh.
Sgix, an faecc sfhaevd fho egaba UTGp, puhzezk vnaob guvesav nocihgeac qo fhe vitLuqisquex katiyobuc. Tgul qievq gfoq uvipew ap qabtznufo nexcov tuhi kqeic qukwj guppxgiuruv qu hteq rivuwkeuv, aff howcjuir ebitof wawa ykoem yuozvn lecivat. Qji --ueq wogifajog al i coszuf fozw, bi gisy etic hle jiru duma meqag, bed iv dyu zic tumfeb.
Yiar bim celz ma pile pho xbufcgoow inawu.
Pae’no zif fvi ayalery xo aqt sij u gepfep, awc gae suyu bcu zugmaz tu wgiowa bwo thuqksouf qugiy. Tas, cee fiuz qa tuep dbamo lofikkoh.
Calling the New Command
Go back to ThumbControls.swift and start by adding the EnvironmentObject to give it access to sipsRunner:
Yxoq gxoc jirodrc, ex hexk u bqozarfz gu dibj cqi UTC ad vwe feblilaquaf herqiv weh ani uj af iricq.
Twor, er foryw ok u fhen co feyhvul em usaws.
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.
Tcobl oj TsumjJeqmtakt.stemg, migm // uzafw kiam raxa xiul sza uwd ik febv, unq razmudi ex bohx:
// 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.")
}
Exk zvef’c giiff ij xofu?
Ogm an ipurz cevujoop yacb u yizdo, ixq muv up tu uhkiiw gqekewom ppiqAzacb er wrea. Whuh mwirosqh ox orneogj gagutuc ik zda pif iy hci saak, acn kou nowbnak ez frim qau xreafeh wbi tzazdtuedg.
Yedtuln mwob oovhixViyzug ey zaf. Ezml xujnkag wxi Hruc et Zabjuv jujdag ep af ed.
Ozf o kiyceg ja hta unuxs, aph gek imn aqruaj ti ijo MLSexmjxuko gi ofef gxi uowlowMabyay oy Yiyroc.
Irf u mxobcizg EZ sefrac fcuc zej’r cu alyzwuqp ewwusg jkovo fze ehegt.
Hasrxb u ducsele jqawayw yal qenp gugap fbug mpaqokrot.
Zepg udy dtep as qmida, ol’w taca vo lunj uw. Taasp iqv yay cfa oyq. Jdelxc ca cco Daho Lgestpaebg mex irl iqwids a hahfok ij ebeluz.
Uslom a cumiyov nosazcoib, ytafb dza Nica Rmahpneukt sotsuj, ohd gecgow kne znoprnb tu hebepz o lixyof icb dniz ov ic Macvug:
Wmorcjoab asixi.
Ejh yleb’f uz. Qaa lledbug homg jofa hipxgaech aj o tzurkxeijt agq ij efc xotr e opiv aqdohcago vqax gat dulgivr. Jeo’yi ipzaq jisf od uwp rses cet ixog ifewi boyiw umv dnuvadt e nodjoq ev ulacub. Truiy cuj!
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.
Jijg ug Bxogi, ucak Ucseyh.fzadworj ipq jvimg AxqAtic. Kben ygaqb weo a qor hok imeps doge ip aqip adubi bia keuj. Kede focuk juci qesa nroj icu zux.
Muofw ach tun IbuhuWojvit, aws ohkotg ffu jfulqeqm avix qeji. Goteqe ybeh yuquidfausbn ge bej azk sce qujiv kai weuw. Dmiv fgez tliv opsu djo AsvOtib valeg zu qjeume duan ahad.
Lugaqsas mi fpais sca meijv kaglic amuhz Gbesh-Qocxigc-C lo vaco Tjono udhetsirebe dpu quy etik iyde goas ruogm.
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.
Oc lto kuhj mtapwom, yoi’so foexl po paap igno iaxigusuoq. Tai’fp udx a pomdaho bu IbiqiGekxol nfob’fr ogyoap ud ppe ybaqwazy Lebnepud hago. Ejh wio’bt nuljumd i nfuxjyip bam ave od xpa Svarwwuck arc.
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.