MKMapView setVisibleMapRect with completion? - ios

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;
}

Related

How to find close of a candle and store its value in EA?

I'm new to mql4 and am confused with the basics. I want to prepare an exit strategy. Here are the conditions. If it's a buy trade and we have to sell for closing the order:
The candle should give a close below the Supertrend.
Next candle should cut the low of the previous candle.
Below is the part of the code I've prepared.
i=1;
if (Close[i]<st)
{
low=close[1];
a=checkt1();
if (a==True)
{
OrderClose()
}
}
else if(Close[i]>st)
{
return(EMPTY_VALUE);
}
bool check1t()
{
if (Ask<a && Bid<a)
{
CloseOrder();
}
return True
}
Here the value of close keeps changing as I have set it to close[1]. Is there any function or any way that can store the value of close of the candle that had cut supertrend only? And not take up any other values?
so you need to check that previous candle is below the one before, and that candle before closed below the indicator.
`void Check4closeBuy(){
double low1 = iLow(_Symbol,0,1), low2 = iLow(_Symbol,0,2),
close2 = iClose(_Symbol,0,2), ind2 = iCustom(_Symbol,0,ind_name,***,buffer,2);
if (ind2>close2 && low2>low1){
//close here
}
}`
about comparing doubles - since they are doubles not integers it is better to compare the difference below half-tick or something like that, but need to be careful with that.
so in basic case: double1>double2 -> double1-double2>Point/2
but it depends on your indicator, it can have _Digits or more (like different MA may have more digits after dot then just 5).

Swift: Random number arrays inside images and variables with a for loop

I am creating a game in which, depending on the number of 'swipes' chosen to do, (let's say 3), 3 different patterns show on the screen, one by one. I am working on developing the first pattern.
So I have this:
if (swipes.no_of_swipes) == 3 {
swipeArray = Array<UInt32>(count: 3, repeatedValue: 0)
for i in 0 ..< 3 {
swipeArray[i] = arc4random_uniform(84)}
}
As far as I am aware, this code creates an array with three UInts which can be accessed by doing swipeArray[0], swipeArray[1], and swipeArray[2]. My first question is how long will this swipeArray stay the same? Until the close the view? Should I have a 'refresh button' when the user loses - and if so, how would I make one?
Then I have a property observer. You will notice the for loop, which I am using to keep code concise. I understand that I could do something like x++ somewhere in here so that it will go through each one.
var playBegin: Bool = false{
didSet {
if playBegin == true {
println("\(playBegin)")
var swipes = Menu()
if (swipes.no_of_swipes) == 3 {
for i in 0 ..< 3 {
patternRoom.image = UIImage(named: "pattern\(swipeArray[x])")
//rest of code
}
}
}
The pattern image comes from a set of 84 images named like pattern7 and pattern56. My second question is, how could I code the for loop to go through each swipeArray[x].
Thank you in advance,
Will
how long will this swipeArray stay the same?
This is a bit too open ended. Itā€™ll stay the same until you assign a new value to it, either from this same bit of code or a different part. Only you can know when that will be, by looking at your code.
Since you express an interest in keeping the code concise, hereā€™s a couple of code tips.
You might think about writing your first snippetā€™s loop like this:
swipeArray = (0..<swipes.no_of_swipes).map { _ in
arc4random_uniform(84)
}
This combines creating a new array and populating the values. By the way, just in case you donā€™t realize, thereā€™s no guarantee this array wonā€™t contain the same value twice.
Itā€™s also probably better to make swipeArray of type [Int] rather than [UInt32], and to convert the result of arc4random to an Int straight away:
Int(arc4random_uniform(84))
Otherwise the UInt32s will probably be a pain to work with.
For your second for loop, you can do this:
for i in swipeArray {
patternRoom.image = UIImage(named: "pattern\(i)")
// rest of code
}
When writing Swift, usually (but not always), when you find yourself using array[x] thereā€™s a better more expressive way of doing it.

What is the recommended way to break out of a block or stop the enumeration?

So, I just realize that break is only for loop or switch.
Here's my question: Is there a recommended way to break out of a block? For example:
func getContentFrom(group: ALAssetsGroup, withAssetFilter: ALAssetsFilter) {
group.enumerateAssetsUsingBlock { (result, index , stop) -> Void in
//I want to get out when I find the value because result contains 800++ elements
}
}
Right now, I am using return but I am not sure if this is recommended. Is there other ways? Thanks folks.
return is fine, block concept is similar to function, so returning is okay.
If you want to stop the current iteration of the enumeration, simply return.
But you say:
I want to get out when I find the value because result contains 800++ elements
So, that means that you want to completely stop the enumeration when you find the one you want. In that case, set the boolean value that the pointer points to. Or, a better name for that third parameter would be stop, e.g.:
func getContentFrom(group: ALAssetsGroup, withAssetFilter: ALAssetsFilter) {
group.enumerateAssetsUsingBlock() { result, index, stop in
let found: Bool = ...
if found {
//I want to get out when I find the value because result contains 800++ elements
stop.memory = true
}
}
}

How best would I store a variable that will be used multiple times throughout the execution of a method and will stay the same?

Say I'm handling a pan gesture with UIPanGestureRecognizer, and I want to know the initial point every time the function is called when the view is panned. So I save it into a variable and for each additional time that function is called I can just access this unchanging variable, as the original value I got it from (say, the first time the function is called I save its value) will be changed as the function gets called more.
Static variables don't seem to work as if I do the following:
static CGFloat originalImageTopPosition = CGRectGetMinY(self.imageBeingOverlayedScrollView.frame);
I get:
Initializer element is not a compile-time constant.
I could use a property, but if there were tons of cases where I needed things like this, it would seem like a poor choice to clutter up my list of properties with random saved values.
You can declare a static variable without initializing it (or initialize to zero or something), then in an initialization method set it.
well floats are a little tricker, generally you will use some sort of magic value, assuming that you will want 0 to be valid there is a little float trick you can use
static float blah = 0/0.f;
if (blah!=blah)
{
blah = 3.4;
}
basically you use a compile time constant that is a NaN, then NaN!=NaN... then you get to set at run time.
so for your case it would be...
static CGFloat originalImageTopPosition = 0.0/0.0;
if(originalImageTopPosition!=originalImageTopPosition)
{
originalImageTopPosition = CGRectGetMinY(self.imageBeingOverlayedScrollView.frame);
}
Edit - just thought of a less cryptic way...
static CGFloat * originalImageTopPositionPtr = NULL;
static CGFloat originalImageTopPosition = 0.0/0.0;
if(!originalImageTopPositionPtr)
{
originalImageTopPosition = CGRectGetMinY(self.imageBeingOverlayedScrollView.frame);
originalImageTopPositionPtr = &originalImageTopPosition;
}
and an even less cryptic way...
static BOOL originalImageTopPositionInited = NO;
static CGFloat originalImageTopPosition = 0.0/0.0;
if(!originalImageTopPositionInited)
{
originalImageTopPosition = CGRectGetMinY(self.imageBeingOverlayedScrollView.frame);
originalImageTopPositionInited = YES;
}
It depends on the situation. For your example of handling a pan gesture, you can assign the value to a local variable inside an if clause like this:
CGPoint point;
if (sender.state == UIGestureRecognizerStateBegan){
point = [sender locationInView:nil];
}
This will only be called once during a pan gesture, so you can use this point to do calculations during the panning stage of the gesture.

Button Set Selected via BOOL

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);
}

Resources