mirror of
https://github.com/nicoverbruggen/NVAlert.git
synced 2025-08-06 01:00:09 +02:00
Initial commit
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Nico Verbruggen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
20
Package.swift
Normal file
20
Package.swift
Normal file
@ -0,0 +1,20 @@
|
||||
// swift-tools-version: 5.10
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "NVAlert",
|
||||
products: [
|
||||
.library(
|
||||
name: "NVAlert",
|
||||
targets: ["NVAlert"]),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "NVAlert"),
|
||||
.testTarget(
|
||||
name: "NVAlertTests",
|
||||
dependencies: ["NVAlert"]),
|
||||
]
|
||||
)
|
23
README.md
Normal file
23
README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# NVAlert Package
|
||||
|
||||
**Important**: 👷♂️ This package is currently **under construction**, and may change at any time.
|
||||
|
||||
## What is this?
|
||||
|
||||
This is a package that helps you present larger alerts (with more text) for macOS, if you dislike the smaller alerts introduced in more recent versions of macOS.
|
||||
|
||||
Since PHP Monitor displays many helpful prompts, I wanted something a little bit more robust than the default alerts which ship with macOS.
|
||||
|
||||
This was originally written as part of my "zero non first-party dependencies" policy for [PHP Monitor](https://github.com/nicoverbruggen/phpmon).
|
||||
|
||||
## Example usage
|
||||
|
||||
```swift
|
||||
NVAlert().withInformation(
|
||||
title: NSLocalizedString("lite_mode_explanation.title", nil),
|
||||
subtitle: NSLocalizedString("lite_mode_explanation.subtitle", nil),
|
||||
description: NSLocalizedString("lite_mode_explanation.description", nil)
|
||||
)
|
||||
.withPrimary(text: NSLocalizedString("generic.ok", nil))
|
||||
.show()
|
||||
```
|
16
Sources/NVAlert/Extensions/NSWindow+Center.swift
Normal file
16
Sources/NVAlert/Extensions/NSWindow+Center.swift
Normal file
@ -0,0 +1,16 @@
|
||||
import Foundation
|
||||
import Cocoa
|
||||
|
||||
extension NSWindow {
|
||||
/** Centers a window. Taken from: https://stackoverflow.com/a/66140320 */
|
||||
public func setCenterPosition(offsetY: CGFloat = 0) {
|
||||
if let screenSize = screen?.visibleFrame.size {
|
||||
self.setFrameOrigin(
|
||||
NSPoint(
|
||||
x: (screenSize.width - frame.size.width) / 2,
|
||||
y: (screenSize.height - frame.size.height) / 2 + offsetY
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
103
Sources/NVAlert/NVAlert.swift
Normal file
103
Sources/NVAlert/NVAlert.swift
Normal file
@ -0,0 +1,103 @@
|
||||
import Foundation
|
||||
import Cocoa
|
||||
|
||||
@MainActor
|
||||
open class NVAlert {
|
||||
|
||||
var windowController: NSWindowController!
|
||||
|
||||
var noticeVC: NVAlertVC {
|
||||
return self.windowController.contentViewController as! NVAlertVC
|
||||
}
|
||||
|
||||
public init() {
|
||||
let storyboard = NSStoryboard(name: "NVAlert", bundle: Bundle.module)
|
||||
|
||||
self.windowController = storyboard.instantiateController(
|
||||
withIdentifier: "window"
|
||||
) as? NSWindowController
|
||||
}
|
||||
|
||||
public static func make() -> NVAlert {
|
||||
return NVAlert()
|
||||
}
|
||||
|
||||
public func withPrimary(
|
||||
text: String,
|
||||
action: @MainActor @escaping (NVAlertVC) -> Void = { vc in
|
||||
vc.close(with: .alertFirstButtonReturn)
|
||||
}
|
||||
) -> Self {
|
||||
self.noticeVC.buttonPrimary.title = text
|
||||
self.noticeVC.actionPrimary = action
|
||||
return self
|
||||
}
|
||||
|
||||
public func withSecondary(
|
||||
text: String,
|
||||
action: (@MainActor (NVAlertVC) -> Void)? = { vc in
|
||||
vc.close(with: .alertSecondButtonReturn)
|
||||
}
|
||||
) -> Self {
|
||||
self.noticeVC.buttonSecondary.title = text
|
||||
self.noticeVC.actionSecondary = action
|
||||
return self
|
||||
}
|
||||
|
||||
public func withTertiary(
|
||||
text: String = "",
|
||||
action: (@MainActor (NVAlertVC) -> Void)? = nil
|
||||
) -> Self {
|
||||
if text == "" {
|
||||
self.noticeVC.buttonTertiary.bezelStyle = .helpButton
|
||||
}
|
||||
self.noticeVC.buttonTertiary.title = text
|
||||
self.noticeVC.actionTertiary = action
|
||||
return self
|
||||
}
|
||||
|
||||
public func withInformation(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
description: String = ""
|
||||
) -> Self {
|
||||
self.noticeVC.labelTitle.stringValue = title
|
||||
self.noticeVC.labelSubtitle.stringValue = subtitle
|
||||
self.noticeVC.labelDescription.stringValue = description
|
||||
|
||||
// If the description is missing, handle the excess space and change the top margin
|
||||
if description == "" {
|
||||
self.noticeVC.labelDescription.isHidden = true
|
||||
self.noticeVC.primaryButtonTopMargin.constant = 0
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Shows the modal and returns a ModalResponse.
|
||||
If you wish to simply show the alert and disregard the outcome, use `show`.
|
||||
*/
|
||||
@MainActor public func runModal() -> NSApplication.ModalResponse {
|
||||
if !Thread.isMainThread {
|
||||
fatalError("You should always present alerts on the main thread!")
|
||||
}
|
||||
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
|
||||
windowController.window?.makeKeyAndOrderFront(nil)
|
||||
windowController.window?.setCenterPosition(offsetY: 70)
|
||||
return NSApplication.shared.runModal(for: windowController.window!)
|
||||
}
|
||||
|
||||
/** Shows the modal and returns true if the user pressed the primary button. */
|
||||
@MainActor public func didSelectPrimary() -> Bool {
|
||||
return self.runModal() == .alertFirstButtonReturn
|
||||
}
|
||||
|
||||
/**
|
||||
Shows the modal and does not return anything.
|
||||
*/
|
||||
@MainActor public func show() {
|
||||
_ = self.runModal()
|
||||
}
|
||||
}
|
70
Sources/NVAlert/NVAlertVC.swift
Normal file
70
Sources/NVAlert/NVAlertVC.swift
Normal file
@ -0,0 +1,70 @@
|
||||
import Foundation
|
||||
import Cocoa
|
||||
|
||||
open class NVAlertVC: NSViewController {
|
||||
|
||||
// MARK: - Outlets
|
||||
|
||||
@IBOutlet weak var labelTitle: NSTextField!
|
||||
@IBOutlet weak var labelSubtitle: NSTextField!
|
||||
@IBOutlet weak var labelDescription: NSTextField!
|
||||
|
||||
@IBOutlet weak var buttonPrimary: NSButton!
|
||||
@IBOutlet weak var buttonSecondary: NSButton!
|
||||
@IBOutlet weak var buttonTertiary: NSButton!
|
||||
|
||||
var actionPrimary: (@MainActor (NVAlertVC) -> Void) = { _ in }
|
||||
var actionSecondary: (@MainActor (NVAlertVC) -> Void)?
|
||||
var actionTertiary: (@MainActor (NVAlertVC) -> Void)?
|
||||
|
||||
@IBOutlet weak var imageView: NSImageView!
|
||||
|
||||
@IBOutlet weak var primaryButtonTopMargin: NSLayoutConstraint!
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
open override func viewWillAppear() {
|
||||
imageView.image = NSApp.applicationIconImage
|
||||
|
||||
if actionSecondary == nil {
|
||||
buttonSecondary.isHidden = true
|
||||
}
|
||||
if actionTertiary == nil {
|
||||
buttonTertiary.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
open override func viewDidAppear() {
|
||||
view.window?.makeFirstResponder(buttonPrimary)
|
||||
}
|
||||
|
||||
deinit {
|
||||
// print("deinit: \(String(describing: self)).\(#function)")
|
||||
}
|
||||
|
||||
// MARK: Outlet Actions
|
||||
|
||||
@IBAction func primaryButtonAction(_ sender: Any) {
|
||||
self.actionPrimary(self)
|
||||
}
|
||||
|
||||
@IBAction func secondaryButtonAction(_ sender: Any) {
|
||||
if self.actionSecondary != nil {
|
||||
self.actionSecondary!(self)
|
||||
} else {
|
||||
self.close(with: .alertSecondButtonReturn)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func tertiaryButtonAction(_ sender: Any) {
|
||||
if self.actionTertiary != nil {
|
||||
self.actionTertiary!(self)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor public func close(with code: NSApplication.ModalResponse) {
|
||||
self.view.window?.close()
|
||||
NSApplication.shared.stopModal(withCode: code)
|
||||
}
|
||||
}
|
||||
|
5
Sources/NVAlert/NVAlertableError.swift
Normal file
5
Sources/NVAlert/NVAlertableError.swift
Normal file
@ -0,0 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
protocol NVAlertableError {
|
||||
func getErrorMessageKey() -> String
|
||||
}
|
180
Sources/NVAlert/Resources/NVAlert.storyboard
Normal file
180
Sources/NVAlert/Resources/NVAlert.storyboard
Normal file
@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--AlertVC-->
|
||||
<scene sceneID="k1w-dS-AUA">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="noticeVC" id="Fib-nQ-xWn" customClass="NVAlertVC" customModule="NVAlert" sceneMemberID="viewController">
|
||||
<view key="view" id="Dol-uH-OmZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="500" height="212"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<visualEffectView blendingMode="behindWindow" material="popover" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="TK5-ft-RTC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="500" height="212"/>
|
||||
<subviews>
|
||||
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="glZ-s9-p3a">
|
||||
<rect key="frame" x="383" y="13" width="104" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Primary" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="JBv-EB-QYu">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
</buttonCell>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="90" id="fqc-j1-0xt"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="primaryButtonAction:" target="Fib-nQ-xWn" id="7Lo-3h-5v0"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eMy-LR-df3">
|
||||
<rect key="frame" x="281" y="13" width="104" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Secondary" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="JpX-k6-Rvu">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
Gw
|
||||
</string>
|
||||
</buttonCell>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="90" id="WSV-2Y-d6r"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="secondaryButtonAction:" target="Fib-nQ-xWn" id="Eur-fz-Z7O"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Cih-OK-f1X">
|
||||
<rect key="frame" x="13" y="13" width="81" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Tertiary" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="zKJ-wZ-d63">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="tertiaryButtonAction:" target="Fib-nQ-xWn" id="1Kb-aK-Aua"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="glZ-s9-p3a" secondAttribute="bottom" constant="20" symbolic="YES" id="5rO-Qb-XXE"/>
|
||||
<constraint firstItem="Cih-OK-f1X" firstAttribute="leading" secondItem="TK5-ft-RTC" secondAttribute="leading" constant="20" symbolic="YES" id="A7n-Sm-1ha"/>
|
||||
<constraint firstItem="Cih-OK-f1X" firstAttribute="bottom" secondItem="eMy-LR-df3" secondAttribute="bottom" id="GBm-uq-pA7"/>
|
||||
<constraint firstAttribute="bottom" secondItem="eMy-LR-df3" secondAttribute="bottom" constant="20" symbolic="YES" id="GWz-5e-H2n"/>
|
||||
<constraint firstItem="glZ-s9-p3a" firstAttribute="leading" secondItem="eMy-LR-df3" secondAttribute="trailing" constant="12" symbolic="YES" id="Jqh-C4-dw4"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Cih-OK-f1X" secondAttribute="bottom" constant="20" symbolic="YES" id="RDP-uA-ti3"/>
|
||||
<constraint firstAttribute="trailing" secondItem="glZ-s9-p3a" secondAttribute="trailing" constant="20" symbolic="YES" id="VBR-Q0-NzW"/>
|
||||
<constraint firstAttribute="trailing" secondItem="glZ-s9-p3a" secondAttribute="trailing" constant="20" symbolic="YES" id="vR3-e8-l3i"/>
|
||||
<constraint firstItem="eMy-LR-df3" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Cih-OK-f1X" secondAttribute="trailing" constant="12" symbolic="YES" id="vb7-zi-S48"/>
|
||||
<constraint firstAttribute="bottom" secondItem="glZ-s9-p3a" secondAttribute="bottom" constant="20" symbolic="YES" id="y7Q-UK-jiB"/>
|
||||
</constraints>
|
||||
</visualEffectView>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="W3Q-oe-uSr">
|
||||
<rect key="frame" x="98" y="153" width="384" height="19"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="380" id="Qas-PU-3VH"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" title="This is the title of the notice window." id="lHK-Bt-Li3">
|
||||
<font key="font" metaFont="systemBold" size="15"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="tgD-Mg-YN4">
|
||||
<rect key="frame" x="98" y="127" width="384" height="16"/>
|
||||
<textFieldCell key="cell" selectable="YES" title="This is a slightly more expanded explanation." id="m3c-P9-gfK">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3DL-Vb-hLF">
|
||||
<rect key="frame" x="12" y="144" width="48" height="48"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" imagePosition="only" alignment="center" imageScaling="proportionallyUpOrDown" inset="2" id="me9-6W-T16">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="GQ7-OB-bDn">
|
||||
<rect key="frame" x="20" y="111" width="64" height="64"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="64" id="3LN-dA-F7M"/>
|
||||
<constraint firstAttribute="width" constant="64" id="Ug6-65-gJ4"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="ocb-ea-QMS"/>
|
||||
</imageView>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ra-hO-Hjb">
|
||||
<rect key="frame" x="98" y="70" width="384" height="42"/>
|
||||
<textFieldCell key="cell" selectable="YES" id="dlD-Jr-DXC">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<string key="title">Sometimes you need a really long explanation and in that case you can get a really, really long description here, along with, for example, various steps you can take. This allows for a lot of text to be displayed, yay!</string>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="W3Q-oe-uSr" firstAttribute="leading" secondItem="GQ7-OB-bDn" secondAttribute="trailing" constant="16" id="5jE-dU-EcP"/>
|
||||
<constraint firstItem="GQ7-OB-bDn" firstAttribute="top" secondItem="W3Q-oe-uSr" secondAttribute="top" constant="-3" id="9az-OF-bd6"/>
|
||||
<constraint firstItem="glZ-s9-p3a" firstAttribute="top" secondItem="0ra-hO-Hjb" secondAttribute="bottom" constant="30" id="Gho-MH-E5R"/>
|
||||
<constraint firstItem="GQ7-OB-bDn" firstAttribute="leading" secondItem="Dol-uH-OmZ" secondAttribute="leading" constant="20" symbolic="YES" id="Jy3-2p-Xfi"/>
|
||||
<constraint firstItem="0ra-hO-Hjb" firstAttribute="leading" secondItem="tgD-Mg-YN4" secondAttribute="leading" id="Q91-z5-Aru"/>
|
||||
<constraint firstAttribute="trailing" secondItem="TK5-ft-RTC" secondAttribute="trailing" id="R29-Ez-4Vr"/>
|
||||
<constraint firstItem="tgD-Mg-YN4" firstAttribute="trailing" secondItem="W3Q-oe-uSr" secondAttribute="trailing" id="Rvt-wQ-Qlu"/>
|
||||
<constraint firstAttribute="trailing" secondItem="W3Q-oe-uSr" secondAttribute="trailing" constant="20" symbolic="YES" id="Xke-N3-MQO"/>
|
||||
<constraint firstItem="W3Q-oe-uSr" firstAttribute="top" secondItem="Dol-uH-OmZ" secondAttribute="top" constant="40" id="Yxh-mG-9V4"/>
|
||||
<constraint firstItem="tgD-Mg-YN4" firstAttribute="leading" secondItem="W3Q-oe-uSr" secondAttribute="leading" id="cok-Hc-1yU"/>
|
||||
<constraint firstItem="TK5-ft-RTC" firstAttribute="top" secondItem="Dol-uH-OmZ" secondAttribute="top" id="dLM-3k-K3s"/>
|
||||
<constraint firstItem="0ra-hO-Hjb" firstAttribute="top" secondItem="tgD-Mg-YN4" secondAttribute="bottom" constant="15" id="dds-45-S0W"/>
|
||||
<constraint firstItem="0ra-hO-Hjb" firstAttribute="trailing" secondItem="tgD-Mg-YN4" secondAttribute="trailing" id="qSo-G9-3vT"/>
|
||||
<constraint firstAttribute="bottom" secondItem="TK5-ft-RTC" secondAttribute="bottom" id="rcu-CP-dZk"/>
|
||||
<constraint firstItem="TK5-ft-RTC" firstAttribute="leading" secondItem="Dol-uH-OmZ" secondAttribute="leading" id="sG1-PM-7OO"/>
|
||||
<constraint firstItem="tgD-Mg-YN4" firstAttribute="top" secondItem="W3Q-oe-uSr" secondAttribute="bottom" constant="10" id="xg1-yQ-wBh"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="buttonPrimary" destination="glZ-s9-p3a" id="Jbe-0I-Vrm"/>
|
||||
<outlet property="buttonSecondary" destination="eMy-LR-df3" id="iTa-Yk-UTu"/>
|
||||
<outlet property="buttonTertiary" destination="Cih-OK-f1X" id="Q51-nr-umm"/>
|
||||
<outlet property="imageView" destination="GQ7-OB-bDn" id="6c1-kL-u33"/>
|
||||
<outlet property="labelDescription" destination="0ra-hO-Hjb" id="WFn-lY-hmQ"/>
|
||||
<outlet property="labelSubtitle" destination="tgD-Mg-YN4" id="fxm-4M-3cu"/>
|
||||
<outlet property="labelTitle" destination="W3Q-oe-uSr" id="QYO-gb-aQO"/>
|
||||
<outlet property="primaryButtonTopMargin" destination="Gho-MH-E5R" id="saQ-nQ-322"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="KbB-Mj-PlN" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="230" y="2267"/>
|
||||
</scene>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="Xsy-Ws-OsP">
|
||||
<objects>
|
||||
<windowController storyboardIdentifier="window" id="yFW-Ep-rS8" sceneMemberID="viewController">
|
||||
<window key="window" title="Notice" separatorStyle="none" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="alertPanel" frameAutosaveName="" titlebarAppearsTransparent="YES" toolbarStyle="unified" titleVisibility="hidden" id="1ff-Fi-MzV">
|
||||
<windowStyleMask key="styleMask" titled="YES" fullSizeContentView="YES"/>
|
||||
<rect key="contentRect" x="425" y="462" width="480" height="270"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<view key="contentView" id="i7I-3l-Avl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="yFW-Ep-rS8" id="zbW-GZ-m3U"/>
|
||||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<segue destination="Fib-nQ-xWn" kind="relationship" relationship="window.shadowedContentViewController" id="Iax-Bg-bFz"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="dw0-2X-8Hj" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-374" y="2267"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
12
Tests/NVAlertTests/NVAlertTests.swift
Normal file
12
Tests/NVAlertTests/NVAlertTests.swift
Normal file
@ -0,0 +1,12 @@
|
||||
import XCTest
|
||||
@testable import NVAlert
|
||||
|
||||
final class NVAlertTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// XCTest Documentation
|
||||
// https://developer.apple.com/documentation/xctest
|
||||
|
||||
// Defining Test Cases and Test Methods
|
||||
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user