Can someone please educate me why the following does not work? The button never gets set to selected.
[self.boldButton setSelected:isBold];
If I replace the above with an if else statement it works fine. I can also change the setSelected values to 1 or 0, instead of YES or NO and it still works fine.
if (isBold)
{
[self.boldButton setSelected:YES];
}
else
{
[self.boldButton setSelected:NO];
}
So I have a working project, but I don't understand why these two implementations don't deliver the same results. Thanks.
FWIW - I test for bold with another method. Though if the test were flawed, I don't see how the second approach could work, while the first still doesn't.
- (BOOL)isBold
{
CTFontRef fontRef = (CTFontRef)CFBridgingRetain(self);
CTFontSymbolicTraits symbolicTraits = CTFontGetSymbolicTraits(fontRef);
return (symbolicTraits & kCTFontTraitBold);
}
BOOL is defined like this in <objc/objc.h>:
typedef signed char BOOL;
That means a BOOL can actually hold any value in the range -128 through 127 (inclusive).
-[UIControl setSelected:] works roughly like this:
#define kSelectedBitPosition 10
#define kSelectedBit (1 << kSelectedBitPosition)
- (void)setSelected:(BOOL)selected {
if (((self->_controlFlags >> kSelectedBitPosition) & 1) == selected) {
return;
} else {
self->_controlFlags = (self->_controlFlags & ~kSelectedBit)
| ((selected & 1) << kSelectedBitPosition);
[self setNeedsDisplay];
}
}
(I disassembled the simulator version of UIKit with Hopper to figure that out.)
So, notice two things:
The if statement condition can only be true if selected == 0 or selected == 1. It will never be true if selected has any other value.
The assignment statement (that updates _controlFlags) only uses bit 0 (the 1's bit) of selected. So, for example, if selected == -2, which is logically true in C and has every bit set except bit 0, the assignment statement will still not turn on the bit in _controlFlags.
This means that you must pass 0 or 1 to -[UIControl setSelected:]. No other value will work reliably.
The shortest way to convert all non-zero values to 1 in C is by applying the ! operator twice:
[self.boldButton setSelected:!!isBold];
However, it would probably be better to fix your -isBold method to return a “safe” BOOL instead:
- (BOOL)isBold {
CTFontRef fontRef = (CTFontRef)CFBridgingRetain(self);
CTFontSymbolicTraits symbolicTraits = CTFontGetSymbolicTraits(fontRef);
return !!(symbolicTraits & kCTFontTraitBold);
}
Related
I am having a heck of a time getting around some very annoying behavior in MapKit. Specifically, I want to show a popover annotation view after the map has animated to a certain visible region.
I can use mapView:regionDidChangeAnimated: to get a callback after the map transition (which I will call the "zoom"), but this only fires if the visible region actually changes.
You see the zoom happens in my app when an annotation (or its counterpart in a table view) is selected: I want to zoom the map to essentially focus on the selected annotation.
The problem is that if the map is already in the correct region, I will get no regionDidChange, and therefore no signal to show my popover view.
I am using variants of setVisibleMapRect: to actually perform the zoom. So I thought I was being smart by comparing the new and old MKMapRects to see if they are equal, and if so manually calling my callback.
The problem is it doesn't work! Even if I determine that the two MKMapRects are not equal, by means of MKMapRectEqualToRect, MapKit just sometimes decides it won't fire the regionDidChange event! Perhaps it has to be over a certain delta or something, I don't know.
So my question is: what the heck is the expected best practice for getting a guaranteed completion from setVisibleMapRect:?
Update: I'm now thinking this might have to do with the edgePadding argument which I am also using:
- (BOOL)vivoSetVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate {
BOOL equal = MKMapRectEqualToRect(self.visibleMapRect, mapRect);
NSLog(#"Map rects are: %#", equal ? #"equal" : #"NOT equal");
NSLog(#"%#\n\n%#", MKStringFromMapRect(self.visibleMapRect), MKStringFromMapRect(newRect));
[self setVisibleMapRect:mapRect edgePadding:insets animated:animate];
return !equal;
}
As you can see, the edge padding is not taken into account in the comparison, yet it is most likely having an effect on the finally computed mapRect.
Does anyone know how I can perform this test to properly take into account edgePadding?
Update 2: It's now looking like MKMapRectEqualToRect is wrong and therefore completely useless. Check out this log statement:
2016-03-16 17:06:30.841 Mobile[70089:6240786] Map rects are: NOT equal
{{42403042.3, 91858289.9}, {14878.4, 12832.6}}
{{42403042.3, 91858289.9}, {14878.4, 12832.6}}
They look pretty darn equal to me!! 😒
MapRects are defined using Doubles, and comparing Doubles can give unexpected behavior. My recommendation is to define your own comparison that compares to your desired tolerance. For instance, compare to the nearest integer value.
In Swift:
public extension Double {
func roundToInt() -> Int {
let value = Int(self)
return self - Double(value) < 0.5 ? value : value + 1
}
}
public func == (lhs: MKMapRect, rhs: MKMapRect) -> Bool {
if lhs.origin.x.roundToInt() != rhs.origin.x.roundToInt() {return false}
if lhs.origin.y.roundToInt() != rhs.origin.y.roundToInt() {return false}
if lhs.size.width.roundToInt() != rhs.size.width.roundToInt() {return false}
if lhs.size.height.roundToInt() != rhs.size.height.roundToInt() {return false}
return true
}
You can then do a comparison by typing
if mapRect1 == mapRect 2 {
...
}
It turns out MKMapRectEqualToRect is completely broken (at least on the simulator). Don't use it!!
I changed it to instead do a comparison on the strings returned by MKStringFromMapRect, as well as adjusting the map rect with mapRectThatFits:edgePadding: before the comparison and it now appears to be working correctly:
- (BOOL)vivoSetVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate {
MKMapRect newRect = [self mapRectThatFits:mapRect edgePadding:insets];
BOOL equal = [MKStringFromMapRect(self.visibleMapRect) isEqualToString:MKStringFromMapRect(newRect)];
[self setVisibleMapRect:newRect animated:animate];
return !equal;
}
Why is (self.window) in the following code a condition?
-(void)beginRefreshing {
[UIView animateWithDuration:MJRefreshFastAnimationDuration
animations:^{
self.alpha = 1.0;
}];
self.pullingPercent = 1.0;
if (self.window) {
self.state = MJRefreshStateRefreshing;
} else {
self.state = MJRefreshStateWillRefresh;
[self setNeedsDisplay];
}
}
In this case:
if (self.window) {
which is a short version of
if (self.window != nil) {
is a test whether the view (UIView instance) is in the view hierarchy of any window, that is, whether the view is displayed on the screen.
Certain variables are boolean meaning they can either be equal to true or false. Others can have a wide range of values. for example off you want to do something if an Integer is equal to 5 you can write:
if (myInt == 5) {
//do something
}
If your variable is a Boolean there are only two options so we can write is shorter:
if (myBoolean) {
//case where myBoolean = true
} else {
//case where myBoolean = false
}
In your particular example, self.window evaluates to a Boolean in the following manner: either self.windows exists in the view hierarchy or not (self.windows == true) or it do not (self.windows == false).
I've decided to expand my comment to an answer for future reference.
It's actually a side effect, AFAIK. Because false is defined as 0, and true is defined as anything different than 0. A valid objects address is non-zero so it is treated as true, whereas, thanks to ARC, nil == 0. You can check this by printing this NSLog(#"%p", nil). Bear in mind, that for integers, this means that a negative value, also evaluates to true
It is also worth noting, that because the underlying storage for a boolean is rarely one-bit it may not always be true that when someBool == true, then someBool == 1. This means that taking such a shortcut :
- (NSInteger)increment:(NSInteger)toIncrement ifTrue:(BOOL)condition {
return toIncrement + condition;
}
may not always lead to toIncrement being incremented by 1. The code is valid, becasue a BOOL will get promoted to integer automatically.
This is of course somwhat of an edge case, but still possible.
So I am trying to make an iOS app that checks prime numbers in an input field as practice. I refactored my code to have a struct specifically for calculation functions like isPrime. For some reason my for loops is not working correctly when its in the struct. It works if I refactored it back into the controller.
func isPrime(number:Int) -> Bool{
let start = 2
for var i = number-1; i > 1; i-- {
if (number % i == 0){
return true
}
}
return false
}
The debugger thingy gives back these inputs:
Types 12 into text field
number = 12
i = 14070095816392014214
Why is my variable i in the for loops so damn large? I also tested putting a stray variable inside the function and it does the same thing (ex; start_int = 14214124123232423)?
Did you try printing the value of your number variable inside the function?
Your logic seems to be reversed. if a number x is divisible by another number less than x, then x is not prime. You have returned true if x is divisible. it should be false.
I have a question regarding integers outside of methods in Objective-C/Xcode. I'm trying to create a simple guessing game, however my randomizer randomize number every time when the method is called, here is the code snipped:
- (IBAction)guessButton:(id)sender {
int tempUserGuess = [self.textField.text integerValue];
int randomNumber = (arc4random() % 11);
if(tempUserGuess == randomNumber){
self.guessAns.text = #"you won!";
}
if (tempUserGuess < randomNumber){
self.guessAns.text = #"no! too low!";
}
if (tempUserGuess > randomNumber){
self.guessAns.text = #"no! too high!";
}
}
The reason why I'm trying to put an int outside of the method is of that once randomized integer should not be randomized every single time (of course). By the way, everything works fine, the app compiles and works but every single time when I hit return, it randomizes the number.
I know how to do this in Java, but Objective-C seems to be more complex.
Your guessButton method is probably a member of some class. You need to add property to that class holding that randomized number.
You only have to create/store a random number once per game play. There is no need to call the randomize method each time the guess button is pressed. The guessButton method is basically like an ActionListener in java. Each time the button is pressed, whatever's inside the curly braces will be executed. If you add a play again button to the game, then you might want to call the randomize method inside of it's action method.
if(tempUserGuess == num){
self.guessAns.text = #"you won!";
}
This will be a good way because it will be using less memory in the sytem when trying to divert the random number. Good work
Okay, I got it.
Here is the answer:
in ViewController.h I had to include:
NSInteger num;
and then in ViewController.m a method that simply returns a number:
-(int)giveRandom {
int randomNumber = (arc4random() % 10);
return randomNumber;
}
And then refer to 'num' in the method with if statements such as:
...
if(tempUserGuess == num){
self.guessAns.text = #"you won!";
}
...
For some reason it returns 0, but I will try to solve it.
Thanks!
I used to compare the text entered in a txt field as below,
if ([myTextfield.text isEqualToString: #""])
{
// success code
}
else
{
// my code
}
in iOS 7 this check works perfectly. If nothing entered in to the text field, condition is success. But in iOS 6 if the field is blank, this condition always false and moves to the else block.
So i did like,
if (myTextfield.text.length == 0)
{
// success code
}
else
{
// my code
}
This works fine, and I just wanted to know what is wrong in my first method.
If myTextfield.text is nil, [myTextfield.text isEqualToString: #""] will fail because messaging nil returns nil (or 0, NO as appropriate).
In the second case you have, you are checking for == 0, so even if the string is nil you will still get a positive result.
In iOS7, untouched UITextFields return nil, whereas in previous iOS versions they return an empty string. Touched UITextFields in both cases should return an empty string.
(Did you ask the question in reverse mistaking iOS6 w 7? If not, I'd also make sure the text field is hooked up properly since a touched iOS7 text field could return an empty string while an unsynthesized iOS6 text field could return NULL since iOS6 is especially strict in this way.)