Custom Rendering on IOS a CardView from Frame - ios

I am trying to make a CardView on IOS Xamarin Forms by subclassing the Frame and making a custom renderer. I want to achieve something like this:
Upon looking into the API on setting a shadow, I've done this:
[assembly: ExportRenderer(typeof(CardView), typeof(CardViewRenderer))]
namespace TrabbleMobile.iOS.CustomRenderers
{
public class CardViewRenderer : FrameRenderer
{
public override void Draw(CGRect rect)
{
var cardView = (CardView)this.Element;
using (var context = UIGraphics.GetCurrentContext())
{
//nfloat cornerRadius = 2;
float shadowOffsetWidth = 2;
float shadowOffsetHeight = 4;
var shadowColor = new CGColor(0, 0, 0, (nfloat)0.5);
var boxColor = new CGColor(255, 255, 255);
var shadowBlur = (float)0.5;
var offset = new CGSize(shadowOffsetWidth, shadowOffsetHeight);
context.SetShadow(offset, shadowBlur, shadowColor);
However, it does not render as it should and no shadow at all.

I've done this, and the way that I did this is thru custom rendering on IOS and here is the custom renderer code:
public class CardViewRenderer : FrameRenderer
{
public override void Draw(CGRect rect)
{
SetupShadowLayer();
base.Draw(rect);
}
void SetupShadowLayer()
{
Layer.CornerRadius = 2; // 5 Default
if (Element.BackgroundColor == Xamarin.Forms.Color.Default)
{
Layer.BackgroundColor = UIColor.White.CGColor;
}
else
{
Layer.BackgroundColor = Element.BackgroundColor.ToCGColor();
}
Layer.ShadowRadius = 2; // 5 Default
Layer.ShadowColor = UIColor.Black.CGColor;
Layer.ShadowOpacity = 0.4f; // 0.8f Default
Layer.ShadowOffset = new CGSize(0f, 2.5f);
if (Element.OutlineColor == Xamarin.Forms.Color.Default)
{
Layer.BorderColor = UIColor.Clear.CGColor;
}
else
{
Layer.BorderColor = Element.OutlineColor.ToCGColor();
Layer.BorderWidth = 1;
}
Layer.RasterizationScale = UIScreen.MainScreen.Scale;
Layer.ShouldRasterize = true;
}
}

Related

What methods are called after DidSelectAnnotationView/DidDeSelectAnnotationView in MapView in Xamarin iOS?

I'm working on a Xamarin iOS project and have a view with a MapView that contains modified MKAnnotations and implements IMKMapViewDelegate.
The view shows a map with a few annotations on the map. Tapping an annotation displays a callout bubble with extra info.
The view also has an overlay view at the bottom of the screen which can be swiped up to display detailed info. The problem is that when an annotation is selected or deselected, the overlay view moves half way up the screen. The annotation selection event calls the methods DidSelectAnnotationView/DidDeselectAnnotationView but the movement of the overlay is triggered after those calls and I cannot hit any break points in any of my code when this event occurs.
So the question is, which iOS methods get called after DidSelectAnnotationView and DidDeselectAnnotationView which could be causing my issue?
The code below reverts the overlay back to its original position but with an obvious UI glitch.
[Export("mapView:didSelectAnnotationView:")]
public void DidSelectAnnotationView(MKMapView mapView, MKAnnotationView annotationView)
{
DelayedOverlayReset();
}
[Export("mapView:didDeselectAnnotationView:")]
public void DidDeselectAnnotationView(MKMapView mapView, MKAnnotationView annotationView)
{
DelayedOverlayReset();
}
private async Task DelayedOverlayReset()
{
await Task.Run(async () =>
{
Thread.Sleep(150);
});
_overlayView?.SetUpOverlayMinPosition();
}
I can share more code but its work related so I need to anonymise it. Happy to provide further detail or answer questions as necessary.
EDIT overlay view:
namespace Touch.Views.Detail
{
[Register("DetailsOverlayView")]
public sealed class DetailsOverlayView : UIView
{
private const float OverlayDragBarWidth = 56.0f;
private const float OverlayDragBarTopPadding = 20.0f;
private const float OverlayDragBarHeight = 6.0f;
private const float OverlayDragBarBottomPadding = 18.0f;
private const float CornerRadius = 18.0f;
private const float ScoreImageWidth = 60.0f;
private const float IconImageWidth = 16.0f;
private const float ShadowOpacity = 0.15f;
private const float ShadowRadius = 10.0f;
private const float ShadowOffsetWidth = 0f;
private const float ShadowOffsetHeight = 10.0f;
private const float ViewAnchorMidPoint = 0.5f;
private const float ImageDurationToLabelGap = 30;
private const float AddressIconWidthAndPadding = 18.0f;
private const double StartingOverlayAnimationSpeed = 0.3;
private const double AnimationSpeedThreshold = 1.3;
private const string DistanceImageName = "icon_map";
private const string DurationImageName = "icon_clock";
private static readonly TextStyle Heading3SemiBold = TextStyle.Heading3.SemiBold.BrandSecondary;
private static readonly TextStyle BodyRegularCaption = TextStyle.Body.Regular.TextCaption;
private readonly DetailOverlayDisplayData? _displayData;
private readonly double _maxPosition;
private readonly double _minPosition;
private readonly UIView? _parentView;
private readonly ElementLinksViewModel? _ActionViewModel;
private NSLayoutConstraint? _dragBarBottomPadding;
private UIView? _overlayDragBarView;
private UIView? _overlayHeaderAndContentView;
private UIPanGestureRecognizer? _panGestureRecognizer;
private UIScrollView? _scrollView;
private UIStackView? _stackView;
private double _startingPosition;
public NSLayoutConstraint? OverlayHeight;
public DetailsOverlayView(DetailOverlayDisplayData displayData, ElementLinksViewModel action,
UIView parentView)
{
_parentView = parentView;
_displayData = displayData;
_ActionViewModel = action;
TranslatesAutoresizingMaskIntoConstraints = false;
BackgroundColor = UIColor.White;
ClipsToBounds = false;
Layer.CornerRadius = CornerRadius;
Hidden = true;
Layer.ShadowOpacity = ShadowOpacity;
Layer.ShadowOffset = new CGSize(ShadowOffsetWidth, -ShadowOffsetHeight);
Layer.ShadowRadius = ShadowRadius;
SetUp();
var headerView = new DetailHeaderView();
headerView.SetUp(displayData, _scrollView!, _stackView!, parentView);
CreateContentPanel();
SetUpOverlayConstraints();
_minPosition = Math.Round(parentView.Frame.Height - GetHeaderHeight(displayData, parentView.Bounds.Width));
_maxPosition = 0;
}
public void SetUpOverlayStartingPosition()
{
if (_parentView == null)
{
return;
}
Center = new CGPoint(Center.X, _parentView.Frame.Height);
Layer.AnchorPoint = new CGPoint(ViewAnchorMidPoint, 0f);
Hidden = false;
SetOverlayPosition(StartingOverlayAnimationSpeed, _minPosition, CornerRadius, true);
_overlayDragBarView!.Hidden = false;
}
public void SetUpOverlayMinPosition()
{
SetOverlayPosition(0.01, _minPosition, CornerRadius, true);
_overlayDragBarView!.Hidden = false;
}
private void SetUp()
{
_overlayDragBarView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
BackgroundColor = UIColor.LightGray,
ClipsToBounds = true
};
_overlayDragBarView.RoundCorners(ViewCornerStyle.All, OverlayDragBarHeight / 2);
_overlayHeaderAndContentView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
ClipsToBounds = true
};
AddSubview(_overlayDragBarView);
AddSubview(_overlayHeaderAndContentView);
_panGestureRecognizer = new UIPanGestureRecognizer(PanOverlayView);
_panGestureRecognizer.CancelsTouchesInView = true;
AddGestureRecognizer(_panGestureRecognizer);
var (outerScrollView, _, innerStackView) =
_overlayHeaderAndContentView!.CreateScrollViewWithFooterAndStackView(UIColor.White);
_stackView = innerStackView;
_stackView.LayoutMarginsRelativeArrangement = true;
_scrollView = outerScrollView;
_scrollView.UserInteractionEnabled = false;
_scrollView.Bounces = true;
_scrollView.SetContentOffset(CGPoint.Empty, true);
_scrollView.Scrolled += ScrollViewOnScrolled;
}
private void CreateContentPanel()
{
var view = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
BackgroundColor = UIColor.Clear,
ClipsToBounds = true
};
var contentStackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Vertical,
LayoutMarginsRelativeArrangement = true,
Spacing = StyleGuideDimens.PaddingSmall,
DirectionalLayoutMargins =
new NSDirectionalEdgeInsets(0, StyleGuideDimens.PaddingSmall, StyleGuideDimens.PaddingSmall,
StyleGuideDimens.PaddingSmall)
};
_stackView!.AddArrangedSubview(view);
view.AddSubview(contentStackView);
NSLayoutConstraint.ActivateConstraints(new[]
{
view.LeadingAnchor.ConstraintEqualTo(view.Superview.LeadingAnchor),
view.TrailingAnchor.ConstraintEqualTo(view.Superview.TrailingAnchor),
contentStackView.TopAnchor.ConstraintEqualTo(view.TopAnchor),
contentStackView.BottomAnchor.ConstraintEqualTo(view.BottomAnchor),
contentStackView.LeadingAnchor.ConstraintEqualTo(view.LeadingAnchor),
contentStackView.TrailingAnchor.ConstraintEqualTo(view.TrailingAnchor)
});
var milesTimePanel = SetUpDistanceAndDurationLayout();
var ActionView = ElementLinksView.Create();
_ActionViewModel!.Alignment = Alignment.Center;
ActionView.SetUp(_ActionViewModel);
var scoreSummaryView = new ScoreSummaryView();
scoreSummaryView.SetUp(_displayData!.ScoreSummary);
contentStackView.AddArrangedSubview(milesTimePanel);
contentStackView.AddArrangedSubview(ActionView);
contentStackView.AddArrangedSubview(scoreSummaryView);
contentStackView.SetCustomSpacing(StyleGuideDimens.PaddingMedium, milesTimePanel);
milesTimePanel.LayoutIfNeeded();
view.LayoutIfNeeded();
_stackView.LayoutIfNeeded();
}
private void SetUpOverlayConstraints()
{
NSLayoutConstraint.ActivateConstraints(new[]
{
_overlayDragBarView!.CenterXAnchor.ConstraintEqualTo(CenterXAnchor),
_overlayDragBarView.TopAnchor.ConstraintEqualTo(TopAnchor, OverlayDragBarTopPadding),
_overlayDragBarView.WidthAnchor.ConstraintEqualTo(OverlayDragBarWidth),
_overlayDragBarView.HeightAnchor.ConstraintEqualTo(OverlayDragBarHeight),
_dragBarBottomPadding = _overlayHeaderAndContentView!.TopAnchor.ConstraintEqualTo(
_overlayDragBarView!.BottomAnchor, OverlayDragBarBottomPadding),
_overlayHeaderAndContentView.BottomAnchor.ConstraintEqualTo(BottomAnchor),
_overlayHeaderAndContentView.LeadingAnchor.ConstraintEqualTo(LeadingAnchor),
_overlayHeaderAndContentView.TrailingAnchor.ConstraintEqualTo(TrailingAnchor)
});
_overlayDragBarView.LayoutIfNeeded();
_overlayHeaderAndContentView.LayoutIfNeeded();
}
private void PanOverlayView(UIPanGestureRecognizer gesture)
{
if (_parentView == null || OverlayHeight == null)
{
return;
}
if (gesture.State == UIGestureRecognizerState.Began)
{
_startingPosition = Frame.Y;
}
var scrollOffset = _scrollView!.ContentOffset.Y;
var velocity = gesture.VelocityInView(_parentView);
var y = _parentView.Frame.GetMinY();
var newPos = _startingPosition + gesture.TranslationInView(_parentView).Y;
if (newPos >= _minPosition)
{
SetOverlayPosition(0, _minPosition);
}
else if (newPos <= _maxPosition)
{
SetOverlayPosition(0, _maxPosition, 0, true, 0);
}
else
{
//Scroll the scrollView content to the top
_scrollView!.SetContentOffset(CGPoint.Empty, true);
if (scrollOffset <= 0)
{
Frame = new CGRect(0, newPos, Frame.Width, Frame.Height);
}
}
if (gesture.State == UIGestureRecognizerState.Ended)
{
var duration = velocity.Y < 0 ? (y - _maxPosition) / -velocity.Y : _minPosition - (y / velocity.Y);
duration = duration > AnimationSpeedThreshold ? 1 : duration;
if (velocity.Y > 0)
{
if (scrollOffset <= 0)
{
//Scroll the scrollView content to the top
_scrollView!.SetContentOffset(CGPoint.Empty, true);
_scrollView!.UserInteractionEnabled = false;
SetOverlayPosition(duration, _minPosition);
}
}
else
{
_scrollView!.UserInteractionEnabled = true;
SetOverlayPosition(duration, _maxPosition, 0, true, 0);
}
}
}
private void SetOverlayPosition(double animationDuration, double position, float cornerRadius = CornerRadius,
bool dragBarVisibility = false, float dragBarBottomPadding = 18.0f)
{
Animate(animationDuration, () =>
{
Frame = new CGRect(0, position, Frame.Width, Frame.Height);
_overlayDragBarView!.Hidden = dragBarVisibility;
_dragBarBottomPadding!.Constant = dragBarBottomPadding;
});
AnimateCornerRadius(cornerRadius);
}
private void ScrollViewOnScrolled(object sender, EventArgs e)
{
var scrollOffset = _scrollView!.ContentOffset.Y;
if (scrollOffset >= -1 && _panGestureRecognizer != null)
{
_scrollView.UserInteractionEnabled = false;
}
}
#region Helper Methods
private void AnimateCornerRadius(float cornerRadius) =>
Animate(0.1, () => { Layer.CornerRadius = cornerRadius; });
private UIView SetUpDistanceAndDurationLayout()
{
var containerView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
ClipsToBounds = false
};
if (_displayData == null)
{
return containerView;
}
var imageDistance = new UIImageView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Image = UIImage.FromBundle(DistanceImageName),
ContentMode = UIViewContentMode.ScaleAspectFit
};
var imageDuration = new UIImageView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Image = UIImage.FromBundle(DurationImageName),
ContentMode = UIViewContentMode.ScaleAspectFit
};
var distanceDurationWrapperView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
ClipsToBounds = true
};
var stackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Horizontal,
Alignment = UIStackViewAlignment.Fill,
Spacing = 0
};
containerView.AddSubview(stackView);
stackView.TopAnchor.ConstraintEqualTo(containerView.TopAnchor).Active = true;
stackView.BottomAnchor.ConstraintEqualTo(containerView.BottomAnchor).Active = true;
stackView.LeadingAnchor.ConstraintEqualTo(containerView.LeadingAnchor).Active = true;
stackView.TrailingAnchor.ConstraintEqualTo(containerView.TrailingAnchor, 0).Active = true;
var distanceLabel = _displayData.TotalMiles.CreateLabel(BodyRegularCaption, ElementMargins.None(),
UITextAlignment.Left);
var durationLabel = _displayData.TotalJourneyTime.CreateLabel(BodyRegularCaption, ElementMargins.None(),
UITextAlignment.Left);
stackView.AddArrangedSubview(distanceDurationWrapperView);
distanceDurationWrapperView.AddSubview(imageDistance);
distanceDurationWrapperView.AddSubview(distanceLabel);
distanceDurationWrapperView.AddSubview(imageDuration);
distanceDurationWrapperView.AddSubview(durationLabel);
imageDistance.WidthAnchor.ConstraintEqualTo(IconImageWidth).Active = true;
imageDistance.HeightAnchor.ConstraintEqualTo(IconImageWidth).Active = true;
imageDuration.WidthAnchor.ConstraintEqualTo(IconImageWidth).Active = true;
imageDuration.HeightAnchor.ConstraintEqualTo(IconImageWidth).Active = true;
imageDistance.CenterYAnchor.ConstraintEqualTo(distanceDurationWrapperView.CenterYAnchor).Active = true;
imageDistance.LeadingAnchor.ConstraintEqualTo(distanceDurationWrapperView.LeadingAnchor).Active = true;
distanceLabel.TopAnchor.ConstraintEqualTo(distanceDurationWrapperView.TopAnchor).Active = true;
distanceLabel.BottomAnchor.ConstraintEqualTo(distanceDurationWrapperView.BottomAnchor, 0).Active = true;
distanceLabel.LeadingAnchor.ConstraintEqualTo(imageDistance.TrailingAnchor,
StyleGuideDimens.PaddingExtraSmall).Active = true;
imageDuration.CenterYAnchor.ConstraintEqualTo(distanceDurationWrapperView.CenterYAnchor).Active = true;
imageDuration.LeadingAnchor.ConstraintEqualTo(distanceLabel.TrailingAnchor, ImageDurationToLabelGap)
.Active = true;
durationLabel.TopAnchor.ConstraintEqualTo(distanceDurationWrapperView.TopAnchor).Active = true;
durationLabel.BottomAnchor.ConstraintEqualTo(distanceDurationWrapperView.BottomAnchor, 0).Active = true;
durationLabel.LeadingAnchor.ConstraintEqualTo(imageDuration.TrailingAnchor,
StyleGuideDimens.PaddingExtraSmall).Active = true;
durationLabel.TrailingAnchor.ConstraintEqualTo(distanceDurationWrapperView.TrailingAnchor).Active = true;
distanceDurationWrapperView.LayoutIfNeeded();
containerView.LayoutIfNeeded();
stackView.LayoutIfNeeded();
return containerView;
}
public static nfloat GetHeaderHeight(JourneyDetailOverlayDisplayData displayData, nfloat availableWidth)
{
//Left padding, left score panel padding, score panel width, right padding
var totalAvailableWidth = availableWidth - (StyleGuideDimens.PaddingSmall +
StyleGuideDimens.PaddingSmall + ScoreImageWidth + StyleGuideDimens.PaddingSmall);
var headerString = new NSString(displayData.JourneyDate);
var distance = new NSString(displayData.TotalMiles);
var duration = new NSString(displayData.TotalJourneyTime);
var sizeOfHeader = headerString.GetCalculatedSizeOfString(FontStyle.SemiBold.GetFont(Heading3SemiBold.Size),
totalAvailableWidth);
var (sizeOfStartAddress, sizeOfEndAddress) = GetAddressPanelHeights(displayData, availableWidth);
var sizeOfDistance = distance.GetCalculatedSizeOfString(FontStyle.Regular.GetFont(BodyRegularCaption.Size),
totalAvailableWidth);
var sizeOfDuration = duration.GetCalculatedSizeOfString(FontStyle.Regular.GetFont(BodyRegularCaption.Size),
totalAvailableWidth);
var distanceHeight = sizeOfDistance.Height > IconImageWidth
? sizeOfDistance.Height
: IconImageWidth;
var durationHeight = sizeOfDuration.Height > IconImageWidth
? sizeOfDuration.Height
: IconImageWidth;
var distanceDurationPanelHeight = distanceHeight > durationHeight ? distanceHeight : durationHeight;
return OverlayDragBarTopPadding
+ OverlayDragBarHeight
+ OverlayDragBarBottomPadding
+ sizeOfHeader.Height
+ StyleGuideDimens.PaddingSmall
+ sizeOfStartAddress.Height
+ StyleGuideDimens.PaddingExtraSmall
+ sizeOfEndAddress.Height
+ StyleGuideDimens.PaddingMedium
+ distanceDurationPanelHeight
+ StyleGuideDimens.PaddingSmall;
}
public static (CGSize startAddressHeight, CGSize endAddressHeight) GetAddressPanelHeights(
JourneyDetailOverlayDisplayData displayData, nfloat availableWidth)
{
//Left padding, Address icon width and padding, left score panel padding, score panel width, right padding
var totalAvailableWidth = availableWidth - (StyleGuideDimens.PaddingSmall + AddressIconWidthAndPadding +
StyleGuideDimens.PaddingSmall + ScoreImageWidth + StyleGuideDimens.PaddingSmall);
var startAddress = new NSString(displayData.JourneyStartAddress);
var endAddress = new NSString(displayData.JourneyEndAddress);
var sizeOfStartAddress = startAddress.GetCalculatedSizeOfString(
FontStyle.Regular.GetFont(BodyRegularCaption.Size),
totalAvailableWidth);
var sizeOfEndAddress = endAddress.GetCalculatedSizeOfString(
FontStyle.Regular.GetFont(BodyRegularCaption.Size),
totalAvailableWidth);
return (sizeOfStartAddress, sizeOfEndAddress);
}
#endregion
}
}

