UISegmentedControl with rounded corner and non proportional segments bug - ios

i recently updated a rounded corner segment control to have different width for each segment.
the issue i have is that the last segment doesn't align properly with the end of the segmented control
i just used this code for this sample (seg being my segmented control) :
seg.layer.borderWidth = 1
seg.layer.cornerRadius = seg.bounds.height / 2
seg.layer.masksToBounds = true
seg.apportionsSegmentWidthsByContent = true
if i remove the masksToBounds line i can see that the right segment doesn't reach the edge of the segmented control.
is there any way to fix this issue?

It appears that this is caused by a rendering bug in UISegmentedControl when apportionsSegmentWidthsByContent = true. I couldn't find a simple workaround by manipulating the CALayers of the control. You can create a custom control using UIStackView to mimic the UISegmentedControl.
You should also file a radar with Apple.

I don't know if this still affects iOS, but a workaround in Xamarin.iOS is: Override UISegmentView, calculate text width of the last segment element and update it's width.
Sample code in C#:
public class MySegmentedControl : UISegmentedControl
{
public override void MovedToSuperview ()
{
// Fix truncation of last element
// when ApportionsSegmentWidthsByContent = true
if (ApportionsSegmentWidthsByContent) {
RecalculateLastSegmentWidth ();
}
}
private void RecalculateLastSegmentWidth()
{
var font = UIFont.FromName("Helvetica", 12);
var lastSegment = NumberOfSegments - 1;
var segmentTitle = TitleAt(lastSegment);
var segmentWidth = segmentTitle.StringSize(font).Width + 20;
SetWidth(segmentWidth, lastSegment);
}
}

Related

How to render a UIButton in Xamarin.iOS

How do I render a UIButton in Xamarin.iOS? See the current Code for the full list.
This is the code I'm using to create and add the button to the Xamarin.Forms.Platform.iOS.CellTableViewCell cell. I cannot get the button to display anything.
With the use of a Foundation.NSMutableAttributedString, it shows a cut-off section of text in the top left corner, regardless of anything I try (alignments, insets, bounds, various constraints, etc). I'm currently trying things from Xamarin.Forms.Platform.iOS.Renderers.ButtonRenderer, but still can't get anything to display at all, no text, no button, or its outline.
If you could fork the repo and fix it or post the solution here, I would be very grateful.
protected override void SetUpContentView()
{
var insets = new UIEdgeInsets(SVConstants.Cell.PADDING.Top.ToNFloat(), SVConstants.Cell.PADDING.Left.ToNFloat(), SVConstants.Cell.PADDING.Bottom.ToNFloat(), SVConstants.Cell.PADDING.Right.ToNFloat());
_Button = new UIButton(UIButtonType.RoundedRect)
{
AutoresizingMask = UIViewAutoresizing.All,
HorizontalAlignment = UIControlContentHorizontalAlignment.Center,
VerticalAlignment = UIControlContentVerticalAlignment.Center,
ContentEdgeInsets = insets,
// TitleEdgeInsets = insets
};
DefaultFontSize = _Button.TitleLabel.ContentScaleFactor;
DefaultTextColor = _Button.TitleLabel.TextColor;
_Recognizer = new UILongPressGestureRecognizer(RunLong);
_Button.TouchUpInside += OnClick; // https://stackoverflow.com/a/51593238/9530917
_Button.AddGestureRecognizer(_Recognizer); // https://stackoverflow.com/a/6179591/9530917
ContentView.AddSubview(_Button);
_Button.CenterXAnchor.ConstraintEqualTo(ContentView.CenterXAnchor).Active = true;
_Button.CenterYAnchor.ConstraintEqualTo(ContentView.CenterYAnchor).Active = true;
_Button.WidthAnchor.ConstraintEqualTo(ContentView.WidthAnchor).Active = true;
_Button.HeightAnchor.ConstraintEqualTo(ContentView.HeightAnchor).Active = true;
UpdateConstraintsIfNeeded();
LayoutIfNeeded();
}
Found out that you can't subclass it. Any button added to the view must be native (UIButton) or custom rendered, such as Xamarin.Forms.Platform.iOS.ButtonRenderer; It doesn't show up otherwise.

