How to create UIStackView from code correctly - ios

I can't understand why this layout won't render the way i need it. Completing the requirement on android with linearlayouts should be equivalent to this?
What i get:
What i need:
My code:
// my stackview only renders at all if i pass any height > 0 && width > 0,
// but i would love this to be as small as possible, given that all children
// are rendered.
var stackView = new UIStackView(View.Frame);
stackView.Axis = UILayoutConstraintAxis.Vertical;
stackView.Distribution = UIStackViewDistribution.Fill;
foreach (var deviceVm in devices)
{
// i would prefer this not to have a rect parameter and scale as needed.
var descriptionLabel = new UILabel(new CGRect(0, 0, View.Bounds.Width, 20));
descriptionLabel.Text = deviceVm.Description;
descriptionLabel.TextColor = UIColor.Blue;
descriptionLabel.ContentMode = UIViewContentMode.ScaleToFill;
stackView.AddArrangedSubview(descriptionLabel);
}
// i assume this should make it as small as possible, as big as necessary?
stackView.SizeToFit();
var scrollView = new UIScrollView(View.Frame);
scrollView.BackgroundColor = UIColor.Yellow;
scrollView.AddSubview(stackView);
View.AddSubview(scrollView);
Can someone spot a mistake i am doing here? Am i configuring the StackView incorrectly or is this control not the equivalent of LinearLayout(android) / StackPanel(wpf) ?
PS: Sorry for eye cancer paint pictures.

Related

How do I get text to wrap and add another line below in a Xamarin Forms iOS custom renderer?

I am creating a custom renderer to display a title and description in a translucent white space on an image. The title should wrap to the second line if wider than the image, and an optional description may appear below if there is room. In Android I am able to do this using a StaticLayout:
// Create text rectangle
var height = Height / 3;
canvas.Save();
canvas.ClipRect(0, Height - height, Width, Height);
canvas.DrawARGB(191, 255, 255, 255);
canvas.Restore();
var item = ((ImageTile) Element).Item;
var textSize = (height - 15) / 2;
var textPaint = new TextPaint
{
StrokeWidth = 5,
TextSize = textSize,
FakeBoldText = true,
};
if (Build.VERSION.SdkInt >= BuildVersionCodes.Honeycomb)
SetLayerType(LayerType.Software, textPaint);
textPaint.SetStyle(Paint.Style.Fill);
textPaint.Color = global::Android.Graphics.Color.Black;
// init StaticLayout for text
var titleLayout = new StaticLayout(
item.Title, textPaint, Width - 10, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, false);
canvas.Translate(5, Height - height + 5);
titleLayout.Draw(canvas);
canvas.Restore();
textPaint = new TextPaint
{
StrokeWidth = 4,
TextSize = textSize - 10,
};
if (Build.VERSION.SdkInt >= BuildVersionCodes.Honeycomb)
SetLayerType(LayerType.Software, textPaint);
var descLayout = new StaticLayout(
item.Description, textPaint, Width - 10, Android.Text.Layout.Alignment.AlignNormal, 1.0f, 0.0f, false);
canvas.Translate(5, Height - height + titleLayout.Height + 5);
descLayout.Draw(canvas);
canvas.Restore();
in iOS I am using CATextLayers, but I am unable to get the text to wrap even thought I define the frame and set both Wrapped to true and TextTruncationMode to None. I also don't know how to get the actual height of the tilteLayer so I can position the descLayer below it. This is what I have so far, which draws the title and description on top of each other without wraping.
var textLayer = new CALayer();
var textRec = new CGRect(0, element.HeightRequest - textheight, element.WidthRequest,
textheight);
textLayer.Frame = textRec;
var backgroundcolor = Color.FromRgba(255, 255, 255, .25).ToCGColor();
textLayer.BackgroundColor = backgroundcolor;
Control.Layer.AddSublayer(textLayer);
var titleLayer = new CATextLayer
{
String = element.Item.Title,
ForegroundColor = Color.Black.ToCGColor(),
FontSize = 14,
Wrapped = true,
TextTruncationMode = CATextLayerTruncationMode.None,
TextAlignmentMode = CATextLayerAlignmentMode.Left,
//Bounds = new CGRect(2, element.HeightRequest - textheight + 2, element.WidthRequest - 4,
// textheight - 4),
};
var titleRec = new CGRect(2, element.HeightRequest - textheight + 2, element.WidthRequest - 4,
textheight - 4);
titleLayer.Frame = titleRec;
Control.Layer.AddSublayer(titleLayer);
var descLayer = new CATextLayer
{
String = element.Item.Description,
ForegroundColor = Color.Black.ToCGColor(),
FontSize = 12,
Wrapped = true,
TextTruncationMode = CATextLayerTruncationMode.None,
};
var descRec = new CGRect(2, element.HeightRequest - textheight + 2, element.WidthRequest - 4,
textheight - 4);
descLayer.ContentsRect = descRec;
Control.Layer.AddSublayer(descLayer);
Why not try autoLayout? You want to add a background view and two types of text on the original image. CALayer may achieve your effect, but it can't use autoLayout so that you need to use hard code(calculate the text height and the layer's position) to construct that. Also you said
I am unable to get the text to wrap. And I also don't know how to get
the actual height of the tilteLayer so I can position the descLayer
below it.
In the image's custom renderer, since this control has not been rendered, its Frame and HeightRequest are also unknown. Then you won't get the correct frame neither the layer, so the text will not be shown. I think the best way to do that is using AutoLayout:
// Create a view to hold content just like your textLayer
UIView bgView = new UIView();
bgView.BackgroundColor = UIColor.FromRGBA(1, 1, 1, 0.25f);
bgView.TranslatesAutoresizingMaskIntoConstraints = false;
Control.AddSubview(bgView);
bgView.LeadingAnchor.ConstraintEqualTo(Control.LeadingAnchor).Active = true;
bgView.TopAnchor.ConstraintGreaterThanOrEqualTo(Control.TopAnchor).Active = true;
bgView.TrailingAnchor.ConstraintEqualTo(Control.TrailingAnchor).Active = true;
bgView.BottomAnchor.ConstraintEqualTo(Control.BottomAnchor).Active = true;
UILabel titleLabel = new UILabel();
bgView.AddSubview(titleLabel);
titleLabel.TranslatesAutoresizingMaskIntoConstraints = false;
// Set this property to 0, then your label will move to several lines if your text is too large.
titleLabel.Lines = 0;
titleLabel.Font = UIFont.SystemFontOfSize(14);
titleLabel.Text = Element.Item.Title;
titleLabel.LeadingAnchor.ConstraintEqualTo(bgView.LeadingAnchor).Active = true;
titleLabel.TopAnchor.ConstraintEqualTo(bgView.TopAnchor).Active = true;
titleLabel.TrailingAnchor.ConstraintEqualTo(bgView.TrailingAnchor).Active = true;
// This constraint will show the titleLabel's content at high priority. It means show the descLabel if the image has enough place.
titleLabel.SetContentHuggingPriority(249, UILayoutConstraintAxis.Vertical);
UILabel descLabel = new UILabel();
bgView.AddSubview(descLabel);
descLabel.TranslatesAutoresizingMaskIntoConstraints = false;
descLabel.Lines = 0;
descLabel.Text = Element.Item.Description;
descLabel.Font = UIFont.SystemFontOfSize(12);
descLabel.LeadingAnchor.ConstraintEqualTo(bgView.LeadingAnchor).Active = true;
descLabel.TopAnchor.ConstraintEqualTo(titleLabel.BottomAnchor).Active = true;
descLabel.TrailingAnchor.ConstraintEqualTo(bgView.TrailingAnchor).Active = true;
descLabel.BottomAnchor.ConstraintEqualTo(bgView.BottomAnchor).Active = true;
In this way, bgView will expand its height depending on the titleLabel and descLabel. The largest height will be the original image's height. Moreover titleLabel will auto calculate its size depending on its content. Also the descLabel will always lies below the titleLabel if the room allows.
You can adjust these constraints to fit your own requirement.