How setting up shadow of the CALayer breaks AutoLayout constraints?

I have cell's imageView shadow customization:
override func layoutSubviews() {
moviePosterImageView.layer.shadowOffset = .zero
moviePosterImageView.layer.shadowColor = UIColor.black.cgColor
moviePosterImageView.layer.shadowRadius = 5
moviePosterImageView.layer.shadowOpacity = 1
moviePosterImageView.layer.masksToBounds = false
moviePosterImageView.layer.shadowPath = UIBezierPath(rect: moviePosterImageView.bounds).cgPath
}
But it breaks imageView sizes and constraints. Without shadow it works pretty good.
But it breaks imageView sizes and constraints.
Yes, because you forgot to start by saying
override func layoutSubviews() {
super.layoutSubviews() // important
Since autolayout happens in layoutSubviews, if you don’t call super you prevent it from operating.
Use This Code in another swift File and set shadow in story board or xib easily
extension UIView {
#IBInspectable
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
#IBInspectable
var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
#IBInspectable
var borderColor: UIColor? {
get {
if let color = layer.borderColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.borderColor = color.cgColor
} else {
layer.borderColor = nil
}
}
}
#IBInspectable
var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue
}
}
#IBInspectable
var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
#IBInspectable
var shadowOffset: CGSize {
get {
return layer.shadowOffset
}
set {
layer.shadowOffset = newValue
}
}
#IBInspectable
var shadowColor: UIColor? {
get {
if let color = layer.shadowColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.shadowColor = color.cgColor
} else {
layer.shadowColor = nil
}
}
}
}

