Custom heightForRowAtIndexPath (CGSize sizeWithFont) with NSAttributedString iOS - ios6

I have a table view where my cells height is defined dynamically depending on the text it is representing.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//getting my text for this cell, the row etc ...
...
//here is the part interesting us
NSAttributedString* theText = [myTextForThisCell objectAtIndex:indexPath.row];
NSInteger labelWidth = self.tableView.bounds.size.width-HORIZONTAL_CELL_PADDING;
CGSize textSize = [theText sizeWithFont:[UIFont systemFontOfSize:customFontSize] constrainedToSize:CGSizeMake(labelWidth, MAXFLOAT) lineBreakMode:NSLineBreakByWordWrapping];
return textSize.height+VERTICAL_CELL_PADDING;
}
Ok so now my problem.
The tableview is the result of a search action which after scanning a plist file shows the lines containing a given string.
Up to now that was it. But now with iOS 6 and NSAttributedString allowing easily to bold part of strings I decided to bold the search word.
It is working, it bolds the words I want but now I am no more able to calculate the cell height as sizeWithFont ask for a NSString. And as bolding takes a wider width I cannot simply calculate the cell height with the string without attributes.
I am simply stuck here.
Anyone can help me ?

In fact I simply had to read apple documentation of NSAttributedText.
In my case I have to replace the last two lines of code by
CGRect rectSize = [theText boundingRectWithSize:CGSizeMake(labelWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin context:NULL];
return rectSize.size.height+VERTICAL_CELL_PADDING;
FOLLOW UP iOS 7
I have been struggling making this work in iOS7 with attributed text.
Apple documentation says
In iOS 7 and later, this method returns fractional sizes (in the size
component of the returned CGRect); to use a returned size to size
views, you must use raise its value to the nearest higher integer
using the ceil function.
Which way obviously not working for me ! For me the solution was to add +1 to the ceil of height. This is probably a bug from Apple, but for me now everything works as in iOS6.
CGRect rectSize = [theAttributedText boundingRectWithSize:CGSizeMake(labelWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin context:NULL];
return ceil(rectSize.size.height) + 1 + VERTICAL_CELL_PADDING;

Related

Codename One: how to get the width in millimeters of a component?

I created an app to calculate the sizes of a multi-image, in order to use them in the Codename One designer.
As you can see in the screenshot below, my app has a slider and I get the width (in millimeters) of it. The problem is that the value of the selected width is incorrect. I tested the app on two Android devices and the measured lengths are different from the ones reported by the app.
You can see the full source code, however the relevant code is the following:
Label value = new Label("Move the cursor on the slider...");
Style thumbStyle = new Style();
thumbStyle.setFont(Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_LARGE), true);
Slider slider = new Slider();
slider.setMaxValue(1000);
slider.setMinValue(0);
slider.setProgress(20); // Set the starting value
slider.setThumbImage(FontImage.create("|", thumbStyle));
slider.setEditable(true); // to it works as a slider instead of a progress bar
slider.addActionListener(e -> {
Integer valueSelected = slider.getProgress();
Integer sliderWidth = slider.getWidth();
Double inch = sliderWidth.doubleValue() / (slider.getMaxValue() - slider.getMinValue()) * valueSelected / 100.0;
Integer millimeters = Double.valueOf(inch * 25.4).intValue();
value.setText("Value selected: " + millimeters.toString() + " mm");
});
Thank you very much for any help
Millimeter measures aren't accurate. A device can return different values or ratios for the convert method than the value it returns for the density flag.
Unfortunately, Googles test suite to certify a device as "good" doesn't actually cover these things. There isn't much we can do about that.

How to set pagesize in ConvertToXpsDocument(SaveOptions.XpsDefault);

I try to load an excel file and show in xpsdocument viewer by following code
XpsDocument xpsDocument = ef.ConvertToXpsDocument(SaveOptions.XpsDefault);
documentViewer.Tag = xpsDocument;
documentViewer.Document = xpsDocument.GetFixedDocumentSequence();
That works so far. The problem is that during the conversion the pagesize changes. It seems that a 8 by 11 inch pagesize is assumed and the document is streched. Excel document is designed for A4 papersize. That means the width grows and the last column moves to the next page.
How can I influence the paper size and border width for SaveOptions.XpsDefault??
The A4 format is 8.267" x 11.692" so it seems that the assumption is right. Nevertheless, you can change the paper size like the following:
ExcelWorksheet ws = ef.Worksheets.ActiveWorksheet;
ws.PrintOptions.PaperType = PaperType.A4;
However, regarding the content being moved to next page, this will require investigating your Excel file's content.
But in case you're interested you can explicitly specify that the content's width (and/or height) should fit on a single page, like the following:
ws.PrintOptions.FitWorksheetWidthToPages = 1;
Last regarding the borders, you can specify the width by using LineStyle.

Determine the 'real size' of a visual element

I'm creating a control that allows the user to create a visual element that can be printed. The challenge is the user can change the size of visual but needs to know the actual size of the visual they are creating. In other words I have the size of the element in pixels but I need to know what the actual size of the element will be in inches.
For example if I create a button that is 96 pixels wide, I would expect it to be 1 inch long, yet the size of the button is different on different monitors with different resolutions.
Is this possible?
It is not possible in cases where you don't know the actual PPI of your monitor (such as with a projector), but here is an attempt at calculating it from a Rectangle of 96x96 device independent pixels:
var ppi = 220; //The PPI on my monitor.
var devicePixels = GetElementPixelSize(MyRectangle); //Convert WPF pixels to device pixels
var widthInInches = devicePixels.Width / ppi; //Convert pixels to inches based on the PPI.
Using this code (from How do I convert a WPF size to physical pixels?)
public Size GetElementPixelSize(UIElement element)
{
Matrix transformToDevice;
var source = PresentationSource.FromVisual(element);
if (source != null)
transformToDevice = source.CompositionTarget.TransformToDevice;
else
using (var source2 = new HwndSource(new HwndSourceParameters()))
transformToDevice = source2.CompositionTarget.TransformToDevice;
if (element.DesiredSize == new Size())
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
return (Size)transformToDevice.Transform((Vector)element.DesiredSize);
}
In my case, 96 translates to 144px. 144/220 is 0.65656565 inches.
To get the ppi, take a look at this question: acquire monitor physical dimension
It is not an easy task, and there is no guarantee that simply because it is rendered a certain size, it will also look that way on print. A printer also has a DPI and a paper size, after all.
You really should google on WPF, DPI and Scaling because the scaling system of WPF has some important characteristics. From the search you will find this site for a first read about the topic.
About your question is this possible? Yes it surely is! ;)

