├── .gitignore
├── Demos
└── DSFLabelledTextField Demo
│ ├── DSFLabelledTextField Demo
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── DSFLabelledTextField_Demo.entitlements
│ ├── AppDelegate.swift
│ ├── Info.plist
│ ├── ViewController.swift
│ └── Base.lproj
│ │ └── Main.storyboard
│ └── DSFLabelledTextField Demo.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
│ ├── xcshareddata
│ └── xcschemes
│ │ ├── DSFLabelledTextField Demo.xcscheme
│ │ └── DSFLabelledTextField RTL Demo.xcscheme
│ └── project.pbxproj
├── Tests
├── LinuxMain.swift
└── DSFLabelledTextFieldTests
│ ├── XCTestManifests.swift
│ └── DSFLabelledTextFieldTests.swift
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── DSFLabelledTextField.podspec
├── LICENSE
├── Package.swift
├── Sources
└── DSFLabelledTextField
│ ├── private
│ └── NSGraphicsContext+extensions.swift
│ ├── DSFLabelledTextFieldGroup.swift
│ └── DSFLabelledTextField.swift
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import DSFLabelledTextFieldTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += DSFLabelledTextFieldTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tests/DSFLabelledTextFieldTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(DSFLabelledTextFieldTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Tests/DSFLabelledTextFieldTests/DSFLabelledTextFieldTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DSFLabelledTextField
3 |
4 | final class DSFLabelledTextFieldTests: XCTestCase {
5 | func testExample() {
6 | }
7 |
8 | static var allTests = [
9 | ("testExample", testExample),
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo/DSFLabelledTextField_Demo.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "DSFAppearanceManager",
6 | "repositoryURL": "https://github.com/dagronf/DSFAppearanceManager",
7 | "state": {
8 | "branch": null,
9 | "revision": "beb1e728eb8d4dfee45f95eab8f18c16879d19fa",
10 | "version": "3.0.4"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // DSFLabelledTextField Demo
4 | //
5 | // Created by Darren Ford on 14/9/20.
6 | //
7 |
8 | import Cocoa
9 |
10 | @NSApplicationMain
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | @IBOutlet var window: NSWindow!
14 |
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | // Insert code here to initialize your application
18 | }
19 |
20 | func applicationWillTerminate(_ aNotification: Notification) {
21 | // Insert code here to tear down your application
22 | }
23 |
24 |
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/DSFLabelledTextField.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "DSFLabelledTextField"
3 | s.version = "1.1.0"
4 | s.summary = "A simple macOS labelled text field using Swift"
5 | s.description = <<-DESC
6 | A simple macOS labelled text field using Swift
7 | DESC
8 | s.homepage = "https://github.com/dagronf/DSFLabelledTextField"
9 | s.license = { :type => "MIT", :file => "LICENSE" }
10 | s.author = { "Darren Ford" => "dford_au-reg@yahoo.com" }
11 | s.social_media_url = ""
12 | s.osx.deployment_target = "10.11"
13 | s.source = { :git => ".git", :tag => s.version.to_s }
14 | s.source_files = "Sources/DSFLabelledTextField/*.swift"
15 | s.frameworks = "AppKit"
16 | s.swift_version = "5.2"
17 | end
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Darren Ford
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSMainStoryboardFile
26 | Main
27 | NSPrincipalClass
28 | NSApplication
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "DSFLabelledTextField",
8 | platforms: [
9 | .macOS(.v10_11)
10 | ],
11 | products: [
12 | .library(
13 | name: "DSFLabelledTextField",
14 | type: .static,
15 | targets: ["DSFLabelledTextField"]
16 | ),
17 | .library(
18 | name: "DSFLabelledTextField-Dynamic",
19 | type: .dynamic,
20 | targets: ["DSFLabelledTextField"]
21 | ),
22 | ],
23 | dependencies: [
24 | .package(url: "https://github.com/dagronf/DSFAppearanceManager", from: "3.0.0")
25 | ],
26 | targets: [
27 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
28 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
29 | .target(
30 | name: "DSFLabelledTextField",
31 | dependencies: ["DSFAppearanceManager"]),
32 | .testTarget(
33 | name: "DSFLabelledTextFieldTests",
34 | dependencies: ["DSFLabelledTextField"]),
35 | ]
36 | )
37 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // DSFLabelledTextField Demo
4 | //
5 | // Created by Darren Ford on 14/9/20.
6 | //
7 |
8 | import Cocoa
9 |
10 | import DSFLabelledTextField
11 |
12 | class ViewController: NSViewController {
13 |
14 | @objc dynamic var frameX: CGFloat = 100.0
15 | @objc dynamic var frameY: CGFloat = 34.0
16 | @objc dynamic var frameW: CGFloat = 1366
17 | @objc dynamic var frameH: CGFloat = 990
18 |
19 | @objc dynamic var boundsX: CGFloat = 0.0
20 | @objc dynamic var boundsY: CGFloat = 0.0
21 | @objc dynamic var boundsW: CGFloat = 1366
22 | @objc dynamic var boundsH: CGFloat = 990
23 |
24 | @objc dynamic var ta: CGFloat = 0.5
25 | @objc dynamic var tb: CGFloat = 0.5
26 | @objc dynamic var tc: CGFloat = 1.0
27 | @objc dynamic var td: CGFloat = 1.0
28 | @objc dynamic var tx: CGFloat = 0.0
29 | @objc dynamic var ty: CGFloat = 0.0
30 |
31 | /// A labelled field group. All labels will have the same width
32 |
33 | let group = DSFLabelledTextFieldGroup(padding: 5)
34 | @IBOutlet weak var redField: DSFLabelledTextField!
35 | @IBOutlet weak var greenField: DSFLabelledTextField!
36 | @IBOutlet weak var blueField: DSFLabelledTextField!
37 |
38 | let dynamicGroup = DSFLabelledTextFieldGroup()
39 | @IBOutlet weak var dynamicStackView: NSStackView!
40 |
41 | @objc dynamic var labelIsEnabled = false
42 |
43 | override func viewDidLoad() {
44 | super.viewDidLoad()
45 |
46 | /// Sync the widths of the red, green and blue labels
47 | group.add(fields: redField, greenField, blueField)
48 |
49 | // Add some fields dynamically
50 |
51 | let a = DSFLabelledTextField()
52 | a.translatesAutoresizingMaskIntoConstraints = false
53 | a.label = "最初"
54 | a.placeholderString = "これが最初です"
55 | dynamicStackView.addArrangedSubview(a)
56 |
57 | let b = DSFLabelledTextField()
58 | b.translatesAutoresizingMaskIntoConstraints = false
59 | b.label = "二番目"
60 | b.placeholderString = "これは2番目です"
61 | dynamicStackView.addArrangedSubview(b)
62 |
63 | // Synchronize the widths of these labels.
64 | dynamicGroup.add(fields: a, b)
65 | }
66 |
67 | override var representedObject: Any? {
68 | didSet {
69 | // Update the view, if already loaded.
70 | }
71 | }
72 |
73 |
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/Sources/DSFLabelledTextField/private/NSGraphicsContext+extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSGraphicsContext+extensions.swift
3 | //
4 | // Copyright © 2022 Darren Ford. All rights reserved.
5 | //
6 | // MIT License
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | #if os(macOS)
28 |
29 | import AppKit
30 |
31 | // MARK: - Graphics Context
32 |
33 | extension NSGraphicsContext {
34 | /// Perform the block without antialiasing
35 | func disablingAntialiasing(_ stateBlock: () throws -> Void) rethrows -> Void {
36 | let existing = self.shouldAntialias
37 | defer { self.shouldAntialias = existing }
38 |
39 | self.shouldAntialias = false
40 | try stateBlock()
41 | }
42 |
43 | /// Execute the supplied block within a `saveGraphicsState() / restoreGraphicsState()` pair
44 | ///
45 | /// Example usage:
46 | /// ```
47 | /// NSGraphicsContext.usingGraphicsState {
48 | /// backgroundColor.setFill()
49 | /// rectanglePath.fill()
50 | /// }
51 | /// ```
52 | ///
53 | /// - Parameter stateBlock: The block to execute within the new graphics state
54 | static func usingGraphicsState(stateBlock: (NSGraphicsContext) throws -> Void) rethrows -> Void {
55 | NSGraphicsContext.saveGraphicsState()
56 | defer {
57 | NSGraphicsContext.restoreGraphicsState()
58 | }
59 |
60 | if let ctx = NSGraphicsContext.current {
61 | try stateBlock(ctx)
62 | }
63 | }
64 | }
65 |
66 |
67 | #endif
68 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo.xcodeproj/xcshareddata/xcschemes/DSFLabelledTextField Demo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo.xcodeproj/xcshareddata/xcschemes/DSFLabelledTextField RTL Demo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
44 |
46 |
52 |
53 |
54 |
55 |
61 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DSFLabelledTextField
2 |
3 |   
4 |  [](https://cocoapods.org) [](https://swift.org/package-manager)
5 |
6 | A simple macOS labelled text field using Swift.
7 |
8 | 
9 | 
10 |
11 | ## Installation
12 |
13 | ### Swift Package Manager
14 |
15 | Add `https://github.com/dagronf/DSFLabelledTextField` to your project.
16 |
17 | ### CocoaPods
18 |
19 | Add the following to your `Podfiles` file
20 |
21 | ```ruby
22 | pod 'DSFLabelledTextField', :git => 'https://github.com/dagronf/DSFLabelledTextField'
23 | ```
24 |
25 | ### Direct
26 |
27 | Add the swift files from the `Sources/DSFLabelledTextField` subfolder to your project
28 |
29 | ## Usage
30 |
31 | 1. Add a new NSTextField using Interface Builder, then change the class type to `DSFLabelledTextField`, or
32 | 2. Programatically create one
33 |
34 | ## Properties
35 |
36 | * `drawsRoundedEdges ` : Draw the control border using round rects
37 | * `drawsLabelBackground` : Draw a solid color behind the label
38 | * `label` : The text to display in the label
39 | * `labelForegroundColor` : The color of the label text
40 | * `labelBackgroundColor` : The color behind the label
41 | * `labelWidth` : The width of the label. If set to -1, fits the label size to the label text.
42 | * `labelAlignment` : The alignment of the label. Useful if you have multiple label fields that you want to synchronise the width for. Defaults to `NSTextAlignment.center`
43 |
44 | ## Grouping fields
45 |
46 | There are times where you might need to synchronise the width of the labels of a number of fields. To do this, you can add the fields to a `DSFLabelledTextFieldGroup` instance and the field labels will size to fit the maximum width of all the labels in the group.
47 |
48 | ### Example
49 |
50 | ```swift
51 |
52 | @IBOutlet weak var redField: DSFLabelledTextField!
53 | @IBOutlet weak var greenField: DSFLabelledTextField!
54 | @IBOutlet weak var blueField: DSFLabelledTextField!
55 | let colorGroup = DSFLabelledTextFieldGroup()
56 |
57 | override func viewDidLoad() {
58 | super.viewDidLoad()
59 |
60 | /// Sync the widths of the red, green and blue labels
61 | self.colorGroup.add(fields: redField, greenField, blueField)
62 | }
63 |
64 | ```
65 |
66 | ## More screenshots
67 |
68 | 
69 | 
70 |
71 | ## History
72 |
73 | * `1.2.0`: Fixed issue with `@IBInspectable` not appearing in Interface Builder. Better 'enabled' visibility.
74 | * `1.1.0`: Added support for label grouping
75 | * `1.0.2`: Added label alignment (left, right, center etc)
76 | * `1.0.1`: Demonstrate dynamically create
77 | * `1.0.0`: Initial release
78 |
79 | ## License
80 |
81 | ```
82 | MIT License
83 |
84 | Copyright (c) 2020 Darren Ford
85 |
86 | Permission is hereby granted, free of charge, to any person obtaining a copy
87 | of this software and associated documentation files (the "Software"), to deal
88 | in the Software without restriction, including without limitation the rights
89 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
90 | copies of the Software, and to permit persons to whom the Software is
91 | furnished to do so, subject to the following conditions:
92 |
93 | The above copyright notice and this permission notice shall be included in all
94 | copies or substantial portions of the Software.
95 |
96 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
97 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
98 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
99 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
100 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
101 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
102 | SOFTWARE.
103 | ```
104 |
--------------------------------------------------------------------------------
/Sources/DSFLabelledTextField/DSFLabelledTextFieldGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DSFLabelledTextFieldGroup.swift
3 | //
4 | // Created by Darren Ford on 20/10/20.
5 | // Copyright © 2020 Darren Ford. All rights reserved.
6 | //
7 | // MIT License
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in all
17 | // copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | // SOFTWARE.
26 |
27 | #if os(macOS)
28 |
29 | import AppKit
30 |
31 | /// A labelled text field grouping construct, allowing the labels to synchronise their widths to match the max content
32 | public class DSFLabelledTextFieldGroup: NSObject {
33 | // We want to use a weak array, so that if one of the fields vanishes it is automatically removed from the grouping
34 | private var grouping = NSHashTable.weakObjects()
35 |
36 | /// Set the padding for the label
37 | public var padding: CGFloat {
38 | didSet {
39 | self.syncWidths()
40 | }
41 | }
42 |
43 | /// Create a grouping object with an optional padding value
44 | public init(padding: CGFloat = 0) {
45 | self.padding = padding
46 | super.init()
47 | }
48 |
49 | /// Add a new field to the group
50 | /// - Parameter field: the field to add. If the field is already a member of a group, does nothing
51 | ///
52 | /// Group members are held weakly, so if one of the fields disappears the group will automatically update
53 | @discardableResult public func add(_ field: DSFLabelledTextField) -> Bool {
54 | guard field.group == nil else {
55 | // field is already a member of a group.
56 | return false
57 | }
58 | self.grouping.add(field)
59 | field.group = self
60 | return true
61 | }
62 |
63 | /// Add a new field to the group
64 | /// - Parameter fields: the fields to add
65 | /// - Returns true if the fields were added, false if one or more fields are already a member of a group
66 | ///
67 | /// Group members are held weakly, so if one of the fields disappears the group will automatically update.
68 | ///
69 | /// If any of the fields is already a group member, this call does nothing
70 | @discardableResult public func add(_ fields: [DSFLabelledTextField]) -> Bool {
71 | // Check ALL of the fields before adding, if any are already a member of a group then the entire fails.
72 | guard fields.filter({ $0.group != nil }).count == 0 else {
73 | // One of more of the fields already are a member of a group
74 | return false
75 | }
76 | fields.forEach { self.add($0) }
77 | self.syncWidths()
78 | return true
79 | }
80 |
81 | /// Add a new field to the group
82 | /// - Parameter fields: the fields to add.
83 | /// - Returns true if the fields were added, false if one or more fields are already a member of a group
84 | ///
85 | /// Group members are held weakly, so if one of the fields disappears the group will automatically update
86 | ///
87 | /// If any of the fields is already a group member, this call does nothing
88 | @discardableResult public func add(fields: DSFLabelledTextField...) -> Bool {
89 | return self.add(fields as [DSFLabelledTextField])
90 | }
91 |
92 | /// Remove a field from the group
93 | /// - Parameter field: the field to remove
94 | /// - Returns true if the field was removed, false if the field is not a member of this group
95 | @discardableResult public func remove(_ field: DSFLabelledTextField) -> Bool {
96 | if field.group === self {
97 | field.group = nil
98 | self.grouping.remove(field)
99 | return true
100 | }
101 | return false
102 | }
103 |
104 | /// Update the label widths of all the fields in the group to be the maximum width of all the field labels
105 | @discardableResult public func syncWidths() -> CGFloat {
106 | var combinedWidth: CGFloat = 0
107 | self.grouping.objectEnumerator().forEach { f in
108 | guard let field = f as? DSFLabelledTextField else { return }
109 | combinedWidth = max(combinedWidth, field.labelTextSize.width)
110 | }
111 |
112 | self.grouping.objectEnumerator().forEach { f in
113 | guard let field = f as? DSFLabelledTextField else { return }
114 | field.labelWidth = combinedWidth + self.padding
115 | }
116 |
117 | return combinedWidth
118 | }
119 | }
120 |
121 | #endif
122 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 23AB08EC253E41AF000277E3 /* DSFLabelledTextField in Frameworks */ = {isa = PBXBuildFile; productRef = 23AB08EB253E41AF000277E3 /* DSFLabelledTextField */; };
11 | 23FC4125250F7B9F0055CF3B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC4124250F7B9F0055CF3B /* AppDelegate.swift */; };
12 | 23FC4127250F7B9F0055CF3B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC4126250F7B9F0055CF3B /* ViewController.swift */; };
13 | 23FC4129250F7BA00055CF3B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23FC4128250F7BA00055CF3B /* Assets.xcassets */; };
14 | 23FC412C250F7BA00055CF3B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23FC412A250F7BA00055CF3B /* Main.storyboard */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | 23EF2AB2253E4166001C71F3 /* DSFLabelledTextField */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DSFLabelledTextField; path = ../..; sourceTree = ""; };
19 | 23FC4121250F7B9F0055CF3B /* DSFLabelledTextField Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DSFLabelledTextField Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
20 | 23FC4124250F7B9F0055CF3B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
21 | 23FC4126250F7B9F0055CF3B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
22 | 23FC4128250F7BA00055CF3B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23 | 23FC412B250F7BA00055CF3B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
24 | 23FC412D250F7BA00055CF3B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
25 | 23FC412E250F7BA00055CF3B /* DSFLabelledTextField_Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DSFLabelledTextField_Demo.entitlements; sourceTree = ""; };
26 | /* End PBXFileReference section */
27 |
28 | /* Begin PBXFrameworksBuildPhase section */
29 | 23FC411E250F7B9F0055CF3B /* Frameworks */ = {
30 | isa = PBXFrameworksBuildPhase;
31 | buildActionMask = 2147483647;
32 | files = (
33 | 23AB08EC253E41AF000277E3 /* DSFLabelledTextField in Frameworks */,
34 | );
35 | runOnlyForDeploymentPostprocessing = 0;
36 | };
37 | /* End PBXFrameworksBuildPhase section */
38 |
39 | /* Begin PBXGroup section */
40 | 23AB08EA253E41AF000277E3 /* Frameworks */ = {
41 | isa = PBXGroup;
42 | children = (
43 | );
44 | name = Frameworks;
45 | sourceTree = "";
46 | };
47 | 23FC4118250F7B9F0055CF3B = {
48 | isa = PBXGroup;
49 | children = (
50 | 23EF2AB2253E4166001C71F3 /* DSFLabelledTextField */,
51 | 23FC4123250F7B9F0055CF3B /* DSFLabelledTextField Demo */,
52 | 23FC4122250F7B9F0055CF3B /* Products */,
53 | 23AB08EA253E41AF000277E3 /* Frameworks */,
54 | );
55 | sourceTree = "";
56 | };
57 | 23FC4122250F7B9F0055CF3B /* Products */ = {
58 | isa = PBXGroup;
59 | children = (
60 | 23FC4121250F7B9F0055CF3B /* DSFLabelledTextField Demo.app */,
61 | );
62 | name = Products;
63 | sourceTree = "";
64 | };
65 | 23FC4123250F7B9F0055CF3B /* DSFLabelledTextField Demo */ = {
66 | isa = PBXGroup;
67 | children = (
68 | 23FC4124250F7B9F0055CF3B /* AppDelegate.swift */,
69 | 23FC4126250F7B9F0055CF3B /* ViewController.swift */,
70 | 23FC4128250F7BA00055CF3B /* Assets.xcassets */,
71 | 23FC412A250F7BA00055CF3B /* Main.storyboard */,
72 | 23FC412D250F7BA00055CF3B /* Info.plist */,
73 | 23FC412E250F7BA00055CF3B /* DSFLabelledTextField_Demo.entitlements */,
74 | );
75 | path = "DSFLabelledTextField Demo";
76 | sourceTree = "";
77 | };
78 | /* End PBXGroup section */
79 |
80 | /* Begin PBXNativeTarget section */
81 | 23FC4120250F7B9F0055CF3B /* DSFLabelledTextField Demo */ = {
82 | isa = PBXNativeTarget;
83 | buildConfigurationList = 23FC4131250F7BA00055CF3B /* Build configuration list for PBXNativeTarget "DSFLabelledTextField Demo" */;
84 | buildPhases = (
85 | 23FC411D250F7B9F0055CF3B /* Sources */,
86 | 23FC411E250F7B9F0055CF3B /* Frameworks */,
87 | 23FC411F250F7B9F0055CF3B /* Resources */,
88 | );
89 | buildRules = (
90 | );
91 | dependencies = (
92 | 23AB08E9253E41AA000277E3 /* PBXTargetDependency */,
93 | );
94 | name = "DSFLabelledTextField Demo";
95 | packageProductDependencies = (
96 | 23AB08EB253E41AF000277E3 /* DSFLabelledTextField */,
97 | );
98 | productName = "DSFLabelledTextField Demo";
99 | productReference = 23FC4121250F7B9F0055CF3B /* DSFLabelledTextField Demo.app */;
100 | productType = "com.apple.product-type.application";
101 | };
102 | /* End PBXNativeTarget section */
103 |
104 | /* Begin PBXProject section */
105 | 23FC4119250F7B9F0055CF3B /* Project object */ = {
106 | isa = PBXProject;
107 | attributes = {
108 | LastSwiftUpdateCheck = 1200;
109 | LastUpgradeCheck = 1200;
110 | TargetAttributes = {
111 | 23FC4120250F7B9F0055CF3B = {
112 | CreatedOnToolsVersion = 12.0;
113 | };
114 | };
115 | };
116 | buildConfigurationList = 23FC411C250F7B9F0055CF3B /* Build configuration list for PBXProject "DSFLabelledTextField Demo" */;
117 | compatibilityVersion = "Xcode 9.3";
118 | developmentRegion = en;
119 | hasScannedForEncodings = 0;
120 | knownRegions = (
121 | en,
122 | Base,
123 | );
124 | mainGroup = 23FC4118250F7B9F0055CF3B;
125 | productRefGroup = 23FC4122250F7B9F0055CF3B /* Products */;
126 | projectDirPath = "";
127 | projectRoot = "";
128 | targets = (
129 | 23FC4120250F7B9F0055CF3B /* DSFLabelledTextField Demo */,
130 | );
131 | };
132 | /* End PBXProject section */
133 |
134 | /* Begin PBXResourcesBuildPhase section */
135 | 23FC411F250F7B9F0055CF3B /* Resources */ = {
136 | isa = PBXResourcesBuildPhase;
137 | buildActionMask = 2147483647;
138 | files = (
139 | 23FC4129250F7BA00055CF3B /* Assets.xcassets in Resources */,
140 | 23FC412C250F7BA00055CF3B /* Main.storyboard in Resources */,
141 | );
142 | runOnlyForDeploymentPostprocessing = 0;
143 | };
144 | /* End PBXResourcesBuildPhase section */
145 |
146 | /* Begin PBXSourcesBuildPhase section */
147 | 23FC411D250F7B9F0055CF3B /* Sources */ = {
148 | isa = PBXSourcesBuildPhase;
149 | buildActionMask = 2147483647;
150 | files = (
151 | 23FC4127250F7B9F0055CF3B /* ViewController.swift in Sources */,
152 | 23FC4125250F7B9F0055CF3B /* AppDelegate.swift in Sources */,
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | };
156 | /* End PBXSourcesBuildPhase section */
157 |
158 | /* Begin PBXTargetDependency section */
159 | 23AB08E9253E41AA000277E3 /* PBXTargetDependency */ = {
160 | isa = PBXTargetDependency;
161 | productRef = 23AB08E8253E41AA000277E3 /* DSFLabelledTextField */;
162 | };
163 | /* End PBXTargetDependency section */
164 |
165 | /* Begin PBXVariantGroup section */
166 | 23FC412A250F7BA00055CF3B /* Main.storyboard */ = {
167 | isa = PBXVariantGroup;
168 | children = (
169 | 23FC412B250F7BA00055CF3B /* Base */,
170 | );
171 | name = Main.storyboard;
172 | sourceTree = "";
173 | };
174 | /* End PBXVariantGroup section */
175 |
176 | /* Begin XCBuildConfiguration section */
177 | 23FC412F250F7BA00055CF3B /* Debug */ = {
178 | isa = XCBuildConfiguration;
179 | buildSettings = {
180 | ALWAYS_SEARCH_USER_PATHS = NO;
181 | CLANG_ANALYZER_NONNULL = YES;
182 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
183 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
184 | CLANG_CXX_LIBRARY = "libc++";
185 | CLANG_ENABLE_MODULES = YES;
186 | CLANG_ENABLE_OBJC_ARC = YES;
187 | CLANG_ENABLE_OBJC_WEAK = YES;
188 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
189 | CLANG_WARN_BOOL_CONVERSION = YES;
190 | CLANG_WARN_COMMA = YES;
191 | CLANG_WARN_CONSTANT_CONVERSION = YES;
192 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
193 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
194 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
195 | CLANG_WARN_EMPTY_BODY = YES;
196 | CLANG_WARN_ENUM_CONVERSION = YES;
197 | CLANG_WARN_INFINITE_RECURSION = YES;
198 | CLANG_WARN_INT_CONVERSION = YES;
199 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
200 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
201 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
202 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
203 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
204 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
205 | CLANG_WARN_STRICT_PROTOTYPES = YES;
206 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
207 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
208 | CLANG_WARN_UNREACHABLE_CODE = YES;
209 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
210 | COPY_PHASE_STRIP = NO;
211 | DEBUG_INFORMATION_FORMAT = dwarf;
212 | ENABLE_STRICT_OBJC_MSGSEND = YES;
213 | ENABLE_TESTABILITY = YES;
214 | GCC_C_LANGUAGE_STANDARD = gnu11;
215 | GCC_DYNAMIC_NO_PIC = NO;
216 | GCC_NO_COMMON_BLOCKS = YES;
217 | GCC_OPTIMIZATION_LEVEL = 0;
218 | GCC_PREPROCESSOR_DEFINITIONS = (
219 | "DEBUG=1",
220 | "$(inherited)",
221 | );
222 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
223 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
224 | GCC_WARN_UNDECLARED_SELECTOR = YES;
225 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
226 | GCC_WARN_UNUSED_FUNCTION = YES;
227 | GCC_WARN_UNUSED_VARIABLE = YES;
228 | MACOSX_DEPLOYMENT_TARGET = 10.12;
229 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
230 | MTL_FAST_MATH = YES;
231 | ONLY_ACTIVE_ARCH = YES;
232 | SDKROOT = macosx;
233 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
234 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
235 | };
236 | name = Debug;
237 | };
238 | 23FC4130250F7BA00055CF3B /* Release */ = {
239 | isa = XCBuildConfiguration;
240 | buildSettings = {
241 | ALWAYS_SEARCH_USER_PATHS = NO;
242 | CLANG_ANALYZER_NONNULL = YES;
243 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
244 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
245 | CLANG_CXX_LIBRARY = "libc++";
246 | CLANG_ENABLE_MODULES = YES;
247 | CLANG_ENABLE_OBJC_ARC = YES;
248 | CLANG_ENABLE_OBJC_WEAK = YES;
249 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
250 | CLANG_WARN_BOOL_CONVERSION = YES;
251 | CLANG_WARN_COMMA = YES;
252 | CLANG_WARN_CONSTANT_CONVERSION = YES;
253 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
254 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
255 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
256 | CLANG_WARN_EMPTY_BODY = YES;
257 | CLANG_WARN_ENUM_CONVERSION = YES;
258 | CLANG_WARN_INFINITE_RECURSION = YES;
259 | CLANG_WARN_INT_CONVERSION = YES;
260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
264 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
265 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
266 | CLANG_WARN_STRICT_PROTOTYPES = YES;
267 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
268 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
269 | CLANG_WARN_UNREACHABLE_CODE = YES;
270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
271 | COPY_PHASE_STRIP = NO;
272 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
273 | ENABLE_NS_ASSERTIONS = NO;
274 | ENABLE_STRICT_OBJC_MSGSEND = YES;
275 | GCC_C_LANGUAGE_STANDARD = gnu11;
276 | GCC_NO_COMMON_BLOCKS = YES;
277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
279 | GCC_WARN_UNDECLARED_SELECTOR = YES;
280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
281 | GCC_WARN_UNUSED_FUNCTION = YES;
282 | GCC_WARN_UNUSED_VARIABLE = YES;
283 | MACOSX_DEPLOYMENT_TARGET = 10.12;
284 | MTL_ENABLE_DEBUG_INFO = NO;
285 | MTL_FAST_MATH = YES;
286 | SDKROOT = macosx;
287 | SWIFT_COMPILATION_MODE = wholemodule;
288 | SWIFT_OPTIMIZATION_LEVEL = "-O";
289 | };
290 | name = Release;
291 | };
292 | 23FC4132250F7BA00055CF3B /* Debug */ = {
293 | isa = XCBuildConfiguration;
294 | buildSettings = {
295 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
296 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
297 | CODE_SIGN_ENTITLEMENTS = "DSFLabelledTextField Demo/DSFLabelledTextField_Demo.entitlements";
298 | CODE_SIGN_STYLE = Automatic;
299 | COMBINE_HIDPI_IMAGES = YES;
300 | DEVELOPMENT_TEAM = 3L6RK3LGGW;
301 | ENABLE_HARDENED_RUNTIME = YES;
302 | INFOPLIST_FILE = "DSFLabelledTextField Demo/Info.plist";
303 | LD_RUNPATH_SEARCH_PATHS = (
304 | "$(inherited)",
305 | "@executable_path/../Frameworks",
306 | );
307 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.DSFLabelledTextField-Demo";
308 | PRODUCT_NAME = "$(TARGET_NAME)";
309 | SWIFT_VERSION = 5.0;
310 | };
311 | name = Debug;
312 | };
313 | 23FC4133250F7BA00055CF3B /* Release */ = {
314 | isa = XCBuildConfiguration;
315 | buildSettings = {
316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
317 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
318 | CODE_SIGN_ENTITLEMENTS = "DSFLabelledTextField Demo/DSFLabelledTextField_Demo.entitlements";
319 | CODE_SIGN_STYLE = Automatic;
320 | COMBINE_HIDPI_IMAGES = YES;
321 | DEVELOPMENT_TEAM = 3L6RK3LGGW;
322 | ENABLE_HARDENED_RUNTIME = YES;
323 | INFOPLIST_FILE = "DSFLabelledTextField Demo/Info.plist";
324 | LD_RUNPATH_SEARCH_PATHS = (
325 | "$(inherited)",
326 | "@executable_path/../Frameworks",
327 | );
328 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.DSFLabelledTextField-Demo";
329 | PRODUCT_NAME = "$(TARGET_NAME)";
330 | SWIFT_VERSION = 5.0;
331 | };
332 | name = Release;
333 | };
334 | /* End XCBuildConfiguration section */
335 |
336 | /* Begin XCConfigurationList section */
337 | 23FC411C250F7B9F0055CF3B /* Build configuration list for PBXProject "DSFLabelledTextField Demo" */ = {
338 | isa = XCConfigurationList;
339 | buildConfigurations = (
340 | 23FC412F250F7BA00055CF3B /* Debug */,
341 | 23FC4130250F7BA00055CF3B /* Release */,
342 | );
343 | defaultConfigurationIsVisible = 0;
344 | defaultConfigurationName = Release;
345 | };
346 | 23FC4131250F7BA00055CF3B /* Build configuration list for PBXNativeTarget "DSFLabelledTextField Demo" */ = {
347 | isa = XCConfigurationList;
348 | buildConfigurations = (
349 | 23FC4132250F7BA00055CF3B /* Debug */,
350 | 23FC4133250F7BA00055CF3B /* Release */,
351 | );
352 | defaultConfigurationIsVisible = 0;
353 | defaultConfigurationName = Release;
354 | };
355 | /* End XCConfigurationList section */
356 |
357 | /* Begin XCSwiftPackageProductDependency section */
358 | 23AB08E8253E41AA000277E3 /* DSFLabelledTextField */ = {
359 | isa = XCSwiftPackageProductDependency;
360 | productName = DSFLabelledTextField;
361 | };
362 | 23AB08EB253E41AF000277E3 /* DSFLabelledTextField */ = {
363 | isa = XCSwiftPackageProductDependency;
364 | productName = DSFLabelledTextField;
365 | };
366 | /* End XCSwiftPackageProductDependency section */
367 | };
368 | rootObject = 23FC4119250F7B9F0055CF3B /* Project object */;
369 | }
370 |
--------------------------------------------------------------------------------
/Sources/DSFLabelledTextField/DSFLabelledTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DSFLabelledTextField.swift
3 | //
4 | // Created by Darren Ford on 23/6/20.
5 | // Copyright © 2020 Darren Ford. All rights reserved.
6 | //
7 | // MIT License
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in all
17 | // copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | // SOFTWARE.
26 |
27 | #if os(macOS)
28 |
29 | import AppKit
30 | import DSFAppearanceManager
31 |
32 | // The alpha value used for drawing when the control is disabled.
33 | private let alphaValueForDisabled: CGFloat = 0.4
34 |
35 | /// A text field containing an embedded label field
36 | @IBDesignable public class DSFLabelledTextField: NSTextField {
37 | /// Use round rects when drawing the border
38 | @IBInspectable public var drawsRoundedEdges: Bool = true {
39 | didSet {
40 | self.customCell.roundedEdges = self.drawsRoundedEdges
41 | self.needsDisplay = true
42 | }
43 | }
44 |
45 | /// Draw a background behind the label
46 | @IBInspectable public var drawsLabelBackground: Bool = true {
47 | didSet {
48 | self.customCell.drawsLabelBackground = self.drawsLabelBackground
49 | self.needsDisplay = true
50 | }
51 | }
52 |
53 | /// The text to display within the label section
54 | @IBInspectable public var label: String = "" {
55 | didSet {
56 | self.textLabel.stringValue = self.label
57 | self.groupSync()
58 |
59 | self.needsLayout = true
60 | }
61 | }
62 |
63 | /// The color of the text for the label
64 | @IBInspectable public var labelForegroundColor: NSColor = NSColor.secondaryLabelColor {
65 | didSet {
66 | self.textLabel.textColor = self.labelForegroundColor
67 | self.needsDisplay = true
68 | }
69 | }
70 |
71 | /// The background color for the label
72 | @IBInspectable public var labelBackgroundColor: NSColor = NSColor.windowBackgroundColor {
73 | didSet {
74 | self.customCell.labelBackgroundColor = self.labelBackgroundColor
75 | self.needsDisplay = true
76 | }
77 | }
78 |
79 | /// The width of the label section. If -1, fits to the size of the text within the label
80 | @IBInspectable public var labelWidth: CGFloat = -1 {
81 | didSet {
82 | self.widthConstraint.isActive = (self.labelWidth != -1)
83 | if self.labelWidth != -1 {
84 | self.widthConstraint.constant = self.labelWidth
85 | self.customCell.labelWidth = self.labelWidth
86 | }
87 | self.needsLayout = true
88 | }
89 | }
90 |
91 | /// The label alignment. Defaults to center
92 | @IBInspectable public var labelAlignment: Int = 2 {
93 | didSet {
94 | if let align = NSTextAlignment(rawValue: self.labelAlignment) {
95 | self.labelAlignmentRaw = align
96 | }
97 | }
98 | }
99 |
100 | public var labelAlignmentRaw: NSTextAlignment = .left {
101 | didSet {
102 | self.textLabel.alignment = labelAlignmentRaw
103 | self.needsDisplay = true
104 | }
105 | }
106 |
107 | // MARK: Initializers
108 |
109 | override public init(frame frameRect: NSRect) {
110 | super.init(frame: frameRect)
111 | self.setup()
112 | }
113 |
114 | public required init?(coder: NSCoder) {
115 | super.init(coder: coder)
116 | self.setup()
117 | }
118 |
119 | // MARK: Private Properties
120 |
121 | // The group IF this text field is a member of a group, otherwise nil.
122 | // Held weakly so that if the group goes away label can still operate
123 | internal weak var group: DSFLabelledTextFieldGroup?
124 |
125 | // Sync the label widths within the group IF this label is a group member
126 | internal func groupSync() {
127 | self.group?.syncWidths()
128 | }
129 |
130 | // Width constraint for the embedded label
131 | private var widthConstraint: NSLayoutConstraint!
132 |
133 | // The embedded label for the text field
134 | private lazy var textLabel: NSTextField = {
135 | let x = NSTextField()
136 | x.translatesAutoresizingMaskIntoConstraints = false
137 | x.stringValue = self.label
138 | x.isSelectable = false
139 | x.isEditable = false
140 | x.cell?.isEditable = false
141 | x.cell?.isSelectable = false
142 | x.cell?.wraps = false
143 | x.drawsBackground = false
144 | x.isBordered = false
145 |
146 | if let labelFont = self.font {
147 | var c = NSFontManager.shared.convert(labelFont, toSize: labelFont.pointSize - 1)
148 | c = NSFontManager.shared.convertWeight(true, of: c)
149 | c = NSFontManager.shared.convertWeight(true, of: c)
150 | x.font = c
151 | }
152 |
153 | x.textColor = NSColor.secondaryLabelColor
154 | x.alignment = .center
155 | x.translatesAutoresizingMaskIntoConstraints = false
156 | x.userInterfaceLayoutDirection = self.userInterfaceLayoutDirection
157 | return x
158 | }()
159 | }
160 |
161 | private extension DSFLabelledTextField {
162 | /// Build up the label
163 | private func setup() {
164 | let oldCell = self.cell as! NSTextFieldCell
165 | let newCell = DSFPlainTextFieldCell()
166 |
167 | newCell.isEnabled = oldCell.isEnabled
168 | newCell.isEditable = oldCell.isEditable
169 | newCell.isSelectable = oldCell.isSelectable
170 | newCell.placeholderString = oldCell.placeholderString
171 | newCell.isScrollable = oldCell.isScrollable
172 | newCell.isContinuous = oldCell.isContinuous
173 | newCell.font = oldCell.font
174 | newCell.isBordered = oldCell.isBordered
175 | newCell.isBezeled = oldCell.isBezeled
176 | newCell.backgroundStyle = oldCell.backgroundStyle
177 | newCell.bezelStyle = oldCell.bezelStyle
178 | newCell.drawsBackground = oldCell.drawsBackground
179 | newCell.alignment = oldCell.alignment
180 | newCell.formatter = oldCell.formatter
181 | newCell.alignment = oldCell.alignment
182 | newCell.stringValue = oldCell.stringValue
183 |
184 | self.cell = newCell
185 |
186 | let tl = self.textLabel
187 | self.addSubview(tl)
188 |
189 | if #available(OSX 10.11, *) {
190 | tl.allowsDefaultTighteningForTruncation = true
191 | tl.maximumNumberOfLines = 1
192 | }
193 | tl.setContentHuggingPriority(.defaultHigh, for: .horizontal)
194 |
195 | self.addConstraints(
196 | NSLayoutConstraint.constraints(
197 | withVisualFormat: "V:|-(>=0)-[item]-(>=0)-|",
198 | options: .alignAllCenterY, metrics: nil,
199 | views: ["item": tl]
200 | )
201 | )
202 | self.addConstraint(
203 | NSLayoutConstraint(
204 | item: tl, attribute: .centerY,
205 | relatedBy: .equal,
206 | toItem: self, attribute: .centerY,
207 | multiplier: 1, constant: 0
208 | )
209 | )
210 |
211 | let rtl = self.userInterfaceLayoutDirection == .rightToLeft
212 |
213 | self.addConstraint(
214 | NSLayoutConstraint(
215 | item: tl, attribute: rtl ? .right : .left,
216 | relatedBy: .equal,
217 | toItem: self, attribute: rtl ? .right : .left,
218 | multiplier: 1, constant: rtl ? -1 : 1
219 | )
220 | )
221 |
222 | self.addConstraint(
223 | NSLayoutConstraint(
224 | item: tl, attribute: rtl ? .left : .right,
225 | relatedBy: rtl ? .greaterThanOrEqual : .lessThanOrEqual,
226 | toItem: self, attribute: rtl ? .left : .right,
227 | multiplier: 1, constant: 0
228 | )
229 | )
230 |
231 | self.widthConstraint = NSLayoutConstraint(
232 | item: tl, attribute: .width,
233 | relatedBy: .equal,
234 | toItem: nil, attribute: .notAnAttribute,
235 | multiplier: 1, constant: self.labelWidth == -1 ? 20 : self.labelWidth
236 | )
237 |
238 | self.addConstraint(self.widthConstraint)
239 | self.widthConstraint.isActive = self.labelWidth != -1
240 |
241 | self.syncLabelVisibility()
242 | }
243 | }
244 |
245 | public extension DSFLabelledTextField {
246 | private func syncLabelVisibility() {
247 | self.textLabel.alphaValue = self.isEnabled ? 1.0 : alphaValueForDisabled
248 | self.needsDisplay = true
249 | }
250 |
251 | override func viewDidMoveToWindow() {
252 | super.viewDidMoveToWindow()
253 | self.addObserver(self, forKeyPath: "enabled", options: .new, context: nil)
254 |
255 | self.syncLabelVisibility()
256 | }
257 |
258 | override func viewWillMove(toWindow newWindow: NSWindow?) {
259 | super.viewWillMove(toWindow: newWindow)
260 | guard let _ = newWindow else {
261 | self.removeObserver(self, forKeyPath: "enabled")
262 | return
263 | }
264 | }
265 |
266 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
267 | if keyPath == "enabled" {
268 | self.syncLabelVisibility()
269 | }
270 | else {
271 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
272 | }
273 | }
274 |
275 | /// Returns the size of the text within the label regardless of any width constraints set.
276 | var labelTextSize: CGSize {
277 | return self.textLabel.fittingSize
278 | }
279 |
280 | override func drawFocusRingMask() {
281 | let pth = NSBezierPath(roundedRect: self.bounds.insetBy(dx: 0.5, dy: 0.5), xRadius: 2, yRadius: 2)
282 | pth.fill()
283 | }
284 |
285 | override func layout() {
286 | super.layout()
287 |
288 | if self.labelWidth == -1 {
289 | self.customCell.labelWidth = self.textLabel.fittingSize.width
290 | }
291 | else {
292 | self.customCell.labelWidth = self.labelWidth + ((self.textLabel.alignment != .center) ? 3.0 : 0.0)
293 | }
294 | }
295 |
296 | // Convenience for the embedded text cell
297 | private var customCell: DSFPlainTextFieldCell {
298 | return self.cell as! DSFPlainTextFieldCell
299 | }
300 | }
301 |
302 | public extension DSFLabelledTextField {
303 | override func accessibilityValueDescription() -> String? {
304 | let labelled = "Text field is labelled '\(self.label)'"
305 | return labelled
306 | }
307 |
308 | override func accessibilityRoleDescription() -> String? {
309 | let labelled = "Text field is labelled '\(self.label)'"
310 | return labelled
311 | }
312 | }
313 |
314 | private class DSFPlainTextFieldCell: NSTextFieldCell {
315 | @inlinable var isRTL: Bool {
316 | return self.userInterfaceLayoutDirection == .rightToLeft
317 | }
318 |
319 | fileprivate var labelWidth: CGFloat = 20
320 | fileprivate var drawsLabelBackground: Bool = true
321 | fileprivate var roundedEdges: Bool = true
322 | fileprivate var labelBackgroundColor = NSColor.windowBackgroundColor
323 |
324 | private func tweak(_ rect: CGRect) -> NSRect {
325 | let offset: CGFloat = self.labelWidth
326 | var newRect = rect
327 |
328 | newRect.size.width -= offset
329 |
330 | if self.isRTL {
331 | newRect.origin.x = newRect.minX
332 | }
333 | else {
334 | newRect.origin.x = offset
335 |
336 | if self.bezelStyle == .roundedBezel {
337 | newRect.origin.x -= 8
338 | newRect.size.width += 14
339 | }
340 | else {
341 | newRect.size.width -= 2
342 | }
343 | }
344 |
345 | return newRect
346 | }
347 |
348 | override func select(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, start selStart: Int, length selLength: Int) {
349 | super.select(withFrame: self.tweak(rect), in: controlView, editor: textObj, delegate: delegate, start: selStart, length: selLength)
350 | }
351 |
352 | override func edit(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, event: NSEvent?) {
353 | super.edit(withFrame: self.tweak(rect), in: controlView, editor: textObj, delegate: delegate, event: event)
354 | }
355 |
356 | static let DarkEnabledBorderColor = NSColor(calibratedWhite: 1.0, alpha: 0.15)
357 | static let DarkNotEnabledBorderColor = NSColor(calibratedWhite: 1.0, alpha: 0.05)
358 | static let LightEnabledBorderColor = NSColor(calibratedWhite: 0.5, alpha: 0.4)
359 | static let LightNotEnabledBorderColor = NSColor(calibratedWhite: 0.5, alpha: 0.3)
360 |
361 | static let HighContrastBorderColor = NSColor.textColor
362 |
363 | @inlinable var borderColor: NSColor {
364 |
365 | if DSFAppearanceManager.IncreaseContrast {
366 | return isEnabled ? Self.HighContrastBorderColor : Self.HighContrastBorderColor.withAlphaComponent(0.4)
367 | }
368 |
369 | if controlView?.isDarkMode ?? false {
370 | if isEnabled {
371 | return Self.DarkEnabledBorderColor
372 | }
373 | else {
374 | return Self.DarkNotEnabledBorderColor
375 | }
376 | }
377 | else {
378 | if isEnabled {
379 | return Self.LightEnabledBorderColor
380 | }
381 | else {
382 | return Self.LightNotEnabledBorderColor
383 | }
384 | }
385 | }
386 |
387 | static let DarkEnabledBackgroundColor = NSColor.init(calibratedWhite: 1.0, alpha: 0.075)
388 | static let DarkNotEnabledBackgroundColor = NSColor.init(calibratedWhite: 0.3, alpha: 0.04)
389 | static let LightEnabledBackgroundColor = NSColor.white
390 | static let LightNotEnabledBackgroundColor = NSColor(calibratedWhite: 1.0, alpha: 0.3)
391 |
392 | @inlinable var cellBackgroundColor: NSColor {
393 | if controlView?.isDarkMode ?? false {
394 | if isEnabled {
395 | return Self.DarkEnabledBackgroundColor
396 | }
397 | else {
398 | return Self.DarkNotEnabledBackgroundColor
399 | }
400 | }
401 | else {
402 | if isEnabled {
403 | return Self.LightEnabledBackgroundColor
404 | }
405 | else {
406 | return Self.LightNotEnabledBackgroundColor
407 | }
408 | }
409 | }
410 |
411 | override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
412 |
413 | guard let field = self.controlView,
414 | let window = field.window else {
415 | return
416 | }
417 |
418 | NSGraphicsContext.usingGraphicsState { ctx in
419 | let darkMode = field.isDarkMode
420 |
421 | self.borderColor.setStroke()
422 | self.cellBackgroundColor.setFill()
423 |
424 | let pth: NSBezierPath
425 | if self.roundedEdges {
426 | pth = NSBezierPath(roundedRect: cellFrame.insetBy(dx: 1, dy: 1), xRadius: 2, yRadius: 2)
427 | pth.lineWidth = 1
428 | }
429 | else {
430 | pth = NSBezierPath(rect: cellFrame.insetBy(dx: 1, dy: 1))
431 | pth.lineWidth = 1
432 | }
433 | pth.stroke()
434 | pth.fill()
435 |
436 | let inset: CGFloat = darkMode ? 1.5 : 1.0
437 | let rad: CGFloat = darkMode ? 1 : 2.0
438 | let fillPth = NSBezierPath(roundedRect: cellFrame.insetBy(dx: inset, dy: inset), xRadius: rad, yRadius: rad)
439 |
440 | fillPth.setClip()
441 |
442 | if self.drawsLabelBackground {
443 |
444 | // Do our line drawing without antialiasing.
445 |
446 | self.labelBackgroundColor.setFill()
447 |
448 | let split = cellFrame.divided(atDistance: self.labelWidth, from: self.isRTL ? .maxXEdge : .minXEdge)
449 | //let split = cellFrame.divided(atDistance: self.labelWidth, from: .minXEdge)
450 | var labelRect = split.slice
451 |
452 | // Align the labelrect to pixels
453 | labelRect = window.backingAlignedRect(
454 | labelRect,
455 | options: [.alignMaxXInward, .alignMinXOutward, .alignMinYOutward, .alignMaxYInward])
456 |
457 | labelRect.fill()
458 |
459 | self.borderColor.setStroke()
460 |
461 | // Draw the label separator without antialiasing so we sit on pixel boundaries
462 |
463 | ctx.disablingAntialiasing {
464 | let inset = self.isRTL ? self.labelWidth : 0
465 | let line = NSBezierPath()
466 | line.move(to: NSPoint(x: labelRect.maxX - inset, y: cellFrame.minY))
467 | line.line(to: NSPoint(x: labelRect.maxX - inset, y: cellFrame.maxY))
468 | line.close()
469 | line.lineWidth = darkMode ? 1 : 0.5
470 | line.stroke()
471 | }
472 | }
473 | }
474 |
475 | self.drawInterior(withFrame: self.tweak(cellFrame), in: controlView)
476 | }
477 | }
478 |
479 | #endif
480 |
--------------------------------------------------------------------------------
/Demos/DSFLabelledTextField Demo/DSFLabelledTextField Demo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
933 |
934 |
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 |
944 |
945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
954 |
955 |
956 |
957 |
958 |
959 |
960 |
961 |
962 |
963 |
964 |
965 |
966 |
967 |
968 |
969 |
970 |
971 |
972 |
973 |
974 |
975 |
976 |
977 |
978 |
979 |
980 |
981 |
982 |
983 |
984 |
985 |
986 |
987 |
988 |
989 |
990 |
991 |
992 |
993 |
994 |
995 |
996 |
997 |
998 |
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1007 |
1008 |
1009 |
1010 |
1011 |
1012 |
1013 |
1014 |
1015 |
1016 |
1017 |
1018 |
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
1026 |
1027 |
1028 |
1029 |
1030 |
1031 |
1032 |
1033 |
1034 |
1035 |
1036 |
1037 |
1038 |
1039 |
1040 |
1041 |
1042 |
1043 |
1044 |
1045 |
1046 |
1047 |
1048 |
1049 |
1050 |
1051 |
1052 |
1053 |
1054 |
1055 |
1056 |
1057 |
1058 |
1059 |
1060 |
1061 |
1062 |
1063 |
1064 |
1065 |
1066 |
1067 |
1068 |
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
1075 |
1076 |
1077 |
1078 |
1079 |
1080 |
1081 |
1082 |
1083 |
1084 |
1085 |
1086 |
1087 |
1088 |
1089 |
1090 |
1091 |
1092 |
1093 |
1094 |
1095 |
1096 |
1097 |
1098 |
1099 |
1100 |
1101 |
1102 |
1103 |
1104 |
1105 |
1106 |
1107 |
1108 |
1109 |
1110 |
1111 |
1112 |
1113 |
1114 |
1115 |
1116 |
1117 |
1118 |
1119 |
1120 |
1121 |
1122 |
1123 |
1124 |
1125 |
1126 |
1127 |
1128 |
1129 |
1130 |
1131 |
1132 |
1133 |
1134 |
1135 |
1136 |
1137 |
1138 |
1139 |
1140 |
1141 |
1142 |
1143 |
1144 |
1145 |
1146 |
1147 |
1148 |
1149 |
1150 |
1151 |
1152 |
1153 |
1154 |
1155 |
1156 |
1157 |
1158 |
1159 |
1160 |
1161 |
1162 |
1163 |
1164 |
1165 |
1166 |
1167 |
1168 |
1169 |
1170 |
1171 |
1172 |
1173 |
1174 |
1175 |
1176 |
1177 |
1178 |
1179 |
1180 |
1181 |
1182 |
1183 |
1184 |
1185 |
1186 |
1187 |
1188 |
1189 |
1190 |
1191 |
1192 |
1193 |
1194 |
1195 |
1196 |
1197 |
1198 |
1199 |
1200 |
1201 |
1202 |
1203 |
1204 |
1205 |
1206 |
1207 |
1208 |
1209 |
1210 |
1211 |
1212 |
1213 |
1214 |
1215 |
1216 |
1217 |
1218 |
1219 |
1220 |
1221 |
1222 |
1223 |
1224 |
1225 |
1226 |
1227 |
1228 |
1229 |
1230 |
1231 |
1232 |
1233 |
1234 |
1235 |
1236 |
1237 |
1238 |
1239 |
1240 |
1241 |
1242 |
1243 |
1244 |
1245 |
1246 |
1247 |
1248 |
1249 |
1250 |
1251 |
1252 |
1253 | This is a wrapping text field that has an empty label header and a fixed label width. Could be used to highlight (for example) a field that needs completion
1254 |
1255 |
1256 |
1257 |
1258 |
1259 |
1260 |
1261 |
1262 |
1263 |
1264 |
1265 |
1266 |
1267 |
1268 |
1269 |
1270 |
1271 |
1272 |
1273 |
1274 |
1275 |
1276 |
1277 |
1278 |
1279 |
1280 |
1281 |
1282 |
1283 |
1284 |
1285 |
1286 |
1287 |
1288 |
1289 |
1290 |
1291 |
1301 |
1302 |
1303 |
1304 |
1305 |
1306 |
1307 |
1308 |
1309 |
1310 |
1311 |
1312 |
1313 |
1314 |
1315 |
1316 |
1317 |
1318 |
1319 |
1320 |
1321 |
1322 |
1323 |
1324 |
1325 |
1326 |
1327 |
1328 |
1329 |
1330 |
1331 |
1332 |
1333 |
1334 |
1335 |
1336 |
1337 |
1338 |
1339 |
1340 |
1341 |
1342 |
1343 |
1344 |
1345 |
1346 |
1347 |
1348 |
1349 |
1350 |
1351 |
1352 |
1353 |
1354 |
1355 |
1356 |
1357 |
1358 |
1359 |
1360 |
1361 |
1362 |
1363 |
1364 |
1365 |
1366 |
1367 |
1368 |
1369 |
1370 |
1371 |
1372 |
1373 |
--------------------------------------------------------------------------------