Animate loading spinner arc Xamarin.iOS

In my Xamarin.iOS application, I am trying to animate a loading spinner. I can draw the circle and the arc, but I do not know how to animate it. This is my class for the loading spinner:
public class LoadingSpinnerView : UIView
{
private CAShapeLayer _thinCirlce;
private CAShapeLayer _arc;
public LoadingSpinnerView()
{
_arc = new CAShapeLayer();
_arc.LineWidth = 3;
_arc.StrokeColor = UIColor.Blue.CGColor;
_arc.FillColor = UIColor.Clear.CGColor;
_thinCirlce = new CAShapeLayer();
_thinCirlce.LineWidth = 1;
_thinCirlce.StrokeColor = UIColor.Red.CGColor;
_thinCirlce.FillColor = UIColor.Clear.CGColor;
Layer.AddSublayer(_thinCirlce);
Layer.AddSublayer(_arc);
}
private nfloat _angle;
public nfloat Angle
{
get
{
return _angle;
}
set
{
_angle = value;
}
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
_thinCirlce.Path = UIBezierPath.FromOval(new CoreGraphics.CGRect(0, 0, Frame.Width, Frame.Height)).CGPath;
nfloat radius = Frame.Width / 2;
_arc.Path = UIBezierPath.FromArc(new CoreGraphics.CGPoint(radius, radius), radius, 0, Angle, true).CGPath;
}
}
I want to be able to animate it, something like this:
UIView.Animate(5, () => { _loadingSpinnerView.Angle = 3.14f; }); // This doesn't actually work...
Ready to use control. Just set control to UIView in your Storyboard.
[Register(nameof(CircleLoadingView))]
public class CircleLoadingView : UIView
{
public CircleLoadingView(IntPtr handle) : base(handle)
{
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
SetUpAnimation(this.Layer, new CGSize(Frame.Width, Frame.Height), UIColor.Red);
}
public void SetUpAnimation(CALayer layer, CGSize size, UIColor color)
{
var beginTime = 0.5;
var strokeStartDuration = 1.2;
var strokeEndDuration = 0.7;
var strokeEndAnimation = CABasicAnimation.FromKeyPath("strokeEnd");
strokeEndAnimation.Duration = strokeEndDuration;
strokeEndAnimation.TimingFunction = CAMediaTimingFunction.FromControlPoints(0.4f, 0.0f, 0.2f, 1.0f);
strokeEndAnimation.From = NSNumber.FromFloat(0);
strokeEndAnimation.To = NSNumber.FromFloat(1);
var strokeStartAnimation = CABasicAnimation.FromKeyPath("strokeStart");
strokeStartAnimation.Duration = strokeStartDuration;
strokeStartAnimation.TimingFunction = CAMediaTimingFunction.FromControlPoints(0.4f, 0.0f, 0.2f, 1.0f);
strokeStartAnimation.From = NSNumber.FromFloat(0);
strokeStartAnimation.To = NSNumber.FromFloat(1);
strokeStartAnimation.BeginTime = beginTime;
var groupAnimation = new CAAnimationGroup
{
Animations = new CAAnimation[] {/*rotationAnimation,*/ strokeEndAnimation, strokeStartAnimation },
Duration = strokeStartDuration + beginTime,
RepeatCount = float.PositiveInfinity,
RemovedOnCompletion = false,
FillMode = CAFillMode.Forwards
};
var circle = CreateCircle(size, color);
var frame = CGRect.FromLTRB(
(layer.Bounds.Width - size.Width) / 2,
(layer.Bounds.Height - size.Height) / 2,
size.Width,
size.Height
);
circle.Frame = frame;
circle.AddAnimation(groupAnimation, "animation");
layer.AddSublayer(circle);
}
private CAShapeLayer CreateCircle(CGSize size, UIColor color)
{
var layer = new CAShapeLayer();
var path = new UIBezierPath();
path.AddArc(new CGPoint(size.Width / 2, size.Height / 2),
size.Width / 2,
-(float)(Math.PI / 2),
(float)(Math.PI + Math.PI / 2),
true);
layer.FillColor = null;
layer.StrokeColor = color.CGColor;
layer.LineWidth = 2;
layer.BackgroundColor = null;
layer.Path = path.CGPath;
layer.Frame = CGRect.FromLTRB(0, 0, size.Width, size.Height);
return layer;
}
}
I figured it out. Here is my loading spinner class for other Xamarin developers to use. You can adjust it to your own requirements.
public class LoadingSpinnerView : UIView
{
private CAShapeLayer _thinCirlce;
private CAShapeLayer _arcLayer;
public LoadingSpinnerView(nfloat radius)
{
UIColor.Red.SetColor();
_thinCirlce = new CAShapeLayer();
_thinCirlce.LineWidth = 1;
_thinCirlce.Path = UIBezierPath.FromOval(new CoreGraphics.CGRect(0, 0, radius * 2, radius * 2)).CGPath;
_thinCirlce.StrokeColor = UIColor.Red.CGColor;
_thinCirlce.FillColor = UIColor.Clear.CGColor;
Layer.AddSublayer(_thinCirlce);
UIColor.Blue.SetColor();
UIBezierPath arcPath = new UIBezierPath();
arcPath.LineWidth = 4;
arcPath.LineCapStyle = CGLineCap.Round;
arcPath.LineJoinStyle = CGLineJoin.Round;
arcPath.AddArc(new CoreGraphics.CGPoint(radius, radius), radius, 0, 2 * 3.14f, true);
_arcLayer = new CAShapeLayer();
_arcLayer.Path = arcPath.CGPath;
_arcLayer.StrokeColor = UIColor.Blue.CGColor;
_arcLayer.FillColor = UIColor.Clear.CGColor;
_arcLayer.LineWidth = 4;
_arcLayer.StrokeStart = 0;
_arcLayer.StrokeEnd = 1;
if (_arcLayer.SuperLayer != null)
{
_arcLayer.RemoveAllAnimations();
_arcLayer.RemoveFromSuperLayer();
}
}
public void StartAnimation()
{
Layer.AddSublayer(_arcLayer);
CABasicAnimation animation = new CABasicAnimation();
animation.KeyPath = "strokeEnd";
animation.Duration = 3;
animation.From = NSNumber.FromFloat(0);
animation.To = NSNumber.FromFloat(1);
_arcLayer.AddAnimation(animation, null);
}
public void StopAnimation()
{
_arcLayer.RemoveAllAnimations();
_arcLayer.RemoveFromSuperLayer();
}
}

