The Apple documentation shows an unsettling blank space under the 'Creating a Dictionary' section of the UIKit reference here.
Has anyone found a replacement for the NSDictionaryOfVariableBindings macro, or are we expected to just write our own?
EDIT - According to this perhaps the right approach is to write a global function to handle this? Looks like complex macros are out entirely.
According to Apple source code:
NSDictionaryOfVariableBindings(v1, v2, v3) is equivalent to [NSDictionary dictionaryWithObjectsAndKeys:v1, #"v1", v2, #"v2", v3, #"v3", nil];
So in Swift you can do the same using:
let bindings = ["v1": v1, "v2": v2, "v3": v3]
NSDictionaryOfVariableBindings is, as you say, a macro. There are no macros in Swift. So much for that.
Nonetheless, you can easily write a Swift function to assign string names to your views in a dictionary, and then pass that dictionary into constraintsWithVisualFormat. The difference is that, unlike Objective-C, Swift can't see your names for those views; you will have to let it make up some new names.
[To be clear, it isn't that your Objective-C code could see your variable names; it's that, at macro evaluation time, the preprocessor was operating on your source code as text and rewriting it — and so it could just use the text of your variable names both inside quotes (to make strings) and outside (to make values) to form a dictionary. But with Swift, there is no preprocessor.]
So, here's what I do:
func dictionaryOfNames(arr:UIView...) -> Dictionary<String,UIView> {
var d = Dictionary<String,UIView>()
for (ix,v) in arr.enumerate(){
d["v\(ix+1)"] = v
}
return d
}
And you call it and use it like this:
let d = dictionaryOfNames(myView, myOtherView, myFantasicView)
myView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[v2]|", options: nil, metrics: nil, views: d)
)
The catch is that it is up to you to realize that the name for myOtherView in your visual format string will be v2 (because it was second in the list passed in to dictionaryOfNames()). But I can live with that just to avoid the tedium of typing out the dictionary by hand every time.
Of course, you could equally have written more or less this same function in Objective-C. It's just that you didn't bother because the macro already existed!
That functionality is based on macro expansion which is currently not supported in Swift.
I do not think there is any way to do something similar in Swift at the moment. I believe you cannot write your own replacement.
I'm afraid you'll have to manually unroll the dictionary definition, even if it means repeating each name twice.
So I hacked something together which seems to work:
func dictionaryOfVariableBindings(container: Any, views:UIView...) -> Dictionary<String, UIView> {
var d = Dictionary<String, UIView>()
let mirror = Mirror(reflecting: container)
let _ = mirror.children.compactMap {
guard let name = $0.label, let view = $0.value as? UIView else { return }
guard views.contains(view) else { return }
d[name] = view
}
return d
}
Usage:
let views = dictionaryOfVariableBindings(container: self, views: imageView)
ObjC runtime to the rescue!
i created an alternate solution, but it only works if each of the views are instance variables of the same object.
func DictionaryOfInstanceVariables(container:AnyObject, objects: String ...) -> [String:AnyObject] {
var views = [String:AnyObject]()
for objectName in objects {
guard let object = object_getIvar(container, class_getInstanceVariable(container.dynamicType, objectName)) else {
assertionFailure("\(objectName) is not an ivar of: \(container)");
continue
}
views[objectName] = object
}
return views
}
can be used like this:
class ViewController: UIViewController {
var childA: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.redColor()
return view
}()
var childB: UIButton = {
let view = UIButton()
view.setTitle("asdf", forState: .Normal)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.blueColor()
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(childA)
self.view.addSubview(childB)
let views = DictionaryOfInstanceVariables(self, objects: "childA", "childB")
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[childA]|", options: [], metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[childB]|", options: [], metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[childA][childB(==childA)]|", options: [], metrics: nil, views: views))
}
}
unfortunately you still have to type the variable name in as a string, but it will at least assert if there is a typo. this definitely won't work in all situations, but helpful nonetheless
Based on https://stackoverflow.com/a/55086673/1058199 by cherpak-evgeny, this UIViewController extension assumes that the container is self, the current viewController instance.
extension UIViewController {
// Alex Zavatone 06/04/2019
// Using reflection, get the string name of the UIView properties passed in
// to create a dictionary of ["viewPropertyName": viewPropertyObject…] like
// Objective-C's NSDictionaryForVariableBindings.
func dictionaryOfBindings(_ arrayOfViews:[UIView?]) -> Dictionary<String, UIView> {
var bindings = Dictionary<String, UIView>()
let viewMirror = Mirror(reflecting: self)
let _ = viewMirror.children.compactMap {
guard let name = $0.label, let view = $0.value as? UIView else { return }
guard arrayOfViews.contains(view) else { return }
bindings[name] = view
}
return bindings
}
}
Use it like so from within your viewController:
let viewArray = [mySwitch, myField, mySpinner, aStepper, someView]
let constraintsDictionary = dictionaryOfBindings(viewArray)
Tested in Xcode 10.2.1 and Swift 4.2.
Many thanks to Cherpak Evgeny for writing it in the first place.
Once you've stored all your views as properties, you could also use reflection like so:
extension ViewController {
func views() -> Dictionary<String, AnyObject> {
var views = dictionaryOfProperties()
views.forEach {
if !($1 is UIView) {
views[$0] = nil
}
}
return views
}
}
extension NSObject {
func dictionaryOfProperties() -> Dictionary<String, AnyObject> {
var result = Dictionary<String, AnyObject>()
let mirror = Mirror(reflecting: self)
for case let(label?, value) in mirror.children {
result[label] = value as? AnyObject
}
return result
}
}
In my Objective C code I had this:
if ([view conformsToProtocol:#protocol(UITextInputTraits)]) {
id<UITextInputTraits> field = view;
field.enablesReturnKeyAutomatically = YES;
}
Now I'm trying to convert that to swift, so I did this:
if var field = view as? UITextInputTraits {
field.enabledReturnKeyAutomatically = true
}
I'm getting a compiler error saying that 'field' is immutable. What's the right way to accomplish this?
The problem is caused by Swift's peculiar way of dealing with optional protocol requirements. Optional protocol properties have no setter. (I regard this as a bug in the language.) You'll have to work around it.
You can say (horrible):
switch view {
case let field as UITextField:
field.enablesReturnKeyAutomatically = true
case let field as UITextView:
field.enablesReturnKeyAutomatically = true
default: break
}
Another way (equally horrible):
let setter = #selector(setter:UITextInputTraits.enablesReturnKeyAutomatically)
if view.responds(to:setter) {
view.perform(setter, with: 1 as NSNumber)
}
I making an app that uses EZAudio library, and it have to get data from the microphone and store it in variable (i already have done this) :
func microphone(microphone: EZMicrophone!,
hasAudioReceived buffer: UnsafeMutablePointer<UnsafeMutablePointer<Float>>,
withBufferSize bufferSize: UInt32,
withNumberOfChannels numberOfChannels: UInt32)
{
let new = (buffer[0].memory)
audioStack.append(new)
}
As you can see, i`m using EZMicrophoneDelegate, and func above, to get buffer data from microphone.
The future idea is to send the values by WebSocket and play immediately one by one.
I want playback that array, using EZOuput and EZOuputDataSource`s method:
func output(output: EZOutput!,
shouldFillAudioBufferList audioBufferList: UnsafeMutablePointer<AudioBufferList>,
withNumberOfFrames frames: UInt32,
timestamp: UnsafePointer<AudioTimeStamp>) -> OSStatus
{
if audioStack.count > 0 {
let data = audioBufferList.memory.mBuffers.mData
// "data" = is pointer to mData, that i want to fill with float
// and I have do it here
return noErr
}
But the type of "mData" is UnsafeMutablePointer, and I don`t know how to fill it, I saw some examples in objective-C like this, exactly this line:
Float32 *buffer = (Float32 *)audioBufferList->mBuffers[0].mData;
and I can`t understand how I have to store my Float type from array to mData with Void type, in swift?
Casting like:
(audioBufferList.memory.mBuffers.mData) as Float = audioStack[0]
fails..
Looks like i found how to cast an UnsafeMurtablePointer to UnsafeMutablePointer :
let data = UnsafeMutablePointer<Float>(audioBufferList.memory.mBuffers.mData)
I am currently making a calculator app and I need to use the bridgeToObjectiveC code but that is no longer available in the current beta version of xcode 6. This is the code I need to write:
#IBAction func btnAdditionCalculate(sender: AnyObject) {
let firstNumber = txtAdditionFirst.text.bridgeToObjectiveC().floatValue
let secondNumber = txtAdditionSecond.text.bridgeToObjectiveC().floatValue
let answer = firstNumber + secondNumber
var answerFormat: NSString = NSString(format: "%0.0f", answer)
labelAdditionAnswer.text = " \(answerFormat)"
}
I know I need to use NSString and I tried to do this but it doesn't work:
#IBAction func btnAdditionCalculate(sender: AnyObject) {
let firstNumber = (txtAdditionFirst.text as NSString).floatValue
let secondNumber = (txtAdditionSecond.text as NSString).floatValue
let answer = firstNumber + secondNumber
var answerFormat: NSString = NSString(format: "%0.0f", answer)
labelAdditionAnswer.text = " \(answerFormat)"
}
Thanks for all your help
edit 8/15
When I run the code on the iOS simulator, the app loads and I am able to put numbers into the number 1 and number 2 slot, however when I press calculate xcode highlights the let firstNumber line and says Thread 1:EXC_BAD_INSTRUCTION and this is the error message I get:
2014-08-15 16:14:53.019 Innovative Calculator p1[578:8032] Can't find keyplane that supports type 4 for keyboard iPhone-Portrait-NumberPad; using 3876877096_Portrait_iPhone-Simple-Pad_Default
2014-08-15 16:14:55.425 Innovative Calculator p1[578:8032] Can't find keyplane that supports type 8 for keyboard iPhone-Portrait-DecimalPad; using 1425143906_Portrait_iPhone-Simple-Pad_Default
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
If your code is halting on let firstNumber = ... with a nil optional conversion, it means you are incorrectly assuming that either txtAdditionFirst or textAdditionFirst.text is not nil. UITextField makes assurances that .text is never nil so the best bet is that you failed to wire up txtAdditionFirst in Interface Builder.
You can verify this by checking the variable definition to see it it is filled in or set a breakpoint above the line and see if it is nil.
If you have a valid reason for the variable to be nil, you have a couple options for doing this:
// The long way (mutable)
var firstNumber:Float = 0
if let value = textField?.text {
firstNumber = (value as NSString).floatValue
}
// The short way (not mutable)
let firstNumber = (textField?.text? as NSString?)?.floatValue ?? 0.0
The first way sets the number to zero initially then uses optional chaining to test if textField and textField.text are not nil. The second way uses optional chaining the whole way to get an optional float value, then unwraps it if not nil or falls back to 0.
I'm subclassing NSButtonCell to customize the drawing (customizable theme). I'd like to customize the way checkboxes and radio buttons are drawn.
Does anyone know how to detect whether a button is a checkbox or radio button?
There is only -setButtonType:, no getter, and neither -showsStateBy nor -highlightsBy seem to give any unique return values for checkboxes that don't also apply to regular push buttons with images and alternate images.
So far I've found two (not very pretty) workarounds, but they're the kind of thing that'd probably get the app rejected from MAS:
Use [self valueForKey: #"buttonType"]. This works, but since the method is not in the headers, I presume this is something Apple wouldn't want me to do.
Override -setButtonType: and -initWithCoder: to keep track of the button type when it is set manually or from the XIB. Trouble here is the XIB case, because the keys used to save the button type to disk are undocumented. So again, I'd be using private API.
I'd really like this to be a straight drop-in replacement for NSButtonCell instead of forcing client code to use a separate ULIThemeSwitchButtonCell class for checkboxes and a third one for radio buttons.
A button does not know anything about its style.
From the documentation on NSButton
Note that there is no -buttonType method. The set method sets various button properties that together establish the behavior of the type. -
You could use tag: and setTag: (inherited by NSButton from NSControl) in order to mark the button either as a checkbox or a radio button. If you do that programatically then you should define the constant you use. You can also set the tag in Interface Builder, but only as an integer value (magic number).
In initWithCoder, here is my adaptation of the BGHUDButtonCell.m solution, updated for Mac OS Sierra:
-(id)initWithCoder:(NSCoder *)aDecoder {
if ( !(self = [super initWithCoder: aDecoder]) ) return nil;
NSImage *normalImage = [aDecoder decodeObjectForKey:#"NSNormalImage"];
if ( [normalImage isKindOfClass:[NSImage class]] )
{
DLog( #"buttonname %#", [normalImage name] );
if ( [[normalImage name] isEqualToString:#"NSSwitch"] )
bgButtonType = kBGButtonTypeSwitch;
else if ( [[normalImage name] isEqualToString:#"NSRadioButton"] )
bgButtonType = kBGButtonTypeRadio;
}
else
{
// Mac OS Sierra update (description has word "checkbox")
NSImage *img = [self image];
if ( img && [[img description] rangeOfString:#"checkbox"].length )
{
bgButtonType = kBGButtonTypeSwitch;
}
}
}
This is strange to me that it's missing from NSButton. I don't get it. That said, it's easy enough to extend NSButton to store the last set value:
import Cocoa
public class TypedButton: NSButton {
private var _buttonType: NSButton.ButtonType = .momentaryLight
public var buttonType: NSButton.ButtonType {
return _buttonType
}
override public func setButtonType(_ type: NSButton.ButtonType) {
super.setButtonType(type)
_buttonType = type
}
}
Swift 5.5
This is my approach. I use a standard naming convention in my app that relies on plain language identifiers. All my UI elements incorporate their respective property names and what type of UI element is associated with the property. It can make for some pretty long IBOutlet and IBAction names, but remembering tag numbers is way too complicated for me.
For example:
#IBOutlet weak var serveBeerCheckbox: NSButton!
#IBOutlet weak var headSize0RadioButton: NSButton!
#IBOutlet weak var headSize1RadioButton: NSButton!
#IBOutlet weak var headSize2RadioButton: NSButton!
\\ etc.
If there are UI properties that need to be stored, I name those without the type of UI element:
var serveBeer: Bool = true
var headSize: Int = 1
Bare bones example:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var serveBeerCheckbox: NSButton!
#IBOutlet weak var headSize0RadioButton: NSButton!
#IBOutlet weak var headSize1RadioButton: NSButton!
#IBOutlet weak var headSize2RadioButton: NSButton!
var serveBeer: Bool = true
var headSize: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonClicked(button: NSButton) {
guard let identifier = button.identifier else { return }
if identifier.rawValue.contains("Checkbox") {
switch button.identifier {
case serveBeerCheckbox.identifier:
// Do something with the Checkbox
serveBeer = (serveBeerCheckbox?.state == .on)
default:
// Another checkbox button
}
} else if identifier.rawValue.contains("RadioButton") {
switch button.identifier {
case headSize0RadioButton.identifier:
headSize = 0
case headSize1RadioButton.identifier:
headSize = 1
case headSize2RadioButton.identifier:
headSize = 2
default:
}
} // You could continue checking for different types of buttons
print("Serve beer? \(serveBeer ? "Sure!" : "Sorry, no.")")
if serveBeer {
switch headSize {
case 1:
print("With one inch of head.")
case 2:
print("With two inches of head!")
default:
print("Sorry, no head with your beer.")
}
}
}
}
As you can see, one could write a very generic method that can work on any type of UI element and use the rawValue of the identifier string with .contains() to isolate the type of element being worked with.
I have found using this approach allows me to initialize a UI with a lot of different elements pretty quickly and efficiently without having to recall tag numbers.