position view relative to tab indicator - ios

I've been given a UI that uses a UITabBarController, and in one of the tabbed ViewControllers, a View (an arrow as a UIImageView) needs to be positioned relative to the tab indicator (the arrow needs to point down at the indicator, centered along the x-axis). I used basic math to emulate the position based on known variables, and it works properly for iPhone/Pod in both landscape and portrait orientations, but fails on iPad (it's a little too far right in both orientations - seems worse in landscape but that might just be my perception).
Here's what I used:
int visibleWidth = self.view.frame.size.width;
int tabBarSize = self.tabBarController.tabBar.frame.size.width;
int tabSize = tabBarSize / [self.tabBarController.viewControllers count];
int tabPosition = 2; // hint should point at the 3rd tab
int arrowIconWidth = hintIcon.image.size.width;
int arrowIconHeight = hintIcon.image.size.height;
int tabEmulationPosition = visibleWidth / 2 - tabBarSize / 2;
int tabOffsetPosition = tabEmulationPosition + ( tabSize * tabPosition );
int iconPosition = tabOffsetPosition + ( tabSize / 2 - arrowIconWidth / 2 );
Is there a better way than trying to "fake" the math (maybe a getBoundingClientRect type method?) Or is the math approach the best bet, assuming there's a correct-able flaw in what I've got?
TYIA

Your math seems alright except one caveat: the Core Graphics distances are all of type CGFloat not int. Remember that integer division works differently in C than what you might expect:
int x = 5 / 2; // x == 2
Thus, replace your ints with CGFloats and divide by 2.0 rather than 2.
Also, in the iPad the tab button are not necessarily spaced evenly across the whole bar but have a certain padding to the left and right. You would have to fiddle for finding a suitable constant, or experimentally find a reliable way to calculate it based on the number of view controllers.
Assuming a fixed tab bar button width, you could calculate an offset to add to the x of your image:
CGFloat tabSize =
FIXED_TAB_BUTTON_WIDTH * self.tabBarController.viewControllers.count;
CGFloat offset = (tabBarSize - tabSize) / 2.0;

Related

Why .width() and .height() modifiers apply wrong value?

I want two independent rows to have the same width and scroll simultaneously. I have tried to achieve this in this way:
#Composable
fun TwoRows() {
val scrollState = rememberScrollState()
Column(Modifier.fillMaxWidth()) {
Row(Modifier.fillMaxWidth().horizontalScroll(scrollState)) {
for (i in 0 until 100) {
Box(Modifier.width(10.dp).height(10.dp).background(Color.Red))
Spacer(Modifier.width(90.dp))
}
}
Row(Modifier.fillMaxWidth().horizontalScroll(scrollState)) {
for (i in 0 until 100) {
Box(Modifier.width(100.dp).height(10.dp).background(Color.Green))
}
}
}
}
The first row consists of 10 dp width red rectangles and 90 dp width spacers. The second row consists of only 100 dp width green rectangles. I'm expecting these rows to have the same width but the layout inspector shows they're not:
Because of this the elements do not match expecting positions:
How can I fix it?
This is happening because each of .size(), width(), .height() modifiers transform incoming value into pixel just in time it applied. I.e. they apply independently - the first modifier converts its value to pixels, and then the second, and so on. There is no guarantee that you get the same dp value after transforming it to px and back because of rounding. Here is how dp->px->dp transformation works for your case:
dp = 10.dp // Incoming dp value
density = 2.625 // Screen density scale value of your emulator
unrounded_px = 10 * density = 26.25
px = 26
restored_dp = 26 / density = 9.904762.dp
Most likely that the LayoutInspector doesn't round computed dp value to the nearest integer, but simply discards the fractional part. I think this is the reason why it shows different value. Such behavior is especially likely to occur when you work with relatively small views on low pixel density screens.
You can achieve desired behavior wrapping your red Box and Spacer into the Box with specified size:
Box(Modifier.width(100.dp).height(10.dp)) {
Box(Modifier.width(10.dp).height(10.dp).background(Color.Red))
Spacer(Modifier.width(90.dp))
}

CorePlot allow user drag rotate PieChart