Decimal keypad Xcode

I have a text box and a calculate button, I am using a Decimal keypad, when I run my program in the Xcode simulator i get a dot at the bottom left corner but when I run the program in my iPhone i get a comma in the bottom left corner. I know the simulator is set on US Locale and I am in Iceland, I don't get the same outcome if I use the comma instead of the dot. I need to use the dot in my text box, not comma. Is there any code that I can use that tell my program to use dot instead of comma or do I need to make a custom keyboard for this.
Not clear what you're after. If you want to change the way the keyboard looks, then you have to write your own keyboard. But if it is sufficient to respond to the comma by putting a dot in the text field, you can do that easily by intercepting the comma in the text field's delegate. Implement textField:shouldChangeCharactersInRange:replacementString:. Something like this (not tested so you might need to tweak it some):
-(BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
if (![string isEqualToString: #","])
return YES;
textField.text =
[textField.text stringByReplacingCharactersInRange:range
withString: #"."];
return NO;
}
If the problem is that the comma is okay, but in countries where the comma is the decimal point, you want it to be used as a decimal point, just use an NSNumberFormatter when you pull the string out of the text field and turn it into a number. Right now you are doing this by saying [_PipeID.text floatValue]. That call has no intelligence about the locale. NSNumberFormatter does:
NSNumberFormatter* nf = [[NSNumberFormatter alloc] init];
nf.locale = [NSLocale currentLocale];
NSNumber* n = [nf numberFromString: tf.text];
// and *then* you can convert the NSNumber to a float with `floatValue`
NSLog(#"%f",n.floatValue);
By the way, you said:
when I run my program in the Xcode simulator ... but when I run the program in my iPhone
But you can test this perfectly well on the Simulator; just use the Settings app to set the language and region formatting.
float s2 = [[dist.text stringByReplacingOccurrencesOfString:#"," withString:#"."] floatValue];
dist.text = [[NSString alloc] initWithFormat:#"%0.02f", s2];

How do I convert a WPF size to physical pixels?

What's the best way to convert a WPF (resolution-independent) width and height to physical screen pixels?
I'm showing WPF content in a WinForms Form (via ElementHost) and trying to work out some sizing logic. I've got it working fine when the OS is running at the default 96 dpi. But it won't work when the OS is set to 120 dpi or some other resolution, because then a WPF element that reports its Width as 96 will actually be 120 pixels wide as far as WinForms is concerned.
I couldn't find any "pixels per inch" settings on System.Windows.SystemParameters. I'm sure I could use the WinForms equivalent (System.Windows.Forms.SystemInformation), but is there a better way to do this (read: a way using WPF APIs, rather than using WinForms APIs and manually doing the math)? What's the "best way" to convert WPF "pixels" to real screen pixels?
EDIT: I'm also looking to do this before the WPF control is shown on the screen. It looks like Visual.PointToScreen could be made to give me the right answer, but I can't use it, because the control isn't parented yet and I get InvalidOperationException "This Visual is not connected to a PresentationSource".
Transforming a known size to device pixels
If your visual element is already attached to a PresentationSource (for example, it is part of a window that is visible on screen), the transform is found this way:
var source = PresentationSource.FromVisual(element);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
If not, use HwndSource to create a temporary hWnd:
Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.CompositionTarget.TransformToDevice;
Note that this is less efficient than constructing using a hWnd of IntPtr.Zero but I consider it more reliable because the hWnd created by HwndSource will be attached to the same display device as an actual newly-created Window would. That way, if different display devices have different DPIs you are sure to get the right DPI value.
Once you have the transform, you can convert any size from a WPF size to a pixel size:
var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize);
Converting the pixel size to integers
If you want to convert the pixel size to integers, you can simply do:
int pixelWidth = (int)pixelSize.Width;
int pixelHeight = (int)pixelSize.Height;
but a more robust solution would be the one used by ElementHost:
int pixelWidth = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Width));
int pixelHeight = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Height));
Getting the desired size of a UIElement
To get the desired size of a UIElement you need to make sure it is measured. In some circumstances it will already be measured, either because:
You measured it already
You measured one of its ancestors, or
It is part of a PresentationSource (eg it is in a visible Window) and you are executing below DispatcherPriority.Render so you know measurement has already happened automatically.
If your visual element has not been measured yet, you should call Measure on the control or one of its ancestors as appropriate, passing in the available size (or new Size(double.PositivieInfinity, double.PositiveInfinity) if you want to size to content:
element.Measure(availableSize);
Once the measuring is done, all that is necessary is to use the matrix to transform the DesiredSize:
var pixelSize = (Size)transformToDevice.Transform((Vector)element.DesiredSize);
Putting it all together
Here is a simple method that shows how to get the pixel size of an element:
public Size GetElementPixelSize(UIElement element)
{
Matrix transformToDevice;
var source = PresentationSource.FromVisual(element);
if(source!=null)
transformToDevice = source.CompositionTarget.TransformToDevice;
else
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.CompositionTarget.TransformToDevice;
if(element.DesiredSize == new Size())
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
return (Size)transformToDevice.Transform((Vector)element.DesiredSize);
}
Note that in this code I call Measure only if no DesiredSize is present. This provides a convenient method to do everything but has several deficiencies:
It may be that the element's parent would have passed in a smaller availableSize
It is inefficient if the actual DesiredSize is zero (it is remeasured repeatedly)
It may mask bugs in a way that causes the application to fail due to unexpected timing (eg. the code being called at or above DispatchPriority.Render)
Because of these reasons, I would be inclined to omit the Measure call in GetElementPixelSize and just let the client do it.
Simple proportion between Screen.WorkingArea and SystemParameters.WorkArea:
private double PointsToPixels (double wpfPoints, LengthDirection direction)
{
if (direction == LengthDirection.Horizontal)
{
return wpfPoints * Screen.PrimaryScreen.WorkingArea.Width / SystemParameters.WorkArea.Width;
}
else
{
return wpfPoints * Screen.PrimaryScreen.WorkingArea.Height / SystemParameters.WorkArea.Height;
}
}
private double PixelsToPoints(int pixels, LengthDirection direction)
{
if (direction == LengthDirection.Horizontal)
{
return pixels * SystemParameters.WorkArea.Width / Screen.PrimaryScreen.WorkingArea.Width;
}
else
{
return pixels * SystemParameters.WorkArea.Height / Screen.PrimaryScreen.WorkingArea.Height;
}
}
public enum LengthDirection
{
Vertical, // |
Horizontal // ——
}
This works fine with multiple monitors as well.
I found a way to do it, but I don't like it much:
using (var graphics = Graphics.FromHwnd(IntPtr.Zero))
{
var pixelWidth = (int) (element.DesiredSize.Width * graphics.DpiX / 96.0);
var pixelHeight = (int) (element.DesiredSize.Height * graphics.DpiY / 96.0);
// ...
}
I don't like it because (a) it requires a reference to System.Drawing, rather than using WPF APIs; and (b) I have to do the math myself, which means I'm duplicating WPF's implementation details. In .NET 3.5, I have to truncate the result of the calculation to match what ElementHost does with AutoSize=true, but I don't know whether this will still be accurate in future versions of .NET.
This does seem to work, so I'm posting it in case it helps others. But if anyone has a better answer, please, post away.
Just did a quick lookup in the ObjectBrowser and found something quite interesting, you might want to check it out.
System.Windows.Form.AutoScaleMode, it has a property called DPI. Here's the docs, it might be what you are looking for :
public const
System.Windows.Forms.AutoScaleMode Dpi
= 2
Member of System.Windows.Forms.AutoScaleMode
Summary: Controls scale relative to
the display resolution. Common
resolutions are 96 and 120 DPI.
Apply that to your form, it should do the trick.
{enjoy}

Resources