Adjust UILabel height by content in xamarin.ios

i have 3 types of string labels, "loading...", "loading, please wait" and "it's taking more than we expected, sorry...". I want to adjust the height of the IULabel and center in the frame. I'm using this:
labelTitle = new UILabel (new CGRect (
frame.Width / 4,
frame.Height / 2 + Dimensions.margin_small,
frame.Width/2,
40
));
labelTitle.StyleTitle ();
labelTitle.TextAlignment = UITextAlignment.Center;
labelTitle.LineBreakMode = UILineBreakMode.WordWrap;
labelTitle.Lines = 0;
But don't know how to resize the height
try this.
labelTitle = new UILabel ();
labelTitle.StyleTitle ();
labelTitle.TextAlignment = UITextAlignment.Center;
labelTitle.LineBreakMode = UILineBreakMode.WordWrap;
labelTitle.Lines = 0;
And now give the label center point.

How can I set one subview below another subview in xamarin iOS

var mainStack = new UIStackView();
mainStack.Alignment = UIStackViewAlignment.Leading;
mainStack.Spacing = 10;
mainStack.Axis = UILayoutConstraintAxis.Vertical;
mainStack.Frame = new CGRect(10,10,Frame.Width - 30,Frame.Height - 100);
var mainStack1 = new UIStackView();
mainStack1.Alignment = UIStackViewAlignment.Center;
mainStack1.Spacing = 10;
mainStack1.Axis = UILayoutConstraintAxis.Vertical;
mainStack1.Frame = new CGRect(5, 5, Frame.Width - 30, Frame.Height - 200);
this.AddSubview(mainStack);
this.AddSubview(mainStack1);
These are the two subviews i have used, I want to insert one subview below to another subview. I faced a problem that the two subviews are override.
The easiest thing is to change the order you add the views. The last one added will be on top, so if the issue is just with the exact code you shared, just reverse the order of adding the subviews:
this.AddSubview(mainStack1);
this.AddSubview(mainStack);
Also you can bring a subview to the front with (assuming this is a view):
this.BringSubviewToFront(mainStack);
Or you can adjust the Z-Order using the Layer property of a view:
mainStack1.Layer.ZPosition = 0;
mainStack.Layer.ZPosition = 1;
UIView.Layer with higher z positions will appear in front of UIView.Layer with lower z positions.