How can an effect find out the display size of the UIImageView it is attached to?

In Xamarin.Forms, an Effect can be attached to a View. In my case, the View is displaying an Image. And the effect is making a colored "glow" around the visible pixels of the image. XAML:
<Image Source="{Binding LogoImage}" ...>
<Image.Effects>
<effects:GlowEffect Radius="5" Color="White" />
</Image.Effects>
</Image>
The effect is implemented as a subclass of RoutingEffect:
public class GlowEffect : Xamarin.Forms.RoutingEffect
{
public GlowEffect() : base("Core.ImageGlowEffect")
{
}
...
}
On each platform, there is a PlatformEffect to implement the effect. For iOS:
using ...
[assembly: ExportEffect(typeof(Core.Effects.ImageGlowEffect), "ImageGlowEffect")]
namespace Core.Effects
{
public class ImageGlowEffect : PlatformEffect
{
protected override void OnAttached()
{
ApplyGlow();
}
protected override void OnElementPropertyChanged( PropertyChangedEventArgs e )
{
base.OnElementPropertyChanged( e );
if (e.PropertyName == "Source") {
ApplyGlow();
}
}
private void ApplyGlow()
{
var imageView = Control as UIImageView;
if (imageView.Image == null)
return;
var effect = (GlowEffect)Element.Effects.FirstOrDefault(e => e is GlowEffect);
if (effect != null) {
CGRect outSize = AVFoundation.AVUtilities.WithAspectRatio( new CGRect( new CGPoint(), imageView.Image.Size ), imageView.Frame.Size );
...
}
}
...
}
}
The above code works if Source is changed dynamically: at the time Source changes, the UIImageView (Bounds or Frame) has a size. BUT if the Source is set statically in XAML, that logic does not run: so the only call to ApplyGlow is during OnAttach. UNFORTUNATELY, during OnAttach, the UIImageView has size (0, 0).
How get this effect to work on iOS, with a static Source?
NOTE: The equivalent Android effect works via a handler attached to ImageView.ViewTreeObserver.PreDraw - at which time the size is known. So if there is an iOS equivalent event, that would be one way to solve.
More Details:
The original implementation used the original image size (imageView.Image.Size) - which is available during OnAttach. This can be made to "work", but is not satisfactory: the glow is applied to the full size image. If the image is significantly larger than the view area, the glow becomes much too small a radius (iOS shrinks the image+glow as it renders): it does not have the desired appearance.
ApplyGlow has an option to apply a tint color to the image. That tint color is different than the glow color. I mention this because it restricts the possible solutions: AFAIK, can't just set options on an image and let iOS figure out how to render it - need to explicitly resize the image and draw the resized tinted image on top of a blurred version (the glow). This code all works - if imageView.Bounds.Size (or imageView.Frame.Size) is available (and non-zero).
With a breakpoint in OnElementPropertyChanged, I've checked to see if imageView size is known for any property that is always set. No; if no properties are dynamically set, the property changes all occur before imageView has a size.
Maybe it's a workaround and I don't know if it is acceptable to you.
Add a little delay before calling ApplyGlow(); in OnAttached. After the delay, you will get the imageView.Frame.Size or imageView.Bounds.Size.
protected override async void OnAttached()
{
await Task.Delay(TimeSpan.FromSeconds(0.3));// it can be 0.2s,0.1s, depending on you
ApplyGlow();
}
And if you have set WidthRequest, HeightRequest, you can get there without the delay:
private void ApplyGlow()
{
var imageView = Control as UIImageView;
if (imageView.Image == null)
return;
Console.WriteLine(imageView.Image.Size.Width);
Console.WriteLine(imageView.Image.Size.Height);
CoreGraphics.CGSize rect = imageView.Bounds.Size;
CoreGraphics.CGSize rect2 = imageView.Frame.Size;
Console.WriteLine(rect);
Console.WriteLine(rect2);
double width = (double)Element.GetValue(VisualElement.WidthRequestProperty);
double height = (double)Element.GetValue(VisualElement.HeightRequestProperty);
Console.WriteLine(width);
Console.WriteLine(height);
double width2 = (double)Element.GetValue(VisualElement.WidthProperty);
double height2 = (double)Element.GetValue(VisualElement.HeightProperty);
Console.WriteLine(width2);
Console.WriteLine(height2);
}

