mirror of
https://github.com/nicoverbruggen/NVAlert.git
synced 2025-08-07 09:40:08 +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