Reputation: 2703
I am writing an Xamarin.iOS app, and found some Swift code for a loading spinner that I would like to use. So I am trying to convert the Swift code to C#. I hope this question is ok because I've been trying to convert this code for hours. This is the Swift code:
public class CircularProgressView: UIView {
public dynamic var progress: CGFloat = 0 {
didSet {
progressLayer.progress = progress
}
}
fileprivate var progressLayer: CircularProgressLayer {
return layer as! CircularProgressLayer
}
override public class var layerClass: AnyClass {
return CircularProgressLayer.self
}
override public func action(for layer: CALayer, forKey event: String) -> CAAction? {
if event == #keyPath(CircularProgressLayer.progress),
let action = action(for: layer, forKey: #keyPath(backgroundColor)) as? CAAnimation {
let animation = CABasicAnimation()
animation.keyPath = #keyPath(CircularProgressLayer.progress)
animation.fromValue = progressLayer.progress
animation.toValue = progress
animation.beginTime = action.beginTime
animation.duration = action.duration
animation.speed = action.speed
animation.timeOffset = action.timeOffset
animation.repeatCount = action.repeatCount
animation.repeatDuration = action.repeatDuration
animation.autoreverses = action.autoreverses
animation.fillMode = action.fillMode
animation.timingFunction = action.timingFunction
animation.delegate = action.delegate
self.layer.add(animation, forKey: #keyPath(CircularProgressLayer.progress))
}
return super.action(for: layer, forKey: event)
}
}
fileprivate class CircularProgressLayer: CALayer {
@NSManaged var progress: CGFloat
let startAngle: CGFloat = 1.5 * .pi
let twoPi: CGFloat = 2 * .pi
let halfPi: CGFloat = .pi / 2
override class func needsDisplay(forKey key: String) -> Bool {
if key == #keyPath(progress) {
return true
}
return super.needsDisplay(forKey: key)
}
override func draw(in ctx: CGContext) {
super.draw(in: ctx)
UIGraphicsPushContext(ctx)
//Light Grey
UIColor.lightGray.setStroke()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let strokeWidth: CGFloat = 4
let radius = (bounds.size.width / 2) - strokeWidth
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: twoPi, clockwise: true)
path.lineWidth = strokeWidth
path.stroke()
//Red
UIColor.red.setStroke()
let endAngle = (twoPi * progress) - halfPi
let pathProgress = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle , clockwise: true)
pathProgress.lineWidth = strokeWidth
pathProgress.lineCapStyle = .round
pathProgress.stroke()
UIGraphicsPopContext()
}
}
let circularProgress = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: {
circularProgress.progress = 0.76
}, completion: nil)
This is what I have so far:
public class CircularProgressView : UIView
{
// I doubt I made these properties correctly?
public float Progress
{
get
{
return ProgressLayer.Progress;
}
set
{
ProgressLayer.Progress = value;
}
}
public CircularProgressLayer ProgressLayer { get; set; }
public override Foundation.NSObject ActionForLayer(CALayer layer, string eventKey)
{
// I don't understand this part
return base.ActionForLayer(layer, eventKey);
}
}
public class CircularProgressLayer : CALayer
{
public float Progress { get; set; }
private nfloat startAngle = (nfloat)(1.5f * Math.PI);
private nfloat twoPi = (nfloat)(2 * Math.PI);
private double halfPi = Math.PI / 2;
public override bool NeedsDisplay
{
get
{
return base.NeedsDisplay;
}
}
public override void DrawInContext(CoreGraphics.CGContext ctx)
{
base.DrawInContext(ctx);
UIGraphics.PushContext(ctx);
UIColor.LightGray.SetStroke();
var center = new CGPoint(x: Bounds.GetMidX(), y: Bounds.GetMidY());
float strokeWidth = 4;
nfloat radius = (Bounds.Size.Width / 2) - strokeWidth;
var path = UIBezierPath.FromArc(center, radius: radius, startAngle: 0, endAngle: twoPi, clockwise: true);
path.LineWidth = strokeWidth;
path.Stroke();
UIColor.Red.SetStroke();
nfloat endAngle = (nfloat)((twoPi * Progress) - halfPi);
var pathProgress = UIBezierPath.FromArc(center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true);
pathProgress.LineWidth = strokeWidth;
pathProgress.LineCapStyle = CGLineCap.Round;
pathProgress.Stroke();
UIGraphics.PopContext();
}
}
There's a few parts that I do not understand, like this one:
if event == #keyPath(CircularProgressLayer.progress)
and this one:
fileprivate var progressLayer: CircularProgressLayer {
return layer as! CircularProgressLayer
}
I'm very good at C# but am new to Swift. I hope this question is ok because I've been trying to convert this code for hours.
Upvotes: 3
Views: 5019
Reputation: 74164
Update:
I needed to create @dynamic / NSManaged in a Xamarin.iOS prototype and ended up being able to do this by creating them at runtime, see my comment here:
And related gist:
So while Xamarin.iOS
does not support them out-of-the-box
, you can create them at runtime if needed.
Original:
Currently you can not generate dynamic properties (@dynamic
/ NSManaged
) using Xamarin.iOS
so the code you posted will not work even if converted.
Issue: https://bugzilla.xamarin.com/show_bug.cgi?id=38823
If manually constructing CAAction
/CAAnimation
objects is a valid option for your needs, you can review the Xamarin.iOS
CustomPropertyAnimation example app:
Upvotes: 2
Reputation: 2163
This is the compiler friendly way for expressing key paths in Swift. Keypath
is a series of properties in the consecutive objects (eg. company.manager.fullName
). If you want to learn more, read about the KVC. This expression produces a String
, in your case, it will be "progress". I think you can replace it with a simple comparison.
#keyPath(CircularProgressLayer.progress)
This is the read-only property which exposes the layer
property as an instance of the CircularProgressLayer
. The x as! y
means: cast x
to y
or crash if not possible.
fileprivate var progressLayer: CircularProgressLayer {
return layer as! CircularProgressLayer
}
Upvotes: 1