Why isn't cell resizing when content is taller than the EstimatedSize (sizeHint)

I ported a table view implementation from Swift with a variable height cell. But in my Xamarin/ReactiveUI implementation, the cell does not resize when the content grows (i.e. when an optional label is displayed). Please note that the cell does resize to fit in the native Swift implementation.
Most answers online focus on the setting of two properties on UITableView: RowHeight and EstimatedHeight as well as using AutoLayout. My row height is set to UITableView.AutomaticDimension and the EstimatedHeight is set to 44f. I am using autolayout constraints which I will show below. I am using the BindTo extensions in ReactiveTableViewSourceExtensions.
I also tried setting the Text property of the optional label right away to test the theory that setting the property up front would provoke a resize.
Here are the pertinent lines of code:
In a ReactiveViewController<T> ctor:
this.WhenActivated(disposables =>
{
//// ...
// This method automatically wires up the cell reuse key to be the nameof my cell class which is what I want.
this.WhenAnyValue(view => view._cells)
.BindTo<IImportedFileViewModel, ImportedFileCell>(TableView, 44f)
.DisposeWith(disposables);
//// ...
});
Below in ViewDidLoad:
//// ...
TableView = new UITableView
{
RowHeight = UITableView.AutomaticDimension,
EstimatedRowHeight = 44f, // Setting or not setting this doesn't matter
SeparatorStyle = UITableViewCellSeparatorStyle.None,
AllowsSelection = false,
TranslatesAutoresizingMaskIntoConstraints = false,
};
//// ...
In the cell implementation:
ClipsToBounds = true;
PreservesSuperviewLayoutMargins = true;
ContentView.ClipsToBounds = true;
ContentView.PreservesSuperviewLayoutMargins = true;
var stackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Vertical,
Distribution = UIStackViewDistribution.Fill,
Alignment = UIStackViewAlignment.Fill,
Spacing = 4,
};
var fileStackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Horizontal,
Distribution = UIStackViewDistribution.Fill,
Alignment = UIStackViewAlignment.Top,
};
FilenameLabel = new UILabel
{
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(15f, UIFontWeight.Medium),
};
fileStackView.AddArrangedSubview(FilenameLabel);
StatusImage = new UIImageView()
{
TranslatesAutoresizingMaskIntoConstraints = false,
ContentMode = UIViewContentMode.Center,
};
fileStackView.AddArrangedSubview(StatusImage);
stackView.AddArrangedSubview(fileStackView);
var reasonStackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Horizontal,
Distribution = UIStackViewDistribution.Fill,
Alignment = UIStackViewAlignment.Top,
};
// This is the optional label that, when its Text property is set, should resize the cell.
FailureReasonLabel = new UILabel
{
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(13f, UIFontWeight.Medium),
Lines = 0,
};
reasonStackView.AddArrangedSubview(FailureReasonLabel);
stackView.AddArrangedSubview(reasonStackView);
ContentView.AddSubview(stackView);
stackView.BottomAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.BottomAnchor).Active = true;
stackView.TopAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TopAnchor).Active = true;
stackView.LeadingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.LeadingAnchor).Active = true;
stackView.TrailingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TrailingAnchor).Active = true;
StatusImage.HeightAnchor.ConstraintEqualTo(16f).Active = true;
StatusImage.WidthAnchor.ConstraintEqualTo(16f).Active = true;
I would be happy to get a solution to the problem using the basic structure I have now but I would also accept an example of some other pattern that someone has used (based on ReactiveUI) to get this working. I would prefer that the solution not be based on old manual resizing patterns like in the pre-iOS 8 days or some sort of hack.
So two things had to be adjusted to fix my issue.
The sizeHint parameter in the ReactiveUI BindTo method does not behave like TableView.EstimatedRowHeight, which is what I was assuming. So I ended up setting it to the auto dimension constant like this:
.BindTo<IImportedFileViewModel, ImportedFileCell>(TableView, (float)UITableView.AutomaticDimension)
I was thinking that I could just update the properties of the view models within the IObservableCollection implementation that is passed in to the BindTo method but it wasn't until I started provoking changed events on the collection itself (the collection that the view is bound to) that the cells started resizing themselves. So since I am using DynamicData as part of ReactiveUI, that meant calling SourceCache.AddOrUpdate(updatedViewModel) whenever I knew that the FailureReasonLabel had been set.
I am also going to try and restore the ReactiveUI tag that was removed from this post because I believe it to be a relevant part of this question and its answer.

