Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I am not sure how to phrase this better as a title but I need to make an NSSlider that functions as a normal volume knob. At the moment it will spin around as many times as I hold the mouse down and move it around the control. I need it to stop at the "0" position and the "100" position, I cannot have it jumping from 0 to 100 when I drag it the other way. I hope I am making this clear. Does anyone know how to do this or have any suggestions?
An old question, but here we go anyway:
(1) Set the slider to have 100 tickmarks
(2) Select "Only stop at tickmarks"
(3) In Interface Builder, set the custom cell class to SBCustomSliderCell
Use the code below:
#interface SBCustomSliderCell()
#property CGFloat lastSliderValue;
#end
#implementation SBCustomSliderCell
- (void)awakeFromNib
{
self.lastSliderValue = -1;
}
- (double)closestTickMarkValueToValue:(double)value
{
double proposedValue = [super closestTickMarkValueToValue:value];
if (self.lastSliderValue == -1)
{
self.lastSliderValue = proposedValue;
return proposedValue;
}
double MAX_JUMP = 50;
double tickDifference = ABS(self.lastSliderValue - proposedValue);
BOOL isTurningUp = proposedValue > self.lastSliderValue;
if (tickDifference > MAX_JUMP)
{
proposedValue = isTurningUp ? 0.0 : self.maxValue;
}
NSLog(#"value: %.2f gap: %.2f", proposedValue, tickDifference);
self.lastSliderValue = proposedValue;
return proposedValue;
}
- (NSRect)rectOfTickMarkAtIndex:(NSInteger)index
{
return NSZeroRect;
}
#end
I think you'll need to subclass NSSliderCell and override startTrackingAt:inView:, continueTracking:at:inView: and stopTracking:at:inView:mouseIsUp:.
The documentation of continueTracking:at:inView: says:
This method is invoked in trackMouse:inRect:ofView:untilMouseUp:. The default implementation returns YES if the cell is set to continuously send action messages to its target when the mouse button is down or the mouse is being dragged. Subclasses can override this method to provide more sophisticated tracking behavior.
Here's how to do it in Swift
This answer is based on Mark's "SBSLiderCell" answer above, but adapted for Swift.
Because it's a subclass of NSSliderCell, be sure to set the custom class in Interface Builder for the cell, and not the NSSlider superview.
Set the number of Tick Marks to your desired granularity (e.g.: 100) and click the "Only stop on tick marks" checkbox.
class PraxSliderCell : NSSliderCell {
var lastSliderValue: Double = -1
override func closestTickMarkValueToValue(value: Double) -> Double {
var proposedValue = super.closestTickMarkValueToValue(value)
if lastSliderValue == -1 { lastSliderValue = proposedValue }
else {
let tickDifference = abs(lastSliderValue - proposedValue)
let isTurningUp = proposedValue > lastSliderValue
if tickDifference > Double(numberOfTickMarks / 2) {
proposedValue = isTurningUp ? 0.0 : maxValue }
print (NSString(format: "value: %.2f gap: %.2f", proposedValue, tickDifference))
lastSliderValue = proposedValue
}
return proposedValue
}
override func rectOfTickMarkAtIndex(index: Int) -> NSRect { return NSZeroRect }
}
Related
After experimenting with a few little Swift programs, I decided my next step was to port a single module in an Objective-C program into Swift to see what steps were required. I had a number of issues, so I thought I'd post my process and results here in case others might find it useful.
I also created a table to help me remember the different conversions. Unfortunately, StackOverflow doesn't support tables, so I posted these conversions as a Github gist here.
Although Apple will undoubtedly provide an Xcode Refactor to convert from Objective-C to Swift, converting one manually is a great way to get familiar with the differences between the two languages. There is so much 'muscle memory' involved in a language you know well, and this is a great way to get familiar with the new syntax. As promised by Apple, it turns out the languages share so many common ideas, that it's mostly a mechanical process (as opposed to porting from, say C++ or even traditional C).
Note that this process uses none of the exciting new features of Swift, it only gets the code straight across. I should mention that moving to Swift will restrict any backwards compatability to iOS 7 or OS X 10.9. I also ran into a couple of issues (with workarounds below) that I'm sure are just due to the first beta release status of the project, so may not be required in future versions.
I chose iPhoneCoreDataRecipes and picked a module that didn’t rely on a lot of others: IngredientDetailViewController. If you'd like to follow along, check out my "answer" below.
Hope this is of use.
0) Download a copy of the project here and open Recipes.xcodeproj in Xcode version 6.
1) Choose File>New File…>iOS Source>Swift File> IngredientDetailViewController (Folder: Classes, Group: Recipe View Controllers)
2) Reply Yes to “Would you like to configure an Objective-C bridging header?”
3) Copy the first three lines below from Recipes_Prefix.pch and the next three from IngredientDetailViewController.m into Recipes-Bridging-Header.h. If you do further files, obviously don't duplicate lines, and remove any files that you've converted to Swift. I haven't found any where that documents the need for the Cocoa lines, given that they're imported in the swift file, but ...
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "Recipe.h"
#import "Ingredient.h"
#import "EditingTableViewCell.h"
4) Copy/paste the text from both the IngredientDetailViewController.h file and the IngredientDetailViewController.m files into IngredientDetailViewController.swift.
5) Delete both IngredientDetailViewController.h and .m files from project.
6) Do a global Find-and-Replace from #import "IngredientDetailViewController.h" to #import "Recipes-Swift.h" (Only one conversion in this case, and again for further files, don't duplicate this line in your Objective-C modules.)
7) Check the Project>Targets>Recipes>Build Settings Runpath Search Paths. If it shows $(inherited), remove this line or you'll get an error on launch about "no image found"
8) Convert Objective-C syntax in IngredientDetailViewController.swift to Swift. See the GitHub Gist mentioned above the substitutions required, or below for my converted version.
9) You may need to update the IB links. Do a Find>Find in Files on IngredientDetailViewController and select the one in Interface Builder. Open the Identity Inspector in the right-hand column. Select IngredientDetailViewController in the Class field, type xxx or something and tab.
10) Build and Run. Note that after going into a recipe, you must tap Edit and then the info button of an ingredient to activate IngredientDetailViewController
12) Congrats on building your first mixed Swift/Objective-C program!
Here's my cut at this particular module:
``
class IngredientDetailViewController: UITableViewController {
var recipe: Recipe!
var ingredient: Ingredient! {
willSet {
if let newIngredient = newValue {
self.ingredientStr = newIngredient.name
self.amountStr = newIngredient.amount
} else {
self.ingredientStr = ""
self.amountStr = ""
}
}
}
init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName:nibNameOrNil, bundle: nibBundleOrNil?)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
init(style: UITableViewStyle) {
super.init(style: style)
}
// MARK: table's data source
var ingredientStr: String?
var amountStr: String?
// view tags for each UITextField
let kIngredientFieldTag = 1
let kAmountFieldTag = 2
override func viewDidLoad () {
super.viewDidLoad()
self.title = "Ingredient"
self.tableView.allowsSelection = false
self.tableView.allowsSelectionDuringEditing = false
}
override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let IngredientsCellIdentifier = "IngredientsCell"
let cell = tableView.dequeueReusableCellWithIdentifier(IngredientsCellIdentifier, forIndexPath: indexPath ) as EditingTableViewCell
if (indexPath.row == 0) {
// cell ingredient name
cell.label.text = "Ingredient"
cell.textField.text = self.ingredientStr
cell.textField.placeholder = "Name"
cell.textField.tag = kIngredientFieldTag
}
else if (indexPath.row == 1) {
// cell ingredient amount
cell.label.text = "Amount"
cell.textField.text = self.amountStr
cell.textField.placeholder = "Amount"
cell.textField.tag = kAmountFieldTag
}
return cell
}
#IBAction func save (sender: AnyObject!) {
if let context = self.recipe.managedObjectContext {
if (!self.ingredient) {
self.ingredient = NSEntityDescription.insertNewObjectForEntityForName("Ingredient",
inManagedObjectContext:context) as Ingredient
self.recipe.addIngredientsObject(self.ingredient)
self.ingredient.displayOrder = self.recipe.ingredients.count
}
// update the ingredient from the values in the text fields
let cell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow:0, inSection:0)) as EditingTableViewCell
self.ingredient.name = cell.textField.text
// save the managed object context
var error: NSError? = nil
if !context.save( &error) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate.
You should not use this function in a shipping application, although it may be
useful during development. If it is not possible to recover from the error, display
an alert panel that instructs the user to quit the application by pressing the Home button.
*/
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
}
// if there isn't an ingredient object, create and configure one
self.parentViewController.dismissViewControllerAnimated(true, completion:nil)
}
#IBAction func cancel(sender: AnyObject!) {
self.parentViewController.dismissViewControllerAnimated(true, completion:nil)
}
func textFieldDidEndEditing(textField:UITextField) {
// editing has ended in one of our text fields, assign it's text to the right
// ivar based on the view tag
//
switch (textField.tag)
{
case kIngredientFieldTag:
self.ingredientStr = textField.text
case kAmountFieldTag:
self.amountStr = textField.text
default:
break
}
}
}
I have a CCSprite "_wo1" and it has a BOOL property "attack". I have the following code in my update method and I want it to constantly check if "attack" is YES or NO and make my sprite do different kinds of runAction.
So my question is:
How to compare two runAction? I tried "==" and "isEqual" and they are not working..
MySprite *_wo1 = ... // initialize _wo1 using my own class that sub-classed CCSprite
if (![_wo1 attack])
{
_wo1.position = ccp(_wo1.position.x + 10 * dt, _wo1.position.y); // walking forward
if (currentAction == attAction) // currentAction is defined in setting its runAction in the beginning: "currentAction = [wo1 runAction:[s01WalkAction copy]];"
{
[_wo1 stopAction:attAction];
[_wo1 runAction:[walkAction copy]];
}
}
else{
if (currentAction == walkAction)
{
[_wo1 stopAction:walkAction];
[_wo1 runAction:[attAction copy]];
}
}
Any helps or suggestions will be highly appreciated. Thank you!
You can keep an action state of your sprite in the MySprite class which easily can tell you which action is used in the sprite.
When you run an action on the sprite just set the state properly. For example :
mySprite.actionState = ACTION_WALKING;
and then you can easily check which action is used:
if (mySprite.actionState == ACTION_WALKING)
{
// Your handling code
}
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.
The UIKeyboardAnimationCurveUserInfoKey has a UIViewAnimationCurve value. How do I convert it to the corresponding UIViewAnimationOptions value for use with the options argument of +[UIView animateWithDuration:delay:options:animations:completion:]?
// UIView.h
typedef enum {
UIViewAnimationCurveEaseInOut, // slow at beginning and end
UIViewAnimationCurveEaseIn, // slow at beginning
UIViewAnimationCurveEaseOut, // slow at end
UIViewAnimationCurveLinear
} UIViewAnimationCurve;
// ...
enum {
// ...
UIViewAnimationOptionCurveEaseInOut = 0 << 16, // default
UIViewAnimationOptionCurveEaseIn = 1 << 16,
UIViewAnimationOptionCurveEaseOut = 2 << 16,
UIViewAnimationOptionCurveLinear = 3 << 16,
// ...
};
typedef NSUInteger UIViewAnimationOptions;
Obviously, I could create a simple category method with a switch statement, like so:
// UIView+AnimationOptionsWithCurve.h
#interface UIView (AnimationOptionsWithCurve)
#end
// UIView+AnimationOptionsWithCurve.m
#implementation UIView (AnimationOptionsWithCurve)
+ (UIViewAnimationOptions)animationOptionsWithCurve:(UIViewAnimationCurve)curve {
switch (curve) {
case UIViewAnimationCurveEaseInOut:
return UIViewAnimationOptionCurveEaseInOut;
case UIViewAnimationCurveEaseIn:
return UIViewAnimationOptionCurveEaseIn;
case UIViewAnimationCurveEaseOut:
return UIViewAnimationOptionCurveEaseOut;
case UIViewAnimationCurveLinear:
return UIViewAnimationOptionCurveLinear;
}
}
#end
But, is there an even easier/better way?
The category method you suggest is the “right” way to do it—you don’t necessarily have a guarantee of those constants keeping their value. From looking at how they’re defined, though, it seems you could just do
animationOption = animationCurve << 16;
...possibly with a cast to NSUInteger and then to UIViewAnimationOptions, if the compiler feels like complaining about that.
Arguably you can take your first solution and make it an inline function to save yourself the stack push. It's such a tight conditional (constant-bound, etc) that it should compile into a pretty tiny piece of assembly.
Edit:
Per #matt, here you go (Objective-C):
static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCurve curve)
{
switch (curve) {
case UIViewAnimationCurveEaseInOut:
return UIViewAnimationOptionCurveEaseInOut;
case UIViewAnimationCurveEaseIn:
return UIViewAnimationOptionCurveEaseIn;
case UIViewAnimationCurveEaseOut:
return UIViewAnimationOptionCurveEaseOut;
case UIViewAnimationCurveLinear:
return UIViewAnimationOptionCurveLinear;
}
}
Swift 3:
extension UIViewAnimationOptions {
init(curve: UIViewAnimationCurve) {
switch curve {
case .easeIn:
self = .curveEaseIn
case .easeOut:
self = .curveEaseOut
case .easeInOut:
self = .curveEaseInOut
case .linear:
self = .curveLinear
}
}
}
In Swift you can do
extension UIViewAnimationCurve {
func toOptions() -> UIViewAnimationOptions {
return UIViewAnimationOptions(rawValue: UInt(rawValue << 16))
}
}
An issue with the switch based solution is that it assumes no combination of options will be ever passed in. Practice shows though, that there may be situations where the assumption doesn't hold. One instance I found is (at least on iOS 7) when you obtain the keyboard animations to animate your content along with the appearance/disappearance of the keyboard.
If you listen to the keyboardWillShow: or keyboardWillHide: notifications, and then get the curve the keyboard announces it will use, e.g:
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
you're likely to obtain the value 7. If you pass that into the switch function/method, you won't get a correct translation of that value, resulting in incorrect animation behaviour.
Noah Witherspoon's answer will return the correct value. Combining the two solutions, you might write something like:
static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCurve curve)
{
UIViewAnimationOptions opt = (UIViewAnimationOptions)curve;
return opt << 16;
}
The caveat here, as noted by Noah also, is that if Apple ever changes the enumerations where the two types no longer correspond, then this function will break. The reason to use it anyway, is that the switch based option doesn't work in all situations you may encounter today, while this does.
iOS 10+
Swift 5
A Swift alternative to converting UIView.AnimationCurve to UIView.AnimationOptions, which may not even be possible, is to use UIViewPropertyAnimator (iOS 10+), which accepts UIView.AnimationCurve and is a more modern animator than UIView.animate.
Most likely you'll be working with UIResponder.keyboardAnimationCurveUserInfoKey, which returns an NSNumber. The documentation for this key is (Apple's own notation, not mine):
public class let keyboardAnimationCurveUserInfoKey: String // NSNumber of NSUInteger (UIViewAnimationCurve)
Using this approach, we can eliminate any guesswork:
if let kbTiming = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber, // doc says to unwrap as NSNumber
let timing = UIView.AnimationCurve.RawValue(exactly: kbTiming), // takes an NSNumber
let curve = UIView.AnimationCurve(rawValue: timing) { // takes a raw value
let animator = UIViewPropertyAnimator(duration: duration, curve: curve) {
// add animations
}
animator.startAnimation()
}
I'm having a problem finding out which NSTextfield is focused.
I am building a multi-language form and have several NSTextfields for data entry. I have to change the text input source for some of the NSTextfields during data entry, and I need it to happen automatically.
For now, I can change the text input source as I mentioned here without problem.
The problem that I have is to change the input source right when the NSTextfield becomes focused. If I use the controlTextDidBeginEditing: delegate method it changes the source input after typing the first letter.
This means that I lose the first word I typed in proper language.
Is there any delegate to find it ?
You can subclass your NSTextField and override - (BOOL)becomeFirstResponder (NSResponder) to respond to this kind of event.
You can also try control:textShouldBeginEditing: instead.
You will need to subclass NSTextField
Swift 3+
class FocusingTextField : NSTextField {
var isFocused : Bool = false
override func becomeFirstResponder() -> Bool {
let orig = super.becomeFirstResponder()
if(orig) { self.isFocused = true }
return orig
}
override func textDidEndEditing(_ notification: Notification) {
super.textDidEndEditing(notification)
self.isFocused = false
}
override func selectText(_ sender: Any?) {
super.selectText(sender)
self.isFocused = true
}
}
self.view.window?.firstResponder inside your view controller would give a NSTextView