How to programmatically create several NSTextFields? - swift2

I have an array of CGPoints. I need a personal label for every fourth point in array, so I need to create several NSTextFields programmatically. I can add points with mouse clicks and can create as many points as I wish. Labels for these points must be all active to show text for user simultaneously. How can I do it?
(macOS, Xcode 7, Swift 2)
Here's my code:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var drawView: DrawView!
#IBOutlet weak var view: NSView!
let label = NSTextField(frame: NSMakeRect(0,0,100,50))
var pointsArray: [CGPoint] = []
func applicationWillUpdate(aNotification: NSNotification) {
label.backgroundColor = NSColor.clearColor()
label.bezeled = false
label.stringValue = "\(pointsArray.count/4)"
var multiple = (1...25).map { _ in label }
for index in 0..<(pointsArray.count/4) {
let point = CGPoint(x: pointsArray[index*4].x, y: pointsArray[index*4].y)
label.frame = CGRect(origin: point, size: CGSize(width: label.bounds.width, height: label.bounds.height))
let sticker = multiple[index]
view.addSubview(sticker)
}
}
}
At runtime I see only one label but I need to see several labels simultaneously (on every fourth CGPoint). If I have 100 CGPoints I must have 25 labels.

I see only one label
Now that I've straightened out your curly braces and indentation, it's easy to see why. Your loop is incorrectly constructed, so that you create one label and change its frame four times. You need to create four separate labels with four separate frames.

Code for creating several NSTextFields:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var drawView: DrawView!
var pointsArray: [CGPoint] = []
var label1 = NSTextField(frame: NSMakeRect(0,0,100,50))
var label2 = NSTextField(frame: NSMakeRect(0,0,100,50))
var label3 = NSTextField(frame: NSMakeRect(0,0,100,50))
// ....................................................
var label25 = NSTextField(frame: NSMakeRect(0,0,100,50))
override func awakeFromNib() {
super.awakeFromNib()
var labelArray = [label1, label2, label3, ....., label25]
for i in 0 ..< (pointsArray.count / 4) {
labelArray[i].backgroundColor = NSColor.clearColor()
labelArray[i].bezeled = false
labelArray[i].stringValue = "\(i + 1)"
let point = CGPoint(x: (pointsArray[i * 4].x),
y: (pointsArray[i * 4].y))
var originPoint: [CGPoint] = []
originPoint.append(point)
labelArray[i].frame = .init(origin: originPoint[0],
size: CGSize(width: labelArray[i].bounds.width,
height: labelArray[i].bounds.height))
self.view.addSubview(labelArray[i])
}
}
}

Related

Swift 4: How to change font attributes of number in multiple UITextFields when .count > 3 and how to reverse calculation?

