IOS Development

MacOS Programming Tutorial: Utilizing Alerts, Sheets, and Modal Home windows

When creating MacOS functions, builders have to know find out how to current alerts, system panels, or customized home windows to their functions. Whatever the dimension of the applying, it’s going to nonetheless be essential to show an info message to the person or to request a affirmation. Permitting customers to look and open a file from their drive is the case of many sorts of functions, in addition to backing as much as disk.

Presenting one of many parts above will be achieved in two other ways: as a modal dialog or as a sheet. When presenting as a modal dialog, the brand new window is positioned above the present window and interplay is allowed solely with the present window. There’s a title bar and a window will be moved within the display.

Whenever you current a window as a sheet, there is no such thing as a separate window and no transfer can happen. As a substitute, the brand new window slips from the highest of the present window and appears to be a part of it. The frequent level with the modal presentation is that the brand new window stays there till we delete it, and it doesn’t enable to work together with one other content material under.

I like to recommend that you just overview Apple's human interface tips for dialogs, alerts, and sheets.

To summarize, we are going to see at the moment:

How one can current an open panel and permit customers to open information of particular varieties (information with particular extensions).
How one can current a backup panel to do precisely the other and retailer a file anyplace on the disk.
How one can show alerts with several types of content material.
How one can handle a customized window as a sheet.

Let's begin with a fast overview of the demo software we’re going to work on. Then we'll go straight to the brand new issues we have to study right here at the moment.

The demonstration software

The demo software that we’ll deal with at the moment is a transparent picture editor. It is going to be capable of enable us to open any disk picture, apply 4 picture filters, resize and save to disk as a brand new file. There’s a startup challenge to obtain as a result of many of the enterprise logic has already been carried out.

Despite the fact that we will likely be speaking about picture filters (CIFilter objects) later, I’d identical to to say any longer that two of the 4 filters offered require the participation of the person. We'll be sure the app requests this enter by means of alerts, which isn’t beneficial when writing actual apps, nevertheless it's a good way to see the additional options alert can present. Extra on this later.

The identify and dimension of the picture ought to seem on the prime proper of the window. A resize button is there too, which is able to set off the presentation of a brand new window controller in sheet type. On this window, we will present customized values ​​for the width and peak of the picture or cut back them utilizing a predefined proportion. Extra details about this later.

Now let's transfer on to a fast presentation of the remainder of the applying. As already stated, many of the logic of the applying are already carried out, so we will deal with the alerts and panels that the applying will current. Observe that the implementation of the applying is predicated on the MVVM mannequin (Mannequin, View, ViewModel), in order that the precise enterprise logic is separated from all actions carried out within the view.

Observe: If it is advisable to learn on MVVM in Swift, right here is a superb tutorial to do it.

The challenge comprises two view controllers: ImageEditorViewController and ResizeViewController. The respective ViewModel implementations are contained within the ImageEditorViewModel and ResizeViewModel courses, whereas the fashions are the ImageInfo and ImageSize (pretty easy) buildings. As well as, two auxiliary information comprise:

The ImageTools class. It implements some class strategies to load and save a picture to a disk, get the picture dimension, and resize.
The ImageFilter enumeration. An enumeration that comprises not solely the accessible filters offered within the software, but additionally an related code, reminiscent of creating the CIFilter filter, acquiring parameter names for filters requiring person enter, the vary of acceptable values ​​of the component. settings, and some others.

The pre-existing code within the challenge comprises sufficient documentation and so it’s not obscure what every of its parts consists of. I must also point out that almost all of our work right here will likely be achieved within the ImageEditorViewController class; we are going to go to the ResizeViewController only for a second.

I’d advocate taking the time to take a tour within the startup challenge. As soon as you’re feeling snug with this, let's go straight to creating our first alert.

Current your first alert

An alert is a system window that shows info messages with the choice to carry as much as 4 buttons on which customers can click on and set off different actions. As well as, as we are going to see later on this part, alerts could comprise an extra and particular kind of content material. If in case you have already used iOS, an alert (NSAlert) is equal to the alert introduced with the assistance of the UIAlertController class. Alerts are actually helpful like:

They’ll show essential messages to customers or request their enter with out us having to create customized home windows to take action.
They supply an interface (person interface) acquainted to customers as a result of the alerts are a part of the macOS system.

So after we put up an alert to customers, we're simply offering our personalised content material. their look is thought to them and so they know find out how to work together.

Let's create our first alert now. Should you run the applying of the startup challenge introduced within the earlier half, you’ll discover that initially now we have no open picture in our window, it’s only the displayed person interface:

