I have an existing iPad app (XCode 4.6, ARC, Storyboards and iOS 6.2). The app works perfectly in Portrait mode, but not so well in Landscape mode using Auto Layout (see images below).
The image above shows portrait mode, which is correct.
This is landscape mode... notice it's missing the names and background color.
This is the code that creates the top grid:
-(void) drawTopGrid {
// set up notification for redrawing topGrid
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(notificationToRedrawTopGrid:)
name:#"redrawTopGrid" object:nil ];
// setup for drawing grid
CGContextRef context = UIGraphicsGetCurrentContext();
#define START_VERTICAL_LINE 1 // was 110
#define VERTICAL_LINE_INCREMENT 110 // was 110
#define VERTICAL_LINE_LENGTH 52 // (compute based on number of hours)
// draw first vertical line
CGContextMoveToPoint(context, START_VERTICAL_LINE, 0); //start
CGContextAddLineToPoint(context, START_VERTICAL_LINE, VERTICAL_LINE_LENGTH); //draw to this point
CGContextStrokePath(context); // draw it...
// draw remainder of vertical lines (based on count of staff positions defined) <--------------- TODO
for(int i = 2; i < 24; i += 2) { // will hold 6 staff positiions
CGContextMoveToPoint(context, (i * VERTICAL_LINE_INCREMENT) - i, 0); //start
CGContextAddLineToPoint(context, (i * VERTICAL_LINE_INCREMENT- i), VERTICAL_LINE_LENGTH); //draw to this point
CGContextStrokePath(context); // draw it...
}
// get the staff names
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pathForStaffNames = [documentsDirectory stringByAppendingPathComponent:#"staffNames.plist"];
NSDictionary *staffDict = [NSDictionary dictionaryWithContentsOfFile:pathForStaffNames];
staffPos1 = [staffDict objectForKey:Slot1];
staffPos2 = [staffDict objectForKey:Slot2];
staffPos3 = [staffDict objectForKey:Slot3];
staffPos4 = [staffDict objectForKey:Slot4];
staffPos5 = [staffDict objectForKey:Slot5];
staffPos6 = [staffDict objectForKey:Slot6];
const float hourFontSize = 18;
UIFont *hourfont=[UIFont systemFontOfSize:hourFontSize];
// draw staff names
[[UIColor redColor] set];
[staffPos1 drawAtPoint:CGPointMake(85, 12) withFont:hourfont]; // increment is 220
[[UIColor blueColor] set];
[staffPos2 drawAtPoint:CGPointMake(303, 12) withFont:hourfont];
[[UIColor magentaColor] set];
[staffPos3 drawAtPoint:CGPointMake(523, 12) withFont:hourfont];
[[UIColor redColor] set];
[staffPos4 drawAtPoint:CGPointMake(743, 12) withFont:hourfont];
[[UIColor blueColor] set];
[staffPos5 drawAtPoint:CGPointMake(963, 12) withFont:hourfont];
[[UIColor magentaColor] set];
[staffPos6 drawAtPoint:CGPointMake(1183, 12) withFont:hourfont];
}
My question is: do I have to do something different when using CG drawing methods? The left grid, top grid and center grid are all drawn the same way. The background color is on the UIView below the drawing. The only constraints for the top grid are width and height.
Here's the answer I was looking for:
Core Graphics drawing knows nothing of the view system (including auto layout). If content you are drawing is not appearing, it is likely because your view geometry is causing a context to be passed in that is not producing the geometry that your drawing expects (and the usual solution there is for your drawing code to look at the view's bounds and position content based on that).
Related
I am currently reading Real-World Functional Programming: With Examples in F# and C# by Tomas Petricek and Jon Skeet. I am a bit puzzled by a particular example in which we generate an application that displays a pie chart with some population statistics along with labels.
Now it is the part of drawing the label or rather setting the coordinates of the label that I am confused by. I hope that the author doesn't mind that I attach the excerpt here but it would be hard to get clarification on the code without showing it.
let centerX, centerY = 300.0, 200.0
let labelDistance = 150.0
let drawLabel (gr: Graphics) title startAngle angle =
let lblAngle = float(startAngle + angle / 2)
let ra = Math.PI * 2.0 * lblAngle / 360.0
let x = centerX + labelDistance * cos(ra)
let y = centerY + labelDistance * sin(ra)
let size = gr.MeasureString(title, fnt)
let rc = new PointF(float32(x) - size.Width / 2.0f,
float32(y) - size.Height / 2.0f)
gr.DrawString(title, fnt, Brushes.Black, new RectangleF(rc, size))
It seems that labelDistance and centerX, centerY define some standard "offset" from the center of the drawing surface and I'm guessing that the trigonometric functions define the angle of the label because if I omit those then all labels are placed on top of eachother in the bottom right corner. But I don't quite understand how this works. What exactly happens here?
Giving this a go by adding comments, not necessarily worked out in this order:
// startAngle is the angle in degrees of this segment, angle is the angle of
// the segment itself.
let drawLabel (gr: Graphics) title startAngle angle =
// So this is the angle of the centre of this segment.
let lblAngle = float(startAngle + angle / 2)
// And ra is the same angle, now in radians.
let ra = Math.PI * 2.0 * lblAngle / 360.0
// So these work out the position of the label in the usual
// way, using cosine(angle-in-radians) and then scaling for the X
// and using sine for the Y. Both relative to the centre of the
// circle.
let x = centerX + labelDistance * cos(ra)
let y = centerY + labelDistance * sin(ra)
// How long, in pixels, is the text?
let size = gr.MeasureString(title, fnt)
// Create an instance of the right data structure adjusting
// so the calculated point is the centre of the rectangle
// in which the text will be drawn.
let rc = new PointF(float32(x) - size.Width / 2.0f,
float32(y) - size.Height / 2.0f)
// And, thus, we can now draw the text.
gr.DrawString(title, fnt, Brushes.Black, new RectangleF(rc, size))
I have a canvas with a background image:
var bi = new BitmapImage(new Uri(imgLocFull));
var ib = new ImageBrush(bi) {Stretch = Stretch.UniformToFill};
MyCanvas.Background = ib;
I am overlaying various shapes on the image, and want the position of the shapes relative to the background image to be fixed.
If my application window is resized, the amount of the image that is cropped, horizontally and vertically, changes, and when my shapes are redrawn, they do not appear in the same position on the background image.
How can I determine how much of the image has been cropped (to apply an adjustment factor to the overlaid objects' positions?) Or is there a better way of fixing the location of a shape relative to the background image?
Here is my present drawing code:
var l = new Ellipse();
var scb = new SolidColorBrush();
scb.Color = Color.FromRgb(rCol, gCol, bCol);
l.Fill = scb;
l.StrokeThickness = 0;
l.Width = 3;
l.Height = 3;
Canvas.SetBottom(l, point.Y); // * clipping factor here?
Canvas.SetLeft(l, point.X); // * clipping factor here?
MyCanvas.Children.Add(l);
EDIT: Further Clarification
Here's a concrete example of what I am trying to achieve. My image is an aerial photograph, and I want to mark a particular geographical feature (with, say, an ellipse.)
When the window is resized, the ellipse doesn't stay on the feature, it stays relative to the left and top of the canvas.
I can get it closer to the right place by moving it using a factor (newx = newheight/oldheight * oldx) but this doesn't quite work because of the UniformToFill stretch mode, which sees some of the image clipped off the canvas.
The Top and Left of the Canvas are 'anchored', while the Bottom and Right move when resizing... try setting the Canvas.Top Attached Property instead, along with the Canvas.Left Attached Property as you are:
var l = new Ellipse();
var scb = new SolidColorBrush();
scb.Color = Color.FromRgb(rCol, gCol, bCol);
l.Fill = scb;
l.StrokeThickness = 0;
l.Width = 3;
l.Height = 3;
Canvas.SetTop(l, point.Y); // * clipping factor here?
Canvas.SetLeft(l, point.X); // * clipping factor here?
MyCanvas.Children.Add(l);
UPDATE >>>
You asked Or is there a better way of fixing the location of a shape relative to the background image?
I answered this question, so I don't understand why you would need to do anything else... your objects will not move when the screen in resized *if you only set the Grid.Top and Grid.Left properties.
I want to implement one page with vertical scroll view which contains some Images, Labels and one table view in bottom.
Table may have any number of rows. When user scroll to the top other elements such as images, labels should be hide but table should be visible at the top of screen and only table items should be scrolled.
I have tried by setting the currentoffset of scrollview in method scrollViewDidEndDecelerating but its not such smooth.
Scroll view first goes above, comes down and then set the table at top.
Can you please suggest me what should i implement here?
Thanks.
Try this....
// Add image in scrollView
serviceImage = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"row.png"]];
[serviceImage setBackgroundColor:[UIColor clearColor]];
serviceImage.frame = CGRectMake(10, 10, 300, 100);
[myScrollView addSubview:serviceImage];
[serviceImage release];
// Add label in scrollView
UILabel *rateLbl = [[UILabel alloc]initWithFrame:CGRectMake(10, 105, 300, 20)];
rateLbl.text = #"Rates";
rateLbl.font = [UIFont boldSystemFontOfSize:17.0];
rateLbl.textColor = [UIColor colorWithRed:244/255.0 green:29/255.0 blue:94/255.0 alpha:1.0];
rateLbl.textAlignment = UITextAlignmentLeft;
rateLbl.backgroundColor = [UIColor clearColor];
[myScrollView addSubview:rateLbl];
[rateLbl release];
// Add table in scrollView
int heightRateTbl;
heightRateTbl = [productType count] * 44;
rateTableView.frame =CGRectMake(10, expectedLabelSize.height + expectedDecLblSize.height + heightDayTbl + 555, 300, heightRateTbl);
rateTableView.scrollEnabled = NO;
rateTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[rateTableView reloadData];
You can enable or disable scroll according your choice.
// Set scrollView height
myScrollView.contentSize = CGSizeMake(320, 125 + heightRateTbl);
Do not forget to set delegates and declare all things(in .h file and in .xib file).
Hope i helped.
I have this simple code:
UIView* shadowView = [[UIView alloc] initWithFrame:CGRectMake(0, 30, self.view.frame.size.width, 5)];
shadowView.backgroundColor = [UIColor whiteColor];
shadowView.layer.masksToBounds = NO;
shadowView.layer.shadowOffset = CGSizeMake(0, 0);
shadowView.layer.shadowColor = [[UIColor blackColor] CGColor];
[shadowView.layer setShouldRasterize:YES];
shadowView.layer.shadowOpacity = 1;
[self.view addSubview:shadowView];
This creates a white view with shadow on top and bottom. Both the shadows are oriented nicely, with a gradient from top to bottom.
If I make this change:
shadowView.layer.shadowOffset = CGSizeMake(0, 5);
then the shadow appears only on the bottom (as I wanted) but the gradient is lost somehow. I think the top overlaps the bottom gradient.
How do I make it cast a shadow only on the bottom? (this is under IOS6).
Basically I want to create this image:
Apparently this is just the way shadows work on ios6. The problem was that I was setting a an offset (0, 5) where the 5 was simply larger than the line. After playing with the values for a while, I realized you can cast a shadow downwards, as long as you don't try to get a shadow larger than the image casting the shadow.
I have a Datagrid and I want to know the position of a datacell for overlaying it with a window.
It works fine with only one monitor, but with multiple monitors, the window is displaced.
Here's the code:
Point point = cell.PointToScreen(new Point(0, 0));
...
Window myWindow = new Window();
myWindow.Top = point.Y;
myWindow.Left = point.X;
Somebody has some experience with positioning on multiple monitors?
EDIT:
I made following test:
public MyWindow()
{
...
this.LocationChanged += MyWindow_LocationChanged;
}
void MyWindow_LocationChanged(object sender, EventArgs e)
{
Console.WriteLine(this.Top + " <--> " + this.PointToScreen(new Point(0, 0)).Y);
}
Results:
- Single-Monitor: 0 <--> 30; 20 <--> 50; 100 <--> 130
==> Always difference of 30 (may be caused by title bar)
- Dual-Monitor: 0 <--> 30; 20 <--> 55; 100 <--> 153
==> At 0,0 difference of 30. But the more I moved the window away from 0,0. the greater becomes the difference, but should stay the same. Very strange!
EDIT2:
Here's my solution, thanks to CodeNaked for the hint:
Point point = cell.PointToScreen(new Point(0, 0));
...
Window myWindow = new Window();
PresentationSource source = PresentationSource.FromVisual(this);
myWindow.Top = point.Y / source.CompositionTarget.TransformToDevice.M22;
myWindow.Left = point.X / source.CompositionTarget.TransformToDevice.M11;
This may have to do with a non-standard DPI setting, but I'm pretty sure that setting affects all monitors. This blog shows how to get the correct position. But the code is effectively:
PresentationSource source = PresentationSource.FromVisual(control);
double dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
double dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;
window.Left = point.X * 96.0 / dpiX;
window.Top = point.Y * 96.0 / dpiY;
The behavior you described is not correct and I can't reproduce it.
I created a simple Window with the following code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
LocationChanged += (s, e) =>
{
var screen = PointToScreen(new Point(0, 0));
var window = new Point(Left, Top);
var diff = screen - window;
textbox.Text = window.ToString() + Environment.NewLine +
screen.ToString() + Environment.NewLine + diff;
};
}
}
The last line (= the difference between the two coordinates) never changes.
I'm unable to reproduce the problem your experience. The upper left corner of the client area of the window (the point returned by PointToScreen) is always translated 8 pixels horizontally and 30 pixels vertically from the upper left corner of the window. This is on a two-monitor setup.
You should be able to compute the values 8 and 30 from the SystemParameters class, however I must admit that I'm not sure exactly what parameters to use to arrive at the actual values on my system.