I have three UITextFields that will only contain a whole number between 0 and either 13066 or 3915 maximum. It's presented with a special font where font1 at 50pt is larger than font2 at 50pt so I only need to play around with the font file, not the size.
1) I need help finding a way to have the hundreds presented with font2 but when the number >= 1000, the digits for thousands is presented with font1, while the hundreds still maintain font2. As this is a UITextField input, I need this to happen real-time.
Animated picture of how the end result eventually will be!
2) If you watch the animation, you'll see that each of the three fields totals to the field at the bottom. This is straight forward. However, I also want to reverse this, i.e filling in the total and the dials and digits should fill in as shown (half in each MAIN up to 3920 and remaining in CTR up to 13066). How do I code a functioning reversible calculation like that and avoid conflict?
This is what I got so far but it doesn't quite do what I want yet:
`class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var centerField: UITextField!
#IBOutlet weak var main1Field: UITextField!
#IBOutlet weak var main2Field: UITextField!
#IBOutlet weak var totalField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//UITextField delegation
centerField.delegate = self
main1Field.delegate = self
main2Field.delegate = self
totalField.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//Change font when number input exceeds 3 digits
func getAttributedString(for number: Int) -> NSAttributedString {
let defaultAttributes = [
NSAttributedStringKey.font: UIFont(name: "PMDG_777_DU_A", size: UIFont.labelFontSize)!
]
let bigNumberAttributes = [
NSAttributedStringKey.font: UIFont(name: "PMDG_777_DU_B", size: UIFont.labelFontSize)!
]
let attributedString = NSMutableAttributedString(string: "\(number)", attributes: defaultAttributes)
if attributedString.length > 3 {
let substr = attributedString.string.dropLast(3)
let range = NSMakeRange(0, substr.utf16.count)
attributedString.setAttributes(bigNumberAttributes, range: range)
}
return attributedString
}
//Hide keyboard when hitting Return
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
//Hide keyboard when tapping outside of field
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
//Real-time calculation of entries made to the fields and output it live to the total
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//Convert the String inserted into the fields into Int and create variables
let centerFuel = Int(centerField.text!) ?? 0
centerField.attributedText = getAttributedString(for: centerFuel)
let main1Fuel = Int(main1Field.text!) ?? 0
main1Field.attributedText = getAttributedString(for: main1Fuel)
let main2Fuel = Int(main2Field.text!) ?? 0
main2Field.attributedText = getAttributedString(for: main2Fuel)
let total: Int = centerFuel + main1Fuel + main2Fuel
totalField.attributedText = getAttributedString(for: total)
return true
}
Use NSMutableAttributedString:
func getAttributedString(for number: Int) -> NSAttributedString {
precondition(number >= 0)
let defaultAttributes = [
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 18)
]
let bigNumberAttributes = [
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 30)
]
let attributedString = NSMutableAttributedString(string: "\(number)", attributes: defaultAttributes)
if attributedString.length > 3 {
let range = NSMakeRange(0, attributedString.length - 3)
attributedString.setAttributes(bigNumberAttributes, range: range)
}
return attributedString
}
Then set the attributedText property on your label:
label.attributedText = getAttributedString(for: 13006)
And you can get result like this:
Adjust the styles to taste!
Here is a sample solution:
func attributedString(fromIntStr: String) -> NSAttributedString {
print("Working with: \(fromIntStr)")
let finalAttr = NSMutableAttributedString.init()
let length = fromIntStr.count
if length > 3 //we test this because we can't do dropLast(n) where n is negative
{
let start = String(fromIntStr.dropLast(3))
let firstPart = NSAttributedString.init(string: start,
attributes: [.font: UIFont.systemFont(ofSize: 20)])
finalAttr.append(firstPart)
}
var dropEnd = 0
if length > 3 //we test this because we can't do dropFirst(n) where n is negative
{
dropEnd = length - 3
}
else
{
dropEnd = 0 //That's just an explicit value but it was already set by default
}
let end = String(fromIntStr.dropFirst(dropEnd))
let lastPart = NSAttributedString.init(string: end,
attributes: [.font: UIFont.systemFont(ofSize: 15)])
finalAttr.append(lastPart)
//It seems that you want a right alignement and since we are using NSAttributedString we can do it by using ParagraphStyle
let paragraphStyle = NSMutableParagraphStyle.init()
paragraphStyle.alignment = .right
finalAttr.addAttribute(.paragraphStyle,
value: paragraphStyle,
range: NSRange(location: 0, length: finalAttr.length))
return finalAttr
}
It can be tested on Playground adding this at the end:
let values = ["", "9", "89", "789", "6789", "56789"]
let view = UIView.init(frame: CGRect(x: 0, y: 0, width: 800, height: 400))
for (i, str) in values.enumerated().reversed()
{
let label = UILabel.init(frame: CGRect(x: 0, y: i*50, width: 800, height: 50))
let attr = attributedString(fromIntStr: str)
label.attributedText = attr
view.addSubview(label)
}
view
There are various way to do this but here is the logic I used:
• Create a NSMutableAttributedString.
• Separate the string in two strings (one for 0-999, the other one for the rest)
• Create from them two NSAttributedString with the corresponding font
• Append them to the NSMutableAttributedString previously created.
Of course this could be improved:
Is dropFirst()/dropLast() the best solutions?
I supposed that you add already a String value from a Int (I didn't do the conversion).
Another solution more classic would have been:
• Create a NSMutableAttributedString from the whole String.
• Apply small font on range last from last-3
• Apply big font on range start to last -3
Last point be ommited if you apply the big font from the start on the whole string.

SKScene and Swift Files are not linking

In my xcode project I am trying to transition from one SKScene to another. What triggers the transition is the touching of a SKLabelNode. All of that is working correctly. But after the scene changes none of my code from my class that controls the "StartScence.sks" works. It seems as if my "StartScene.swift" and my "StartScene.sks" are not linked.
This is the code in my GameScene,
import SpriteKit
class GameScene: SKScene {
var isTouched: Bool = false
var booleanTouched: Bool!
let ns = SKScene(fileNamed: "StartScene")
let crosswf = SKTransition.crossFadeWithDuration(2)
override func didMoveToView(view: SKView) {
let backgroundimage = SKSpriteNode(imageNamed: "ipbg")
backgroundimage.size = CGSize(width: self.frame.size.width, height: self.frame.size.height)
backgroundimage.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2)
addChild(backgroundimage)
let playButton = SKLabelNode(fontNamed: "")
playButton.name = "play"
playButton.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2 + 100)
playButton.text = "Play"
let wait = SKAction.waitForDuration(2)
let run = SKAction.runBlock({
let randomNumber = Int(arc4random_uniform(UInt32(4)))
switch(randomNumber){
case (0):
playButton.fontColor = UIColor.blueColor()
case 1:
playButton.fontColor = UIColor.yellowColor()
case 2:
playButton.fontColor = UIColor.purpleColor()
case 3:
playButton.fontColor = UIColor.orangeColor()
default: print("default")
}
})
addChild(playButton)
var repeatActionForever = SKAction.repeatActionForever(SKAction.sequence([wait, run]))
runAction(repeatActionForever)
backgroundimage.zPosition = 1
playButton.zPosition = 2
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first! as UITouch
let touchLocation = touch.locationInNode(self)
let touchedNode = nodeAtPoint(touchLocation)
if (touchedNode.name == "play"){
scene!.view?.presentScene(ns!, transition: crosswf)
}else{
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
And this is the code that is my "StartScene.swift" that isnt controlling the "StartScene.sks" properly.
import SpriteKit
class StartScene: SKScene {
override func didMoveToView(view: SKView) {
print("Scene Loaded")
}
}
There's two things to be aware of.
In your code you are currently loading your .SKS file like this
let ns = SKScene(fileNamed: "StartScene")
Your new scene will load, as all SKS Files are of the class SKScene.
But, it will only use code from that class.
If you want it to load with the code in your class StartScene, a subclass of SKScene. Change the line to this
let ns = StartScene(fileNamed: "StartScene")
We can also make the SKS File have a custom class instead of it's default SKScene. So when it's loaded it uses a custom class.
Open the SKS File in Xcode so you can give the scene a Custom Class. In the scene editor and with nothing selected. Click in the utilities area, switch to the Custom Class Inspector, which is the last tab on the right.
Give it a Custom Class of StartScene.
It should work.

Dynamically adding cells to a NSMatrix laid out with Auto Layout has weird effects; why?

I want to create a group of radio buttons using the NSMatrix method that Interface Builder uses, but in code. The matrix is laid out using Auto Layout. I have it mostly working, except for when I add new options at runtime.
In the following example, clicking Append Item a few times will work fine, then the matrix starts going out of the window near the top (at least I think it's clipped at the top). If you maximize this window after adding a bunch of items, the window will stay the same height and all the items will be clipped to about a pixel high each, which is a very undesirable thing :)
In my real program (not this test below), it works mostly fine, but if I add an option dynamically, after certain numbers of items (initially 5), the options will clip very slightly, appearing slightly squeezed or squished. Adding another option reverts this until the next magic number is hit.
What's going on? I'm testing this on OS X Yosemite. Thanks.
// 17 august 2015
import Cocoa
var keepAliveMainwin: NSWindow? = nil
var matrix: NSMatrix? = nil
class ButtonHandler : NSObject {
#IBAction func onClicked(sender: AnyObject) {
var lastRow = matrix!.numberOfRows
matrix!.renewRows(lastRow + 1, columns: 1)
var cell = matrix!.cellAtRow(lastRow, column: 0) as! NSButtonCell
cell.title = "New Item"
matrix!.sizeToCells()
}
}
var buttonHandler: ButtonHandler = ButtonHandler()
func appLaunched() {
var mainwin = NSWindow(
contentRect: NSMakeRect(0, 0, 320, 240),
styleMask: (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask),
backing: NSBackingStoreType.Buffered,
defer: true)
var contentView = mainwin.contentView as! NSView
var prototype = NSButtonCell()
prototype.setButtonType(NSButtonType.RadioButton)
prototype.font = NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(NSControlSize.RegularControlSize))
matrix = NSMatrix(frame: NSZeroRect,
mode: NSMatrixMode.RadioModeMatrix,
prototype: prototype,
numberOfRows: 0,
numberOfColumns: 0)
matrix!.allowsEmptySelection = false
matrix!.selectionByRect = true
matrix!.intercellSpacing = NSMakeSize(4, 2)
matrix!.autorecalculatesCellSize = true
matrix!.drawsBackground = false
matrix!.drawsCellBackground = false
matrix!.autosizesCells = true
matrix!.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(matrix!)
var button = NSButton(frame: NSZeroRect)
button.title = "Append Item"
button.setButtonType(NSButtonType.MomentaryPushInButton)
button.bordered = true
button.bezelStyle = NSBezelStyle.RoundedBezelStyle
button.font = NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(NSControlSize.RegularControlSize))
button.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(button)
button.target = buttonHandler
button.action = "onClicked:"
var views: [String: NSView]
views = [
"button": button,
"matrix": matrix!,
]
addConstraints(contentView, "V:|-[matrix]-[button]-|", views)
addConstraints(contentView, "H:|-[matrix]-|", views)
addConstraints(contentView, "H:|-[button]-|", views)
mainwin.cascadeTopLeftFromPoint(NSMakePoint(20, 20))
mainwin.makeKeyAndOrderFront(mainwin)
keepAliveMainwin = mainwin
}
func addConstraints(view: NSView, constraint: String, views: [String: NSView]) {
var constraints = NSLayoutConstraint.constraintsWithVisualFormat(
constraint,
options: NSLayoutFormatOptions(0),
metrics: nil,
views: views)
view.addConstraints(constraints)
}
class appDelegate : NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(note: NSNotification) {
appLaunched()
}
func applicationShouldTerminateAfterLastWindowClosed(app: NSApplication) -> Bool {
return true
}
}
func main() {
var app = NSApplication.sharedApplication()
app.setActivationPolicy(NSApplicationActivationPolicy.Regular)
// NSApplication.delegate is weak; if we don't use the temporary variable, the delegate will die before it's used
var delegate = appDelegate()
app.delegate = delegate
app.run()
}
main()
Apparently, you need to omit the call to sizeToCells() after calling renewRows(_:columns:). My guess is that it sets the frame size, which is mostly useless when using auto layout, but also clears a "dirty" flag somewhere that tells the matrix that it needs to invalidate its intrinsic size. In other words, the matrix thinks it already did the re-layout stuff it needed to do.

Resizing the window according to a variable swift

I have a NSViewController and a variable num. I want to change the size of the window dynamically according to that variable. Is there any way to do that in swift?
Let's say your window has an IBOutlet named "window", and your dynamic number is named "myDynamicNumber":
func resize() {
var windowFrame = window.frame
let oldWidth = windowFrame.size.width
let oldHeight = windowFrame.size.height
let toAdd = CGFloat(myDynamicNumber)
let newWidth = oldWidth + toAdd
let newHeight = oldHeight + toAdd
windowFrame.size = NSMakeSize(newWidth, newHeight)
window.setFrame(windowFrame, display: true)
}
In Swift 3 to resize the window you use setFrame.
An example from the ViewController:
func resizeWin(size:(CGFloat,CGFloat)){
self.view.window?.setFrame(NSRect(x:0,y:0,width:size.0,height:size.1), display: true)
}
I needed to toggle viewing a text view so I overlaid the window an invisible view - hideRect just short of the text view; in this way I can resize to the smaller (hideRect) and restore later to the original size - origRect. Hide and original rect captured at viewDidLoad(). Swift 3/Xcode 8.3.3
// class global contants
let kTitleUtility = 16
let kTitleNormal = 22
#IBOutlet var hideView: NSView!
var hideRect: NSRect?
var origRect: NSRect?
#IBAction func toggleContent(_ sender: Any) {
// Toggle content visibility
if let window = self.view.window {
let oldSize = window.contentView?.bounds.size
var frame = window.frame
if toggleButton.state == NSOffState {
frame.origin.y += ((oldSize?.height)! - (hideRect?.size.height)!)
window.setFrameOrigin(frame.origin)
window.setContentSize((hideRect?.size)!)
window.showsResizeIndicator = false
window.minSize = NSMakeSize((hideRect?.size.width)!,(hideRect?.size.height)!+CGFloat(kTitleNormal))
creditScroll.isHidden = true
}
else
{
let hugeSize = NSMakeSize(CGFloat(Float.greatestFiniteMagnitude), CGFloat(Float.greatestFiniteMagnitude))
frame.origin.y += ((oldSize?.height)! - (origRect?.size.height)!)
window.setFrameOrigin(frame.origin)
window.setContentSize((origRect?.size)!)
window.showsResizeIndicator = true
window.minSize = NSMakeSize((origRect?.size.width)!,(origRect?.size.height)!+CGFloat(kTitleNormal))
window.maxSize = hugeSize
creditScroll.isHidden = false
}
}
}
This also preserved the widow's visual origin, and sizing minimum.

2 Text boxes optional inserting in one or both

I have two text fields to insert numbers, I am trying to find out how it could work out if the user is just inserting one value in one of the text fields or two. I tried to do it with if statements, but that does not work. Looks also like I have a mistake some where in the code I get the issue: fatal error: unexpectedly found nil while unwrapping an Optional value I would be happy for some help - as you can see I am a beginner in swift coding. Thanks so Far!
#IBOutlet var budgetShow: UILabel!
#IBOutlet var restShow: UILabel!
#IBOutlet var daysShow: UILabel!
#IBOutlet var dayssShow: UILabel!
#IBOutlet var inputDays: UITextField!
#IBOutlet var inputBudget: UITextField!
#IBAction func findResult(sender: AnyObject) {
var inputBudgetInt = inputBudget.text.toInt()
var inputDaysInt = inputDays.text.toInt()
var dailyRate = 85.50
if inputDays != nil {
var days = Double(inputBudgetInt!) / dailyRate
var daysCosts = dailyRate * days
var daysShow = Int(days)
var costsShow = Int(daysCosts)
dayssShow.text = "For \(costsShow) you can stay \(daysShow) Days."
}
if inputBudget != nil {
var budget = dailyRate / Double(inputBudgetInt!)
var budgetShow = Int(budget)
daysShow.text = "You need /(budgetShow)"
}
var daysBudget = Double(inputBudgetInt!) / Double(inputDaysInt!)
var show = daysBudget - dailyRate
var costs = Double(inputDaysInt!) * dailyRate
var rest = Double(inputBudgetInt!) - costs
var could = Double(inputBudgetInt!) / dailyRate
var couldShow = Int(could)
var restedShow = Int(rest)
var costShow = Int(costs)
var dayShow = Int(inputDaysInt!)
if show > 0 {
budgetShow.text = "For \(dayShow) Days you need \(costShow) €"
restShow.text = "Rest Budget after \(dayShow) Days is \(restedShow) €"
daysShow.text = "With your budget you could stay up to \(couldShow) days"
} else {
budgetShow.text = "Change your budget or days"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
One way to handle optionals safely is if let, like follows:
if let inputBudgetInt = inputBudgetInt {
// safely use it without need for "!" or "?"
var days = Double(inputBudgetInt) / dailyRate
}
This basically means "if this optional has a value, unwrap it and use it here".
I like to keep the name the same personally, you can use a different name if you want.
Another way is using "?", especially good for chaining. If you have a reference to a cell for example:
cell.textField?.text = "Foo"
If textField (optional) has no value, this line silently does nothing.
You should avoid "!" as much as possible as it can easily cause a crash. Only if you are 100% sure it has a value should you risk it. As a beginner I would stick to if let to make sure your optionals have values.

Resources