It’s apparent that when no picture is loaded, it’s unimaginable to use filters or resize. Nonetheless, and regardless of the apparent look of this truth, wouldn’t it’s good to current an alert when customers attempt to apply a filter when no picture exists? Thus, not solely will customers be reminded that they have to first open a picture, however it’s a gesture that contributes to the general person expertise (UX).

When descending into the programming stage, the accessible filters are introduced within the colorFilters popup button. NOTE: The info supply of the colorFilters pop-up window (or the gadgets introduced in it) is the availableFilters property of the imageEditorViewModel object that returns an array of all of the accessible filters, reminiscent of they exist in ImageFilter. enum (imageEditorViewModel is the ViewModel). object that implements all actual enterprise logic).

The filters pop-up is linked to the selectImageFilter (_ 🙂 IBAction technique, invoked every time customers choose a filtering possibility from the listed gadgets. selectImageFilter (_ 🙂 exists within the ImageEditorViewController.swift file. As you will notice under, there’s at the moment no implementation. So let's begin:

@IBAction func selectImageFilter (_ sender: N any)
guard let _ = imageEditorViewModel.displayedImageData else
}

@IBAction func selectImageFilter(_ sender: All)

hold let _ = imageEditorViewModel.displayImageData different

Here’s what occurs with the code above:

Initially, the primary line with the guard declaration checks whether or not a picture is loaded and displayed or not. ShownImageData of imageEditorViewModel comprises the loaded picture knowledge. Due to this fact, if they’re zero, it signifies that there is no such thing as a picture loaded within the software.
On this case (the execution falls within the reverse case of the guard assertion), we name the showNoImageAlert () technique. It doesn’t exist but, we are going to change it immediately.
Whatever the choice made by the person within the filter popup, we robotically return to the primary possibility displaying "none" (no filter utilized).
We’re getting back from the operate – obligatory with the custody assertion!

The strains of code above are the primary implementation of this technique. Later, we are going to add a little bit extra code.

Let's go to the showNoImageAlert () technique. As this technique doesn’t exist but, we are going to start to outline it:

func showNoImageAlert ()
if imageEditorViewModel.showNoImageAlert

func showNoImageAlert()

if imageEditorViewModel.showNoImageAlert

The very first thing to do is to examine if an alert will be introduced by way of the worth of the showNoImageAlert property of the imageEditorViewModel object. When that is true, we will show the alert, in any other case nothing ought to occur. Why do we’d like this situation right here? You’ll quickly perceive.

Let's transfer on to initializing an alert object and sending the String messages we wish to present:

func showNoImageAlert ()
if imageEditorViewModel.showNoImageAlert

depart alert = NSAlert ()
alert.messageText = "Lacking picture"
alert.informativeText = "There isn’t a picture to use the filter to."

func showNoImageAlert()

For a easy alert, what now we have achieved is sufficient. Observe that when an alert is introduced, it makes use of the applying icon to the left of the displayed content material. Since there is no such thing as a customized icon within the software, the default one offered when Xcode generates the challenge will likely be used.

It solely stays for us to current it. Let's present it as a leaf dialog:

func showNoImageAlert ()

func showNoImageAlert()

if imageEditorViewModel.showNoImageAlert

alert.beginSheetModal(for: self.view.the window!)

BeginSheetModal (for: completionHandler 🙂 of the NSAlert class will carry out the precise presentation. The primary parameter is the window to which the alert will likely be introduced. On this case, it’s the fundamental window of our software (we entry it by way of the view property, as you possibly can see). When a button is operated on the alert, the completion supervisor known as with a NSApplication.ModalResponse worth describing the button getting used (study extra right here), and the alert is rejected.

Observe above, the primary implementation of the showNoImageAlert () technique that we didn’t specify any button or button title. That is good as a result of NSAlert will robotically create an "OK" button. As well as, we is not going to use the response worth within the completion handler right here. We’ll see find out how to decide the button clicked later.

Run the applying now and choose a picture filter. Right here's what you must get:

Congratulations! You could have simply created your first alert!

Current a delete button

Alerts exist primarily to tell customers or to make fast selections, however generally customers would like that the identical alert not be displayed completely as a part of a given course of. On this case, simply show a examine field permitting customers to pick out the alert and to not reappear.

Thankfully, NSAlert permits us so as to add a delete button to the alert and let customers select whether or not or not they permit the alert to seem. In actual fact, the delete button is a checkbox … delete, nevertheless it's known as a button as a result of a examine field can be a particular button kind (an NSButton object).

We'll add extra code within the showNoImageAlert () technique that may show a delete button with a customized message. When customers click on on it, the alert will not be displayed. We’ll see the method step-by-step including the next strains within the showNoImageAlert () operate, after initializing the alert however earlier than displaying it:

func showNoImageAlert ()
if imageEditorViewModel.showNoImageAlert

alert.showsSuppressionButton = true
alert.suppressionButton? .title = "I acquired it, don’t present me this message anymore."
alert.suppressionButton? .goal = self
alert.suppressionButton? .motion = #selector (handleNoImageAlertSuppressionButtonClick (_ :))

alert.beginSheetModal (for: self.view.window!)

func showNoImageAlert()

if imageEditorViewModel.showNoImageAlert

The very first thing to do is to allow the looks of the delete button by setting the showsSuppressionButton property of the alert object to true. Then we outline the textual content we wish to show. This textual content ought to clearly point out to the person what the delete button is for.

With the subsequent two strains, we outline the goal and the motion to name while you examine the delete field. We simply inform it to name the handleNoImageAlertSuppressionButtonClick (_ 🙂 technique, which is one other technique so as to add to the ImageEditorViewController class:

@objc func handleNoImageAlertSuppressionButtonClick (_ deleteButton: NSButton)
imageEditorViewModel.showNoImageAlert = false

@objc func handleNoImageAlertSuppressionButtonClick(_ suppressionButton: NSButton)

imageEditorViewModel.showNoImageAlert = false

On this technique, you set the worth of the showNoImageAlert property of the imageEditorViewModel object to false. So, the subsequent time we select a filter with out first loading a picture, we is not going to see the alert. As well as, now you can perceive why we began the implementation of the showNoImageAlert () technique by checking the worth of this property. we show the alert if solely customers haven’t determined in any other case:

func showNoImageAlert ()
if imageEditorViewModel.showNoImageAlert

func showNoImageAlert()

if imageEditorViewModel.showNoImageAlert

Run the applying once more and choose a filter. You will note that the delete button is there:

Click on on it and shut the alert. Then choose a filter once more. no alert this time!

Observe that the results of clicking the delete button should not persistently saved by our software, so in case you rerun them, you’ll have the return alert. Nonetheless, utilizing the delete button is definitely a person choice, which have to be saved completely in real-world functions.

Opening and loading a picture

Now that we've added some extra to the person expertise and realized find out how to show a sheet alert, let's now open a picture to our software. On this half, we are going to meet a brand new component in macOS programming, specifically the NSOpenPanel.

An NSOpenPanel, or an open panel, is a system window that means that you can browse directories and choose a number of information or directories to be opened by an software. You already know this window as a result of it’s offered by any software that helps an Open characteristic. For instance, go to Xcode and open the next menu: File> Open …. What you will notice is what we’re going to obtain right here too.

We’ll present any such performance utilizing the Open button of our demo software (we don’t use menus, it's a future tutorial). This button is already linked to the openImage (_ 🙂 IBAction technique. Thus, the code we are going to add will likely be executed when the Open button is used.

The method of presenting an open panel is straightforward:

We initialize an NSOpenPanel object.
We outline some properties in response to what we want to enable by way of this panel (choose a number of information, choose directories, and so on.).
We current the panel in modal or sheet type.
We course of URLs or URLs pointing to the information or folders chosen by the person.

Let's see this in motion:

@IBAction func openImage (_ sender: N any)
let panel = NSOpenPanel ()
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.allowsMultipleSelection = false

}

@IBAction func OpenImage(_ sender: All)

After initialization of the NSOpenPanel object, the values ​​we assign within the properties proven above:

Lets you select information.
Forbidden to decide on directories.
Forbidden to decide on a number of information.

Now that now we have made it clear that we would like just one file to be chosen by means of our open panel, we have to outline the kind of information the panel ought to enable to open. In our case, we would like picture information, the extensions "png", "jpg" and "jpeg" have to be completely included within the checklist of information licensed to open information. In actual fact, this checklist is specified within the supportedImageFormats property of the imageEditorViewModel object in our software. So let's assign it to the panel as proven under:

@IBAction func openImage (_ sender: N any)

panel.allowedFileTypes = imageEditorViewModel.supportedImageFormats

}

@IBAction func OpenImage(_ sender: All)

Since we’re going to show the panel modally (as a stand-alone window), we will show a message that might not show in leaf view:

@IBAction func openImage (_ sender: N any)

panel.message = "Choose a picture to open."
}

@IBAction func OpenImage(_ sender: All)

signal.message = "Choose a picture to open."

Lastly, let's current the panel:

@IBAction func openImage (_ sender: N any)

// Present the panel modally.
let response = panel.runModal ()
}