How to remove labels from after drawing with drawrect()

I am pretty new to drawing programmatically. I've managed to draw a line graph, complete with points, lines, auto-scaling axes, and axis labels. In other screens, I can change characteristics of the graph and when I return to the screen, I refresh it with setNeedsDisplay() in the viewWillAppear function of the containing viewController. The lines are redrawn perfectly when I do this.
The new data that is added in other screens may require rescaling the axes. The problem is that when the graph is redrawn, the number labels on the axes are just added to the graph, without removing the old ones, meaning that some labels may be overwritten, while some old ones just remain there next to the new ones.
I think I see why this happens, in that I am creating a label and adding a subview, but not removing it. I guess I figured that since the lines are erased and redrawn, the labels would be, too. How do I cleanly relabel my axes? Is there a better way to do this? My function for creating the labels is listed below. This function is called by drawRect()
func createXAxisLabels(interval: Float, numIntervals: Int) {
let xstart: CGFloat = marginLeft
let yval: CGFloat = marginTop + graphHeight + 10 // 10 pts below the x-axis
var xLabelVals : [Float] = [0]
var xLabelLocs : [CGFloat] = [] // gives the locations for each label
for i in 0...numIntervals {
xLabelLocs.append(xstart + CGFloat(i) * graphWidth/CGFloat(numIntervals))
xLabelVals.append(Float(i) * interval)
}
if interval >= 60.0 {
xUnits = "Minutes"
xUnitDivider = 60
}
for i in 0...numIntervals {
let label = UILabel(frame: CGRectMake(0, 0, 50.0, 16.0))
label.center = CGPoint(x: xLabelLocs[i], y: yval)
label.textAlignment = NSTextAlignment.Center
if interval < 1.0 {
label.text = "\(Float(i) * interval)"
} else {
label.text = "\(i * Int(interval/xUnitDivider))"
}
label.font = UIFont.systemFontOfSize(14)
label.textColor = graphStructureColor
self.addSubview(label)
}
}
drawRect should just draw the rectangle, not change the view hierarchy. It can be called repeatedly. Those other labels are other views and do their own drawing.
You have a couple of options
Don't use labels -- instead just draw the text onto the rect.
-or-
Add/remove the labels in the view controller.
EDIT (from the comments): The OP provided their solution
I just replaced my code inside the for loop with:
let str : NSString = "\(xLabelVals[i])"
let paraAttrib = NSMutableParagraphStyle()
paraAttrib.alignment = NSTextAlignment.Center
let attributes = [
NSFontAttributeName: UIFont.systemFontOfSize(14),
NSForegroundColorAttributeName: UIColor.whiteColor(),
NSParagraphStyleAttributeName: paraAttrib]
let xLoc : CGFloat = CGFloat(xLabelLocs[i] - 25)
let yLoc : CGFloat = yval - 8.0
str.drawInRect(CGRectMake(xLoc, yLoc, 50, 16), withAttributes: attributes)

Using variables in CGRectMake, Swift, UIkit

For an App I'm making i need to use variables to change the size and position of objects (Labels). I've tried var example = CGRectMake(0, 0, 0, 100), hoping it would ignore the zeros (Not really thinking it would though). I then tried:
var example = 100
Label1.frame = CGRectMake(20, 20, 50, example)
I changed the syntax a bit, adding "" and replacing the CGRectMake with CGRect etc, but nothing worked... I don't get what I'm doing wrong here... Help!
Below is the new syntax used since Swift 3.
CGRect(x: 0, y: 0, width: 100, height: 100)
CGRectMake takes CGFloats for all of its arguments. Your sample code should work fine if you specify that example is supposed to be a CGFloat, using a type identifier:
// v~~~~ add this...
var example: CGFloat = 100
Label1.frame = CGRectMake(20, 20, 50, example)
Otherwise, swift infers the type of example to be Int, and the call to CGRectMake fails, cuz it can't take an Int as a parameter...
So, there is many ways to skin the cat. It all depends what your needs and requirements are (maybe you could elaborate a bit on what you are trying to achieve?). But one way to do it could be to set a variable when something happens, and then update the frame of the label. If you added a tap gesture recognizer to your view, and updated your label like so:
let myLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
let tapGestRecog = UITapGestureRecognizer(target: self, action: "handleTap:")
self.view.addGestureRecognizer(tapGestRecog)
}
func handleTap(sender:UIGestureRecognizer) {
let newXposition = sender.locationInView(self.view).x
let newYposition = sender.locationInView(self.view).y
myLabel.frame = CGRectMake(newXposition, newYposition, 200, 200)
}
This is just an example, and a very crude way of doing it. There are many other ways of doing it, but it hopefully gives you an idea of how to achieve it.
Swift allows syntax that Objective-C does not:
var example = 100
label.frame.size.height = example
In objective-C you would have to do it differently:
CGRect frame = label.frame; //Create a temporary rect to hold the frame value
frame.size.height = example;
label.frame = frame;

Resources