Multiple NSTextField in an NSAlert - MacOS - swift

I am putting together a login dialog using an NSAlert in Swift 4 on MacOS. Here is the code I have so far:
func dialogOKCancel(question: String, text: String) -> (String, String) {
let alert = NSAlert()
alert.messageText = question
alert.informativeText = text
alert.alertStyle = .warning
alert.addButton(withTitle: "Login")
alert.addButton(withTitle: "Cancel")
let unameField = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
let passField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
let stackViewer = NSStackView()
stackViewer.addSubview(unameField)
alert.accessoryView = stackViewer
let response: NSApplication.ModalResponse = alert.runModal()
if (response == NSApplication.ModalResponse.alertFirstButtonReturn) {
return (unameField.stringValue, passField.stringValue)
} else {
return ("", "")
}
}
This works just fine to show either the unameField or passField when I do alert.accessoryView = passField. But I want to show both fields in the same dialog. As you can see I've experimented with NSStackView (and others), but I have not found a solution to show both elements.

Hope will help you ...
You can use NSStackView and addSubview 2 item : unameField and passField.
But you must set frame for NSStackView and 2 item on NSStackView.
This is my code, you can refer:
let unameField = NSTextField(frame: NSRect(x: 0, y: 2, width: 200, height: 24))
let passField = NSSecureTextField(frame: NSRect(x: 0, y: 28, width: 200, height: 24))
let stackViewer = NSStackView(frame: NSRect(x: 0, y: 0, width: 200, height: 58))
stackViewer.addSubview(unameField)
stackViewer.addSubview(passField)
alert.accessoryView = stackViewer
And this is my result:
my result
My code: https://github.com/nhhthuanck/CustomizeNSAlertMacOS

Related

Why App is Crashed when Change Uiimage Color

I Have Changed Color Of Uiimage But App is crashed. I do this code in a loop. I have Added Autoreleasepool but not solve my problem.
func withColor(_ color: UIColor) -> UIImage {
//this method is extendtion of UIImage
UIGraphicsBeginImageContextWithOptions(size, false, scale)
guard let ctx = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return self }
color.setFill()
ctx.translateBy(x: 0, y: size.height)
ctx.scaleBy(x: 1.0, y: -1.0)
ctx.clip(to: CGRect(x: 0, y: 0, width: size.width, height: size.height), mask: cgImage)
ctx.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
guard let colored = UIGraphicsGetImageFromCurrentImageContext() else { return self }
UIGraphicsEndImageContext()
return colored
}
func getImage() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 600, height: 600))
for i in 0..<50{
let url = arrImages[i]
let elemetView = UIImageView(frame: CGRect(x: 0, y: 0, width: 600, height: 600))
elementView.imgVContent.image = UIImage(named: url)
if let imgColor = elementData.bgColor, imgColor != ""{
elementView.image = elementView.image?.withColor(UIColor(hexString: imgColor))
elementView.image.imgColor = UIColor(hexString: imgColor)
}
view.addSubview(elementView)
}
}
you have mentioned that you have used autorelease pool but I could not find it. May be trying using as below and see if it solves the problem.
In your getImage function, use your for loop with autoreleasepool like below.
for i in 0..<50{
autoreleasepool{
let url = arrImages[i]
let elemetView = UIImageView(frame: CGRect(x: 0, y: 0, width: 600, height: 600))
elementView.imgVContent.image = UIImage(named: url)
if let imgColor = elementData.bgColor, imgColor != ""{
elementView.image = elementView.image?.withColor(UIColor(hexString: imgColor))
elementView.image.imgColor = UIColor(hexString: imgColor)
}
view.addSubview(elementView)
}
}

Swift: NSStackView, bug only one subview is added

Hello in my code below I want to add NSImageView to my stackView, but there is a bug because there is only one that is added. The loop is 3 iterations so normally I should have 3 images:
let imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: 50, height: 50))
imageView.image = image.image
icons.forEach { _ in
stackImage.addArrangedSubview(imageView)
}
print(stackImage.subviews.count) // Outpout 1
Create the NSImageView instances inside the forloop.
And you need to check stackImage.arrangedSubviews.count not stackImage.subviews.count
var icons = [NSImage(named: ""),NSImage(named: ""),NSImage(named: "")]
icons.forEach { image in
let imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: 50, height: 50))
imageView.image = image
stackImage.addArrangedSubview(imageView)
}
print(stackImage.arrangedSubviews.count)