@IBAction func OpenImage(_ sender: All)

// Present the panel modally.

let reply = signal.runModal()

The results of runModal () (a NSApplication.ModalResponse worth) is saved within the response fixed and signifies the button on which the person clicked within the panel (Open or Cancel). Right here's how we deal with it:

@IBAction func openImage (_ sender: N any)

if reply == NSApplication.ModalResponse.OK
guardian left selectedURL = panel.url else
imageEditorViewModel.setSelectedImage (atURL: selectedURL)

}

@IBAction func OpenImage(_ sender: All)

Should you select a single file or listing, the url property of the panel object will comprise the URL of the merchandise chosen while you click on the Open button. Though we don’t assist a number of opening of information right here, the strategy to get a number of URLs from the panel is to make use of the panel.urls file. You’re going to get a desk with the URLs of the chosen information and directories.

As soon as we guarantee that a URL is accessible, we name the setSelectedImage (atURL 🙂 technique of the imageEditorViewModel object to maintain it and to set off the picture loading within the ImageEditorViewModel class (our ViewModel). If profitable, imageEditorViewModel will inform the view controller by closing imageDidLoadHandler. It’s already carried out within the viewDidLoad () technique:

imageEditorViewModel.imageDidLoadHandler = [unowned self] (imageData) in
guard let picture = NSImage (knowledge: imageData) else
self.imageView? .picture = picture
self.imageNameLabel.stringValue = self.imageEditorViewModel.imageName ?? ""
self.imageSizeLabel.stringValue = self.imageEditorViewModel.displayedImageSizeString
self.colorFilters.selectItem (at: zero)

imageEditorViewModel.imageDidLoadHandler =

This closure returns the picture knowledge (a Knowledge object). So we first create an NSImage object utilizing this knowledge and assign it to the picture view. Then we get the dimensions string and picture identify values ​​from the imageEditorViewModel object and we assign them to the respective labels.

Observe: NSImage is the equal of UIImage in iOS.

Be at liberty to run the applying now and use the Open button. Find a picture in your pc and open it.

Apply filters

Beforehand, we managed to current an alert with a message stating that a picture wanted to be loaded earlier than making use of a filter. Now that our software can truly load a picture, let's apply filters to this picture. By programming, a filter is a CIFilter object (Core Picture filter) that may modify a picture in a sure method. You’ll find right here all of the accessible filters from Apple and discover out extra in regards to the CIFilter class.

As I wished to maintain issues easy however ample for studying, I selected to incorporate 4 (four) filters within the software:

Sepia – CISepiaTone
Mono – CIPhotoEffectMono
Blur – CIDiscBlur
Comedian – CIComicEffect

All filters essentially require the supply picture to which they are going to be utilized. As well as, Sepia and Blur require an extra parameter worth to have the ability to produce the ultimate end result. Sepia expects a parameter known as "inputIntensity" with values ​​between zero.zero and 1.zero (the default is 1.zero). Blur expects a parameter known as "inputRadius" with values ​​between zero and 100 (the default is eight).

In a real-world software, we must always present controls that customers anticipate to see for any such change, such for instance slider. Nonetheless, right here we is not going to do this. My purpose is to show nearly all alert options, we current a textual content area in an alert in order that the person can enter the parameters described above.

NOTE: Please overview the code within the ImageFilter enumeration for info on find out how to create a CIFilter filter, find out how to acquire parameter names, acceptable ranges of values, and different associated options, or carry out an extra search on the Web. I’ll keep away from discussing filter particulars right here as they exit of the field and the tutorial will finish very lengthy.

So let's begin by creating the next technique within the ImageEditorViewController class:

func showAlert (withFilterValues ​​filterValues: AlertFilterValues)

func showAlert(avecFilterValues filterDataValues: AlertFilterValues)

AlertFilterValues ​​is a typealias (outlined within the ImageEditorViewModel.swift file) of the tuple: (minValue: String, maxValue: String, paramName: String) that features the minimal acceptable worth, the utmost acceptable worth, and the parameter identify for the filter chosen. The worth of the filterValues ​​parameter will likely be offered by the imageEditorViewModel object after we name this technique.

Let's begin by initializing an alert object and defining the messages displayed. As you will notice subsequent, the min and max values, in addition to the identify of the parameter are a part of the displayed message:

func showAlert (withFilterValues ​​filterValues: AlertFilterValues)
depart alert = NSAlert ()
alert.messageText = "Apply picture filter"
alert.informativeText = "Enter a worth between (filterValues.minValue) and (filterValues.maxValue) for the [(filterValues.paramName)] parameter:"

func showAlert(avecFilterValues filterDataValues: AlertFilterValues)

After we first created our first alert on this tutorial, we don’t specify any buttons. We relied on the truth that NSAlert would create a default button for us. This time, we are going to specify the next buttons:

func showAlert (withFilterValues ​​filterValues: AlertFilterValues)

alert.addButton (withTitle: "OK")
alert.addButton (withTitle: "Cancel")
alert.addButton (withTitle: "Use default worth")

func showAlert(avecFilterValues filterDataValues: AlertFilterValues)

alert.addButton(avecTitre: "D & # 39; AGREEMENT")

alert.addButton(avecTitre: "Cancel")

alert.addButton(avecTitre: "Use default worth")

The addButton (withTitle 🙂 permits us so as to add a button to the alert with a customized title. It’s apparent that the OK and Cancel buttons are meant for. The "Use Default" button that we added because the final will enable the applying to make use of the default worth of the setting for the chosen filter. Observe two issues:

The primary button you add will seem to the suitable of the alert, whereas the final one would be the leftmost one.
You may add as much as 4 buttons to an alert, however don’t get used to it an excessive amount of. the less buttons in an alert, the higher the person expertise.

Now, the essential issues. NSAlert means that you can add customized views to an alert by way of a property known as accessoryView. In fact, it's not a matter of making a whole person interface constructed into the alert, nevertheless it's very helpful in circumstances the place you want one thing greater than what you need. alerts can provide. We’ll use this property and assign it a textual content area (an NSTextField object) in order that we will enter the parameter values.

The customized view object that will likely be assigned to the accessoriesView property of the alert will be created in the best way that fits you greatest (graphically or in code). Right here we are going to create the textual content area on the fly:

func showAlert (withFilterValues ​​filterValues: AlertFilterValues)

func showAlert(avecFilterValues filterDataValues: AlertFilterValues)

Don’t hesitate to alter the body of the textual content area and reposition or resize it to see what is occurring.

It’s now time to introduce the alert. Since we introduced the earlier alert as a leaf, let's current it as a modal window:

func showAlert (withFilterValues ​​filterValues: AlertFilterValues)

func showAlert(avecFilterValues filterDataValues: AlertFilterValues)

let reply = alert.runModal()

Time required to course of the response worth. As I've already stated a couple of instances, the worth returned is a NSApplication.ModalResponse worth that signifies that the button has been activated in an alert or panel (just like the open panel we've seen earlier than). For alerts, the next values ​​are offered to be able to decide which button you clicked:

alertFirstButtonReturn
alertSecondButtonReturn
alertThirdButtonReturn

alertFirstButtonReturn considers the rightmost button so that you can perceive the order. Use an if-else or swap assertion to find out which button the person clicked. If in case you have a fourth button, the case else in an if-else assertion or the default case in a swap assertion will take into account this button.

In our demo software, we have an interest within the first and third buttons, as a result of the second is the Cancel button and there’s no have to take motion:

func showAlert (withFilterValues ​​filterValues: AlertFilterValues)

if reply == .alertFirstButtonReturn else if reply == .alertThirdButtonReturn

func showAlert(avecFilterValues filterDataValues: AlertFilterValues)

Dans la classe ImageEditorViewController, vous trouverez une méthode implémentée appelée applyFilter (usingParameters :). Il obtient un dictionnaire sous forme d’argument contenant le nom du paramètre du filtre et la valeur saisie par l’utilisateur. La fourniture du dictionnaire est facultative. Par conséquent, si l’argument est nil, la valeur par défaut sera utilisée automatiquement pour le paramètre du filtre. Notre however ici est d’appeler cette méthode soit avec la valeur saisie par l’utilisateur (si elle est valide), soit avec nil si cette valeur est fausse ou si le bouton «Utiliser la valeur par défaut» a été activé (troisième bouton). Mettre à jour le code ci-dessus pour y parvenir donnera ceci:

func showAlert (avecFilterValues ​​filterValues: AlertFilterValues)
    …

    si réponse == .alertFirstButtonReturn sinon si réponse == .alertThirdButtonReturn

1

2

three

four

5

6

7

eight

9

ten

11

12

13

14

15

16

17

18

19

20

21

22

func showAlert(avecFilterValues filterValues: AlertFilterValues) {

if reply == .alertFirstButtonReturn different if reply == .alertThirdButtonReturn

appliquer le filtre(usingParameters: nothingness)

}

L'isValid (valeur 🙂 de l'objet imageEditorViewModel validera la valeur d'entrée. S'il est valide, le dictionnaire avec le nom du paramètre et cette valeur est créé, puis transmis en tant qu'argument à la méthode applyFilter (usingParameters :). Dans tous les autres cas, nous appelons la méthode en passant nil et en indiquant ainsi que la valeur par défaut doit être utilisée pour le filtre. Notez qu'avant de vérifier la validité dans le cas du premier bouton, nous convertissons la valeur saisie de String en Double. Si l'utilisateur a entré quelque selected de différent d'un nombre acceptable, alors le cas contraire sera exécuté.

Maintenant que showAlert (withFilterValues ​​:) est prêt, passons à la méthode selectImageFilter (_ 🙂 IBAction. Jusqu’à présent, notre implémentation précédente en faisait partie où nous avons traité le cas d’essayer d’appliquer un filtre alors qu’aucune picture n’existait. Terminez l’implémentation avec le code suivant pour pouvoir appliquer un filtre sélectionné:

@IBAction func selectImageFilter (_ expéditeur: N'importe lequel)

@IBAction func selectImageFilter(_ sender: Tout)

let filtre sélectionné = colorFilters.itemTitle(at: colorFilters.indexOfSelectedItem)

imageEditorViewModel.s'appliqueImageFilter = ImageFilter.filtered(fromString: filtre sélectionné)

if !imageEditorViewModel.shouldSetFilterValue()

appliquer le filtre(usingParameters: nothingness)

different

Avec les deux premières lignes, nous déterminons le filtre à appliquer en fonction de la sélection de l'utilisateur. La propriété appliedImageFilter de l'objet imageEditorViewModel contient la valeur de filtre sélectionnée. Ensuite, nous appelons la méthode shouldSetFilterValue () pour voir si une valeur de paramètre est requise pour le filtre sélectionné. Si ce n'est pas le cas, nous appelons la méthode applyFilter (usingParameters 🙂 dont nous avons parlé en passant précédemment une valeur nulle. Sinon, nous obtenons les valeurs que nous voulons montrer à l'alerte (valeurs de paramètre min et max, nom du paramètre), et finalement nous appelons showAlert (withFilterValues ​​:) que nous avons implémentées juste avant.

L'software peut maintenant appliquer des filtres à l'picture chargée afin que vous puissiez l'essayer. Assurez-vous de fournir à la fois les valeurs valides et non valides à l'alerte lorsque vous sélectionnez le filtre Sépia ou le filtre Flou et voyez son comportement.

Voici à quoi ressemblerait une picture après l’software du filtre sépia.

Redimensionnement de l'picture

Dans les events précédentes, nous avons réussi à initialiser, configurer et présenter les alertes et le panneau ouvert, et nous avons constaté que nous pouvons les afficher sous forme de boîtes de dialogue modales ou de feuilles. Ces modes de présentation peuvent également être appliqués dans des fenêtres personnalisées créées dans l'software, ce que nous allons voir dans cette partie.

Notre interface utilisateur principale contient un bouton Redimensionner qui est censé présenter le ResizeViewController dans un nouveau contrôleur de fenêtre (la nouvelle fenêtre et le nouveau contrôleur de vue existent dans le fichier Important.storyboard). La selected la plus attendue à faire serait de créer une séquence dans le storyboard, puis de présenter le contrôleur de vue de redimensionnement de manière modale en effectuant cette séquence. Mais non, nous n’irons pas de cette façon. Au lieu de cela, nous allons charger le contrôleur de fenêtre dans le code afin de pouvoir présenter sa fenêtre avec le contrôleur de vue de contenu sous forme de feuille.

Le redimensionnement ResizeViewController offre deux choices différentes: soit en définissant une largeur et une hauteur explicites (avec l'possibility de conserver le rapport hauteur / largeur d'origine ou non), soit en choisissant une valeur en pourcentage prédéfinie pour réduire l'picture affichée. Toutes ces fonctionnalités sont déjà implémentées, de sorte que notre travail dans ce contrôleur de vue sera bref. Nous nous concentrerons uniquement sur la façon de la rejeter en tant que feuille renvoyant la valeur NSApplication.ModalResponse appropriée lorsque les boutons Redimensionner et Annuler sont cliqués.

Let’s go now to the resizeImage(_:) IBAction technique within the ImageEditorViewController. The very first thing we’ll do is to load the view controller from the storyboard:

@IBAction func resizeImage(_ sender: Any)

@IBAction func resizeImage(_ sender: Any)

In a quick ahead mode, the above strains:

Initialize a NSStoryboard object essential to entry the objects described within the subsequent steps.
Entry the window controller utilizing the scene identifier “ResizeWindowController” which can be set within the Important storyboard (see the Identification Inspector after deciding on the brand new window controller object).
Get the window controller’s window (resizeWindow above).
Get the content material view controller, which is ResizeViewController occasion.
Be certain that that there’s a a present picture dimension.
Observe: Please learn this earlier tutorial for extra details about window controllers, home windows, and particulars on loading from storyboards in code.

After now we have initialized the resizeViewController occasion, we should cross to it the present dimension of the displayed picture:

@IBAction func resizeImage(_ sender: Any)
    …

    resizeViewController.resizeViewModel.set(originalSize: displayedImageSize)

@IBAction func resizeImage(_ sender: Any)

resizeViewController.resizeViewModel.collectively(originalSize: displayedImageSize)

Discover that the displayedImageSize worth is given as an argument to the set(originalSize:) technique of the resizeViewModel object (the ViewModel object) of the resizeViewController.

Now, let’s current it:

@IBAction func resizeImage(_ sender: Any)

@IBAction func resizeImage(_ sender: Any)

self.view.the window?.beginSheet(resizeWindow, completionHandler: )

Clearly, our subsequent step is to look at the response worth, and get the brand new dimension set within the ResizeViewController when response is the same as “OK”. However wait a minute, it is a customized window and never a system panel, so is it potential to offer us again such a worth? Sure, it’s, however now we have to manually accomplish that.

Open the ResizeViewController.swift file and spot the resize(_:) and cancelResizing(_:) IBAction strategies. They’re empty for now, however they’re known as when the Resize and the Cancel button are clicked.

Let’ begin with the Cancel button. When it’s clicked, we wish to dismiss the window, however since it’s introduced as a sheet, we additionally wish to point out which button was clicked so no additional actions to be taken again to the ImageEditorViewController class. We will do each fairly simply as proven subsequent:

@IBAction func cancelResizing(_ sender: Any)
    guard let window = self.view.window, let guardian = window.sheetParent else
    guardian.endSheet(window, returnCode: .cancel)

@IBAction func cancelResizing(_ sender: Any)

hold let the window = self.view.the window, let relative = the window.sheetParent different

relative.endSheet(the window, returnCode: .Cancel)

Initially, we unwrap the window object, in addition to its guardian window that presents this one as a sheet. Then, we name the endSheet(_:returnCode) technique utilizing the guardian object, and we cross two values as arguments: The present window that ought to be dismissed, and the suitable NSApplication.ModalResponse.

Equally, we will do the identical for the Resize button within the resize(_:) IBAction technique. This time we cross the “OK” worth because the return code:

@IBAction func resize(_ sender: Any)

@IBAction func resize(_ sender: Any)

Again to the ImageEditorViewController class once more, let’s proceed from the purpose we had been left within the resizeImage(_:) IBAction technique. We’ll see what the response worth is, and if it’s “OK” we’ll get the brand new dimension person set and we’ll set off the precise resizing course of:

@IBAction func resizeImage(_ sender: Any)
    …

    self.view.window?.beginSheet(resizeWindow, completionHandler: (response) in

        if response == NSApplication.ModalResponse.OK

)

@IBAction func resizeImage(_ sender: Any)

resize(toSize:) technique is already carried out, so the above will work.

That’s it, now you know the way to current customized home windows as sheets and to dismiss them accordingly passing the right return code again to the caller. It’s potential to create your personal customized NSApplication.ModalResponse codes, nevertheless I depart that apart for now, it’s a bit out of our scope.

Run the app, load a picture after which resize it. The resize window is introduced as a sheet!

As a aspect be aware, remember that in case you resize and then you definately apply a filter, the picture will revert to its unique dimension. That’s as a result of filters apply to the unique picture, so that you’ll have to resize once more. You’re free to change the given implementation and alter that if you would like.

Saving The Edited Picture

Now that our small picture editor works positive as it will probably apply filters and resize a picture, let’s add the ultimate contact, and let’s make it potential to save lots of the edited picture to disk. Against the NSOpenPanel class we used earlier to open a picture, now we are going to use the NSSavePanel class to save lots of. Nonetheless, and that’s essential, there’s one other step that have to be achieved previous to that; to make our app able to writing anyplace.

Within the Venture Navigator choose the challenge and go to the Capabilities tab. Do the next:

Allow the App Sandbox functionality.
Within the File Entry part, change the permission to Learn/Write for the Person Chosen File entry.

It will create a brand new entitlements file that may comprise the brand new permissions you simply granted to the app. Should you skip or overlook this step, you’ll be getting exceptions while you attempt to current a save panel in your apps.

Again to the ImageEditorViewController.swift file and within the saveImage(_:) IBAction technique, the place we are going to add the code that may make the Save button work. The configuration of a NSSavePanel object seems fairly much like the configuration of a NSOpenPanel object, as after its initialization now we have to set a couple of properties after which to current it. Let’s see it first after which we’ll focus on about it:

@IBAction func saveImage(_ sender: Any)
    guard let _ = imageEditorViewModel.displayedImageData, let imageExtension = imageEditorViewModel.imageExtension else

    let savePanel = NSSavePanel()
    savePanel.allowedFileTypes = [imageExtension]
savePanel.nameFieldStringValue = imageEditorViewModel.imageName ?? "untitled"
    savePanel.canCreateDirectories = true
    savePanel.isExtensionHidden = false

@IBAction func saveImage(_ sender: Any)

hold let _ = imageEditorViewModel.displayedImageData, let imageExtension = imageEditorViewModel.imageExtension different

let savePanel = NSSavePanel()

savePanel.allowedFileTypes = [[[[imageExtension]

savePanel.nameFieldStringValue = imageEditorViewModel.imageName ?? "untitled"

savePanel.canCreateDirectories = true

savePanel.isExtensionHidden = false

Here’s what the above properties are for, after the initialization of the savePanel object:

The allowedFileTypes property expects for an array of file extensions that ought to be allowed within the save panel. In our case, and provided that customers can open numerous sorts of picture information, we use the extension of the loaded file (i.e., when “png” is opened, “png” to be saved as effectively).
nameFieldStringValue property will be optionally set. Whenever you do this, you set a preset file identify to be proven within the save panel which customers can change. Should you omit utilizing this property customers will simply need to kind a file identify earlier than having the ability to save.
canCreateDirectories is just about self-explanatory. When true, an extra button to create new folders is displaying app within the save panel.
isExtensionHidden hides or exhibits the extension of the file identify set utilizing the nameFieldStringValue property.

After we end this half, be at liberty to play with the above properties and see what occurs in case you change their values, or simply omit setting a customized worth.

Subsequent, let’s current the save panel:

@IBAction func saveImage(_ sender: Any)
    …

    let response = savePanel.runModal()

@IBAction func saveImage(_ sender: Any)

let reply = savePanel.runModal()

If the response worth is “OK”, that means that the person clicked on the Save button, then we are going to get the URL to the chosen file, and we are going to use the imageEditorViewModel object to carry out the precise saving of the edited picture to disk.

Observe: A save panel is used to offer you again a URL, it doesn’t carry out the precise saving which is as much as every app. Keep in mind that we did the identical with the open panel; we simply used a URL from it, the precise loading of the picture befell elsewhere.

@IBAction func saveImage(_ sender: Any)
    …

    if response == NSApplication.ModalResponse.OK

@IBAction func saveImage(_ sender: Any)

The saveImage(toURL:) technique of the imageEditorViewModel initiates the precise saving job. It returns a Bool worth relying on whether or not the saving was profitable or not. We don’t make any use of that end result right here, however I depart it to you to resolve if you wish to do one thing with that (i.e., to indicate an alert if saving fails).

abstract

Presenting alerts and default panels is a primary information that each macOS developer ought to have. Whether or not you’ll show them modally or as a sheet lies totally on the general app look and habits, nevertheless it’s a private resolution too. By finishing the implementation of the demo app step-by-step we managed to debate all of the essential particulars of this put up’s matter. Moreover alerts and the open and save panels, we additionally made it clear find out how to current a customized window of our app as as sheet too. As a ultimate phrase, utilizing system alerts and panels is an efficient method to supply customers with interfaces which might be accustomed to, so it’s simpler for them to get used to our apps and really feel snug with them. That is all we would like in any case.

For reference, you possibly can obtain the entire challenge on GitHub.