Xcode UIView has no members like border color and etc

Screenshot:
if I use it in code then receive this message:
like: Value of type 'UIView' has no member cornerRadius
What is wrong? Yesterday I didn't have this problem
You need to use Button's layer to set border property. e.g:
button.layer.cornerRadius = button.frame.height / 2.0
button.layer.masksToBounds = true
button.layer.borderColor = UIColor.lightGray.cgColor
button.layer.borderWidth = 1.0
OR
Add this extension to get these options in storyboard.
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
#IBInspectable var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
#IBInspectable var borderColor: UIColor? {
get {
let color = UIColor.init(cgColor: layer.borderColor!)
return color
}
set {
layer.borderColor = newValue?.cgColor
}
}
#IBInspectable var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 2)
layer.shadowOpacity = 0.4
layer.shadowRadius = shadowRadius
}
}
}
You cannot set CALayer properties directly to UIElement. Instead, need to use .layer property.
Example:
button.layer.cornerRadius = button.frame.height/ 2.0
button.layer.maskToBounds = true
It should be like this
#import <QuartzCore/QuartzCore.h>
[[myButton layer] setBorderWidth:2.0f];
[[myButton layer] setBorderColor:[UIColor greenColor].CGColor];
myButton.layer.cornerRadius = myButton.frame.height / 2.0;
For ref:
https://stackoverflow.com/a/32468101/5184217