Swift UIView "Empty Image"

Could someone explain me the difference between these two pictures please?
Code with preview of the UIView:
Code without the preview of the UIView:
What's the difference? And why can't I get a preview on the second code example? It only shows "empty image"....
Thanks for helping me.
This seems to be a "feature" (bug) of a Swift playground. If you don't create the view instance using a non-zero frame width and height, you will get "empty image".
This works:
let rect = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
rect.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
rect.backgroundColor = .green
But this doesn't:
let rect = UIView(frame: .zero)
// also bad: let rect = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 0))
rect.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
rect.backgroundColor = .green
And remember that:
let rect = UIView()
is essentially the same as doing:
let rect = UIView(frame: .zero)
So when using a playground, create a view with a non-zero frame width and height in the initializer if you don't want to see "empty image".

How to make this UIButton's action operate correctly?

Every time I run this swift playground (on Xcode), I get an error on the last line saying:
'PlaygroundView' cannot be constructed because it has no accessible initialisers.
I also have another error on line 4 saying:
class 'PlaygroundView' has no initializers.
import UIKit
import PlaygroundSupport
class PlaygroundView:UIViewController {
var introImage:UIImageView
var introButton:UIButton
override func loadView() {
print("workspace started running.")
//Main Constant and Variable Declarations/General Setup
let mainView = UIView()
//Main View Modifications & styling
mainView.backgroundColor = UIColor.init(colorLiteralRed: 250, green: 250, blue: 250, alpha: 1)
func addItem(item: UIView) {
mainView.addSubview(item)
}
//Sub-Element Constant and Variable Declarations
introImage = UIImageView(frame: CGRect(x: 87.5, y: -300, width: 200, height: 200))
introImage.layer.borderColor = UIColor.black.cgColor
introImage.layer.borderWidth = 4
introImage.alpha = 0
introImage.transform = CGAffineTransform(rotationAngle: -90.0 * 3.14/180.0)
introImage.image = UIImage(named: "profile_pic.png")
introImage.image?.accessibilityFrame = introImage.frame
introImage.layer.cornerRadius = 100
addItem(item: introImage)
introButton = UIButton(frame: CGRect(x: 87.5, y: 900, width: 200, height: 30))
introButton.alpha = 0
introButton.setTitle("Tap to meet me", for: .normal)
introButton.titleLabel?.font = UIFont(name: "Avenir Book", size: 20)
introButton.backgroundColor = UIColor.black
introButton.layer.cornerRadius = 15
introButton.layer.borderWidth = 5
addItem(item: introButton)
introButton.addTarget(self, action: #selector(self.introButtonAction), for: .touchDown)
UIView.animate(withDuration: 1.5, delay: 1, options: .curveEaseInOut, animations: {
self.introImage.frame = CGRect(x: 87.5, y: 175, width: 200, height: 200)
self.introButton.frame = CGRect(x: 87.5, y: 400, width: 200, height: 30)
self.introImage.transform = CGAffineTransform(rotationAngle: 0.0 * 3.14/180.0)
self.introImage.alpha = 1
self.introButton.alpha = 1
}) { (mainAnimationComped) in
if mainAnimationComped == true {
print("introduction animation comped.")
}
}
}
//User Interface Actions
func introButtonAction() {
print("user interacted with user interface")
}
}
PlaygroundPage.current.liveView = PlaygroundView()
How would I fix this problem?
That's because in Swift, you need to initialize variables before use them in the code. On the other hand, since in this case you set them explicitly in your function, you might declare them as implicitly unwrapped optionals
var introImage:UIImageView!
var introButton:UIButton!
and this should work without changing anything else in your code
Add ? after introImage and introButton
var introImage:UIImageView?
var introButton:UIButton?

Change values of variables in class swift

I have 2 classed: Game and ViewController (subclass of NSViewController) that's Game class:
class Game {
required init(num : CGFloat, picName : String, gameName : String, description : String, windowHeight : CGFloat) {
self.number = num
self.pictureName = picName
self.name = gameName
self.desc = description
self.height = windowHeight
}
var number : CGFloat
var pictureName : String
var name : String
var desc : String
var height : CGFloat
var image : NSImage {
return NSImage(named: pictureName)!
}
var imageView : NSImageView {
return NSImageView(frame: NSRect(x: 0, y: height - (110 * (number - 1)) - 100, width: 100, height: 100))
}
var nameField : NSTextField {
return NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 100, width: 300, height: 30))
}
var descField : NSTextField {
return NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 140, width: 300, height: 60))
}
func setImage() {
imageView.image = image
}
func setNameField() {
nameField.font = NSFont(name: "System", size: 15.0)
nameField.stringValue = "Name"
nameField.alignment = NSTextAlignment(rawValue: 2)!
nameField.selectable = false
nameField.editable = false
}
func setDescField() {
descField.selectable = false
descField.editable = false
descField.stringValue = desc
descField.font = NSFont(name: "System", size: 11.0)
}
}
And in viewDidAppear I do this:
var size = self.view.window?.frame.size
var newGame = Game(num: 1, picName: "Bomb.png", gameName: "Bomber", description: "Just a simpla application", windowHeight: size!.height)
newGame.setDescField()
newGame.setImage()
newGame.setNameField()
println(newGame.descField.editable)
self.view.addSubview(newGame.imageView)
self.view.addSubview(newGame.descField)
self.view.addSubview(newGame.nameField)
And descField.editable is always true, even though I write descField.editable = false in function in class Game. How to fix it?
I found the solution. You need to declare all fields and image view as lazy variables and work with them in init:
import Foundation
import Cocoa
import AppKit
class Game {
init(num : CGFloat, picName : String, gameName : String, description : String, windowHeight : CGFloat, sender : NSViewController) {
self.number = num
self.pictureName = picName
self.name = gameName
self.desc = description
self.height = windowHeight
imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
nameField = NSTextField(frame: NSRect(x: 110, y: 60, width: 300, height: 30))
descField = NSTextField(frame: NSRect(x: 110, y: 0, width: 300, height: 60))
nameField.font = NSFont(name: "System", size: 15.0)
nameField.stringValue = self.name
nameField.alignment = NSTextAlignment(rawValue: 2)!
nameField.selectable = false
nameField.editable = false
nameField.backgroundColor = sender.view.window?.backgroundColor
nameField.bordered = false
descField.selectable = false
descField.editable = false
descField.stringValue = desc
descField.font = NSFont(name: "System", size: 11.0)
descField.backgroundColor = NSColor.controlColor()
descField.bordered = true
descField.backgroundColor = sender.view.window?.backgroundColor
imageView.image = NSImage(named: self.pictureName)!
gameView = NSView(frame: NSRect(x: 0, y: /*height - (100 * num)*/ height - (120 * num), width: 600, height: 100))
button = NSButton(frame: NSRect(x: 0, y: 0, width: 600, height: 100))
button.tag = Int(number)
button.action = nil
var gesture = NSClickGestureRecognizer(target: sender, action: Selector("buttonPressed:"))
gesture.numberOfClicksRequired = 1
gesture.buttonMask = 0x1
button.addGestureRecognizer(gesture)
button.bezelStyle = NSBezelStyle(rawValue: 6)!
button.transparent = true
gameView.addSubview(nameField)
gameView.addSubview(imageView)
gameView.addSubview(descField)
gameView.addSubview(button)
}
var number : CGFloat
var pictureName : String
var name : String
var desc : String
var height : CGFloat
lazy var button : NSButton = NSButton()
lazy var imageView : NSImageView = NSImageView()
lazy var nameField : NSTextField = NSTextField()
lazy var descField : NSTextField = NSTextField()
lazy var gameView : NSView = NSView()
}
And then you call this all like that:
var newGame = Game(num: number, picName: "Bomb2.png", gameName: "Bomber", description: "Just a simple application", windowHeight: size!.height, sender: self)
self.view.addSubview(newGame.gameView)
The way your code is written, you're generating a new instance of NSTextField every time you access nameField, and so on.
You want to use lazy instantiation, which is easily supported by the adding the 'lazy' keyword:
lazy var nameField : NSTextField {
return NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 100, width: 300, height: 30))
}
or even more simply:
lazy var nameField = NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 100, width: 300, height: 30))
Do the same for image, imageView, and descField.
How about like that:
var descField = NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 140, width: 300, height: 60))
var editable: Bool {
get {
return descField.editable
}
set {
descField.editable = newValue
}
}
And the you can use your game instance:
var newGame //......
newGame.editable = true