Effect "Label.AdjustsFontSizeToFitWidth" has no effect while other effects work

I've tried the Xamarin effects mentioned in this article:
https://smstuebe.de/2016/08/29/underlinedlabel.xamarin.forms/
(Which derived from this stackoverflow question).
It works perfectly fine.
However, when I instead try to apply the label effect "AdjustsFontSizeToFitWidth", it just doesn't work.
For testing this, I've set the font size in the same sample code to 300, and I changed these lines
var label = (UILabel)Control;
var text = (NSMutableAttributedString)label.AttributedText;
var range = new NSRange(0, text.Length);
to these lines:
var label = (UILabel)Control;
var text = (NSMutableAttributedString)label.AttributedText;
var range = new NSRange(0, text.Length);
label.Lines = 1;
label.MinimumFontSize = 6;
label.AdjustsFontSizeToFitWidth = true;
I've tested it with iPhone iPhone 6 iOS 11.1 and iPhone X iOS 11.1 in the simulator.
Is there something special about this property which makes it un-usable for Xamarin effects or is it possible that this effect works only on a real device?
You need to make sure that you are setting a line truncation on the Label otherwise the normal iOS LabelRenderer is going to set the Lines property to 0 (i.e. unlimited multi-line) and thus there is nothing to resize as the entire control to sized to the text size. Also you can not set the Lines via an Effect as the routing effect happens before the renderer sets its properties and it gets reset to 0;
Set the LineBreakMode property:
<Label Text="Welcome to SizeToFitEffects, SizeToFitEffects, SizeToFitEffects, SizeToFitEffects"
LineBreakMode="TailTruncation">
<Label.Effects>
<sushi:LabelSizeToFitEffect />
</Label.Effects>
</Label>
In the effect, it is wise to set the MinimumScaleFactor to something reasonable otherwise you can end up with a font so small it is unreadable.
public class LabelSizeToFitEffect : PlatformEffect
{
protected override void OnAttached()
{
SetSizeToFit(true);
}
protected override void OnDetached()
{
SetSizeToFit(false);
}
void SetSizeToFit(bool sizeToFit)
{
var label = Control as UILabel;
label.AdjustsFontSizeToFitWidth = sizeToFit;
label.MinimumScaleFactor = 0.33f;
}
}

How to scale the fonts in a Button

I'm trying to complete my first app in Swift and I met with the problem.
I have some buttons with the tittles and background image. Running the simulation on different devices makes them scale, so the tittles goes out of buttons frame.
There is no "Dynamic Type Automatically Adjust Font" checkbox in my Xcode attributes inspector so I made the custom UIButton class and made the inspectable var
#IBInspectable var adjustFontSize : Bool {
set { titleLabel?.adjustsFontForContentSizeCategory = newValue }
get { return titleLabel!.adjustsFontForContentSizeCategory }
but this does't helps and I got "Automatically adjusts font requires using a dynamic type text style" warning
So how can I make my button title scale to fit the Button frame when the button changes size and proportions on different devices?
This worked for me.
btn.titleLabel?.minimumScaleFactor = 0.1
btn.titleLabel?.numberOfLines = 1
btn.titleLabel?.adjustsFontSizeToFitWidth = true
Try these
button.titleLabel!.numberOfLines = 1
button.titleLabel!.adjustsFontSizeToFitWidth = true
button.titleLabel!.baselineAdjustment = .alignCenters
but for them to work, you will have to set button width constraint either in storyboard or programatically.
This will then make the title size change based on the width of the button.

Resources