(As for I got the solution now, it is being shared at the bottom)
Fact is I have been struggling a while about this and I believe quite a lot of discussions I found are related to older versions of CorePlot or unanswered.
Firstly, I am using CorePlot 1.5.1.
I am able to plot a PieChart already and now I would like the user to be able to rotate it by dragging on the screen ( doesn't really matter touch directly the pieChart or the host View).
Using these delegates at the moment:
#interface MyChartViewController : UIViewController<CPTPieChartDataSource,CPTPlotSpaceDelegate,CPTPieChartDelegate>
Got a hostView,
#property (strong, nonatomic) IBOutlet CPTGraphHostingView *hostView;
Made a graph, set as, self.hostView.hostedGraph = graph
and made a PieChart, put into the graph, [graph addPlot:self.mainPieChart];
(I set the pieChart with a strong property to let me refer it anytime)
So, here is my first attempt, and fact is, it is responding, (though not in a desirable way)
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *) self.hostView.hostedGraph.defaultPlotSpace;
[plotSpace setDelegate:self];
(only works by setting plotSpace delegate to self, not sure why, i guess it's about finding a way to receive user's interaction, anyway, then I overwrite these two functions)
Using this value:
static float deltaAngle;
-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceDownEvent:(UIEvent *)event atPoint:(CGPoint)point
{
float dx = point.x - self.mainPieChart.centerAnchor.x;
float dy = point.y - self.mainPieChart.centerAnchor.y;
deltaAngle = atan2(dy,dx);
return YES;
}
This, in order to save the first touching point
Then sense the dragging do use the difference to make the rotation
( at least I wanted so )
-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceDraggedEvent:(UIEvent *)event atPoint:(CGPoint)point
{
int x = self.mainPieChart.centerAnchor.x;
int y = self.mainPieChart.centerAnchor.y;
float dx = point.x - x;
float dy = point.y - y;
double a = atan2(dx,dy);
float angleDifference = deltaAngle - a;
self.mainPieChart.startAngle = -angleDifference;
return YES;
}
And here is an image about it, though i think I covered most of the details already.
http://postimg.org/image/bey0fosqj/
It is in landscape mode though.
Fact is I think this would be the most appropriate function to call, but somehow I cannot call it out (pretty sure I set self.mainPieChart delegate/ datasource to self already)
-(BOOL)pointingDeviceDraggedEvent:(id)event atPoint:(CGPoint)interactionPoint{
(after further testing)
Interesting, after trying to print out different values, by the shouldHandlePointingDevice function (simply clicking), I think i got some ideas now.
the self.mainPieChart.centerAnchor.x / y values always return 0.5 (both)
However, point x, point y are returning values vary from 1-500+,
it seems more like I am comparing two things, though they are on top of each other, from different perspective.
Likely the PlotSpace set delegate part messed that up.
============================================================
So, as for now I still don't know how to call -(BOOL)pointingDeviceDraggedEvent:(id)event atPoint:(CGPoint)interactionPoint{, I tried to put it into a if loop like
if([self.mainPieChart pointingDeviceDownEvent:event atPoint:self.mainPieChart.centerAnchor] == YES)
under my touched function but nothing happened, never mind.
Back to the point, my current solution works well now, even after applying padding.
float x = (self.hostView.bounds.size.width + self.hostView.hostedGraph.paddingLeft)*self.mainPieChart.centerAnchor.x;
float y = self.hostView.bounds.size.height * self.mainPieChart.centerAnchor.y;
float dx = point.x - x;
float dy = point.y - y;
double a = atan2(dx,dy);
these lines are all same for both press / drag functions, as for drag function,
float angleDifference = deltaAngle - a;
self.mainPieChart.startAngle = angleDifference;
are added before the end
However, the case is slightly different when the Pie Chart is not at the middle, or, in other words, the graph holding the Pie Chart is padded.
( my example somehow is mid centre just to make it easy)
you simply have to mortify the x y float value above, it's easier than I expected.
For example if I have,
graph.paddingLeft = -300.0f;
the value of float x in both press/drag will become
float x = (self.hostView.bounds.size.width + self.hostView.hostedGraph.paddingLeft)*self.mainPieChart.centerAnchor.x;
The pie chart centerAnchor is given as fractions of the width and height. Be sure to multiply the anchor values by the corresponding dimension of the graph before computing dx and dy.

How do i resize an object created in actionscript with the point of origin staying central

widthEssentially, i am creating objects in Flash using actionscript. I currently have a problem where i am resizing the object in actionscript but as the point of reference seems to be the top left hand corner, the object when shrinking seems to also move towards the top left of the screen.
Is there a way to either code the point of origin for an object to the center of the object using code or to have it resize without seemingly moving?
my code for creating and resizing an object is like this (it will grow and shrink but at moment im just tryint to fix this problem before i move on):
var blob1:Loader = new Loader();
blob1.x = 1000;
blob1.y = 450;
blob1.load(new URLRequest("blob1.png"));
addChild(blob1);
blob1.addEventListener(Event.ENTER_FRAME, blobTween);
function blobTween(event:Event)
{
var size = 0;
if (size == 0){
blob1.width += 5;
blob1.height += 5;
}else if (size == 1){
}
Can't you update x and y while you change width and height ?
blob1.x += deltaWidth / 2
blob1.y += deltaHeight / 2
You increment width and height by 5, so deltaWidth = 5 and deltaHeight = 5 .
NOTE: deltaWidth / 2 doesn't divide equally, so you'll have slight shift. I suggest having deltaWidth = 6 and deltaHeight = 6 , or something similar.
GreenSock has a great library for doing just that; TransformManager.
http://www.greensock.com/transformmanageras3/
my solution in the end was a bit strange. I took the shape, divided it into 4 quarters and reattached them in flash by placing the objects in the same place and rotating them (as it rotates from the top left corner, the 4 quarters form a complete shape with each quarter having their top left corner in the centre of the "object"). I then resized them all at once to do it properly.
Whats weird is that the top right and bottom left corners required different math from the other two corners. Which makes no sense whatsoever. 2 corners required width and height to be adjusted. The other two corners required just width to be adjusted. very strange but worked :/

Positioning a view after transform rotation

I'm creating a custom popover background, so I subclassed the UIPopoverBackground abstract class. While creating the layout function I came across a problem with placing and rotating the arrow for the background.
The first picture shows the arrow at the desired position. In the following code I calculated the origin I wanted but the rotation seemed to have translated the new position of the image off to the side about 11 points. As you can see, I created a hack solution where I shifted the arrow over 11 points. But that still doesn't cover up the fact that I have a gapping hole in my math skills. If someone would be so kind as to explain to me what's going on here I'd be eternally grateful. What also would be nice is a solution that would not involve magic numbers, so that I could apply this solution to the cases with the up, down and right arrow
#define ARROW_BASE_WIDTH 42.0
#define ARROW_HEIGHT 22.0
case UIPopoverArrowDirectionRight:
{
width -= ARROW_HEIGHT;
float arrowCenterY = self.frame.size.height/2 - ARROW_HEIGHT/2 + self.arrowOffset;
_arrowView.frame = CGRectMake(width,
arrowCenterY,
ARROW_BASE_WIDTH,
ARROW_HEIGHT);
rotation = CGAffineTransformMakeRotation(M_PI_2);
//rotation = CGAffineTransformTranslate(rotation, 0, 11);
_borderImageView.frame = CGRectMake(left, top, width, height);
[_arrowView setTransform:rotation];
}
break;
Well, if the rotation is applied about the center of the arrow view (as it is), that leaves a gap of (ARROW_BASE_WIDTH - ARROW_HEIGHT) / 2 to the post-rotation left of the arrow, which is what you have to compensate for, it seems. By offsetting the center of the arrow view by this much, it should come back into alignment.

Making a position indicator 'map' of position in a scrollview iOS

I have a view containing a scrollview the size of a large image, and i'd like to add a position indicator for where you are in the scrollview, like a map in games.
I've created a smaller view on top of the scrollview with a smaller version of the image, and I'm wondering how I could add a rectangle relative to the size and position of the zoom scale of the scrollview in my smaller view, to indicate on the small image what part of the larger image you're currently looking at? I'm targeting iOS 5+ using ARC.
here's what i have so far - it seems to set the origin correctly, but when i zoom it gets smaller when it should get bigger and vice versa:
int scrollxint = self.scrollView.bounds.origin.x;
int scrollyint = self.scrollView.bounds.origin.y;
int scrollxlength = self.scrollView.contentSize.width;
int scrollylength = self.scrollView.contentSize.height;
int reducedscrollxint = (scrollxint*0.05);
int reducedscrollyint = (scrollyint*0.05);
int reducedscrollxlength = (scrollxlength*0.05);
int reducedscrollylength = (scrollylength*0.05);
areaFrame.frame = CGRectMake(reducedscrollxint, reducedscrollyint, reducedscrollxlength, reducedscrollylength);
[self.mapView addSubview:areaFrame];
Any help would be much appreciated.
I had to use the zoom scale relative to the original size of the area indicator to make this work, but this code is working for me:
int scrollxint = self.scrollView.bounds.origin.x;
int scrollyint = self.scrollView.bounds.origin.y;
float scale = self.scrollView.zoomScale;
int reducedscrollxint = (scrollxint*0.05);
int reducedscrollyint = (scrollyint*0.05);
areaFrame.frame = CGRectMake(reducedscrollxint, reducedscrollyint, (10/scale), (6.5/scale));
[self.mapView addSubview:areaFrame];

Resources