Which options do I have for longer option strings?

As we can see on the bottom of the screen, there are options to be chosen.
Which other options are there for this menu such that I can display longer strings fully?
For your requirement you have to make a customize one to do it:
Try to use this code:
using System;
using System.Collections.Generic;
using CoreGraphics;
using UIKit;
namespace CusActionSheet
{
public delegate void OneParameterEventHandler<T>(T data);
public class ALActionSheet : UIView
{
private nfloat maximumProportion = 0.8f;
public event OneParameterEventHandler<string> RowClicked;
//You also can use this if you don't know the generic(but remeber also change it for source):
//public delegate void OneParameterEventHandler(string data);
//public event OneParameterEventHandler SelectedPhoneNumber;
private UIView backgroundContainer;
private UIView topContainer;
private UILabel topContainerTitleView;
private UITableView topContainerTable;
private ALActionSheetSource topContainerTableSource;
private UIButton btnCancel;
private nfloat itemPadding = 10;
private nfloat itemHeight = 60;
private nfloat cornerRadius = 10;
private List<string> dataList;
private CGRect backContainerShownFrame;
private CGRect backContainerHiddenFrame;
private string selectedNumber;
public CGRect BackContainerShownFrame
{
get
{
return backContainerShownFrame;
}
set
{
backContainerShownFrame = value;
}
}
public ALActionSheet(List<string> _dataList)
{
this.BackgroundColor = UIColor.Clear;
this.dataList = _dataList;
backgroundContainer = new UIView();
backgroundContainer.Layer.CornerRadius = cornerRadius;
backgroundContainer.Layer.MasksToBounds = true;
topContainer = new UIView();
topContainer.BackgroundColor = UIColor.White;
topContainer.Layer.CornerRadius = cornerRadius;
topContainer.Layer.MasksToBounds = true;
topContainerTitleView = new UILabel();
topContainerTitleView.Text = "Select a number";
topContainerTitleView.TextAlignment = UITextAlignment.Center;
topContainerTitleView.BackgroundColor = UIColor.FromRGB(230, 230, 230);
topContainer.AddSubview(topContainerTitleView);
topContainerTableSource = new ALActionSheetSource(dataList);
topContainerTableSource.RowClicked += (data) =>
{
selectedNumber = data;
Close();
};
topContainerTable = new UITableView();
topContainerTable.RowHeight = itemHeight;
topContainerTable.Layer.CornerRadius = cornerRadius;
topContainerTable.Layer.MasksToBounds = true;
topContainerTable.BackgroundColor = UIColor.White;
topContainerTable.Source = topContainerTableSource;
topContainer.AddSubview(topContainerTable);
btnCancel = new UIButton(UIButtonType.System);
btnCancel.SetTitle("Cancel", UIControlState.Normal);
btnCancel.TitleLabel.Font = UIFont.SystemFontOfSize(18);
btnCancel.BackgroundColor = UIColor.White;
btnCancel.Layer.CornerRadius = cornerRadius;
btnCancel.Layer.MasksToBounds = true;
btnCancel.TouchUpInside += delegate
{
Close();
};
backgroundContainer.AddSubview(topContainer);
backgroundContainer.AddSubview(btnCancel);
Layout();
}
public void Layout()
{
this.Frame = UIScreen.MainScreen.Bounds;
nfloat tableMaxHeight = UIScreen.MainScreen.Bounds.Height * maximumProportion - 2 * itemPadding - 2 * itemHeight;
nfloat tableHeight = dataList.Count * itemHeight < tableMaxHeight ? dataList.Count * itemHeight : tableMaxHeight;
nfloat itemWidth = UIScreen.MainScreen.Bounds.Width - 2 * itemPadding;
nfloat backgroundContainerHeight = tableHeight + 2 * itemHeight + itemPadding;
backgroundContainer.Frame = new CGRect(0, 0, itemWidth, backgroundContainerHeight);
nfloat topContainerHeight = itemHeight + tableHeight;
topContainer.Frame = new CGRect(0, 0, itemWidth, topContainerHeight);
topContainerTitleView.Frame = new CGRect(0, 0, itemWidth, itemHeight);
topContainerTable.Frame = new CGRect(0, itemHeight, itemWidth, tableHeight);
btnCancel.Frame = new CGRect(0, topContainerHeight + itemPadding, itemWidth, itemHeight);
BackContainerShownFrame = new CGRect(new CGPoint(itemPadding, UIScreen.MainScreen.Bounds.Height - backgroundContainerHeight - itemPadding), backgroundContainer.Bounds.Size);
backContainerHiddenFrame = new CGRect(new CGPoint(itemPadding, UIScreen.MainScreen.Bounds.Height + itemPadding), backgroundContainer.Bounds.Size);
}
public void Show()
{
UIApplication.SharedApplication.KeyWindow.AddSubview(this);
backgroundContainer.Frame = backContainerHiddenFrame;
this.AddSubview(backgroundContainer);
UIView.Animate(0.3, delegate
{
this.BackgroundColor = UIColor.FromRGBA(0, 0, 0, 0.5f);
backgroundContainer.Frame = BackContainerShownFrame;
});
}
public void Close()
{
UIView.Animate(0.3, delegate
{
backgroundContainer.Frame = backContainerHiddenFrame;
this.BackgroundColor = UIColor.Clear;
}, delegate
{
this.RemoveFromSuperview();
if (null != RowClicked && null != selectedNumber)
RowClicked(selectedNumber);
});
}
}
class ALActionSheetSource : UITableViewSource
{
public event OneParameterEventHandler<string> RowClicked;
private string cellID = "ALActionSheetCell";
private List<string> dataList;
public ALActionSheetSource(List<string> _dataList)
{
dataList = _dataList;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return dataList.Count;
}
public override UITableViewCell GetCell(UITableView tableView, Foundation.NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(cellID);
if (null == cell)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, cellID);
cell.TextLabel.TextAlignment = UITextAlignment.Center;
//cell.TextLabel.AdjustsFontSizeToFitWidth = true;//Auto resize by content
cell.TextLabel.Lines = int.MaxValue;//Multiple lines
}
cell.TextLabel.Text = dataList[indexPath.Row];
return cell;
}
public override void RowSelected(UITableView tableView, Foundation.NSIndexPath indexPath)
{
if (null != RowClicked)
RowClicked(dataList[indexPath.Row]);
}
}
}
And invoke it like this:
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
List<string> testList = new List<string>();
for (int i = 0; i < 10; i++) {
testList.Add("Item " + i.ToString());
}
testList.Add("I'm a very very very very very very very very long text.");
ALActionSheet actionSheet = new ALActionSheet(testList);
actionSheet.RowClicked += (data) => {
Console.WriteLine("data = "+data);
};
UIButton btnTest = new UIButton(UIButtonType.System);
btnTest.SetTitle("Test", UIControlState.Normal);
btnTest.Frame = new CoreGraphics.CGRect(50, 50, 80, 30);
btnTest.TouchUpInside += delegate {
actionSheet.Show();
};
this.Add(btnTest);
}
Hope it can help you.

Resources