Repository navigation
Bring gestures, animations and rendering closer to UIKit on iOS - #443
Conversation
…leration callbacks - Recognizers get touches before the view, send their action on began, every changed, ended and cancelled, and keep their final state until the touch sequence ends - A recognizer that recognizes cancels the view's touches with touchesCancelled, also when it recognizes as the touch ends - require(toFail:), shouldReceive, gestureRecognizerShouldBegin and simultaneous recognition behave as on iOS - Pans begin after 10pt with a velocity averaged over the last tenth of a second; taps fail when they move or lift with the wrong number of fingers - Add UILongPressGestureRecognizer; UIPinchGestureRecognizer scales from where the fingers started - UIScrollView calls scrollViewDidScroll on every frame of a deceleration and scrollViewDidEndDecelerating when it finishes, its pan stops a deceleration on touch down, and its delegate methods are optional
CAShapeLayer with dashed line paths, UIBezierPath lines, UIView.window, rect conversion with nil as the window, willMove(toSuperview:), UIEvent.touches(for:), location(ofTouch:in:), UILabel.intrinsicContentSize, transform, rect and inset helpers, UIColor(white:alpha:), UIImage.withTintColor, CATransaction timing functions, UIScreen.displayCornerRadius on Android, and @preconcurrency on UIView, UIImage, UIFont and UIScreen
- UIView.center is the layer position - A nested UIView.animate restores the outer animation - contentsCenter draws contents as a nine-slice - Tinted images are rebuilt from PNG data, so they come back after Android loses the GL context - Opt-in allowsGroupOpacity composites a translucent subtree once, so overlapping sublayers don't show through each other
4c8c258 to
f8d7d80
Compare
|
|
||
| public var masksToBounds = false | ||
|
|
||
| /// Off by default, unlike iOS: compositing a fading subtree as one costs an offscreen pass per frame. |
There was a problem hiding this comment.
but why? we are trying to make our port behave the same as on iOS, so why change the default? if we need to change this for performance reasons we should do it in app code. but even better: we should fix the performance issue
| /// Where subclasses draw what they add to `contents`, in this layer's coordinates offset by `origin`. | ||
| func sdlRenderContent(at origin: CGPoint, opacity: Float, renderer: UIScreen) {} |
There was a problem hiding this comment.
?
to me, the comment doesn't actually explain what this is for. doesn't seem like this would exist in the original API even internally, so do we really need it? why do we want subclasses of this?
| /// returns a non animating copy of the layer | ||
| func createPresentation() -> CALayer { | ||
| let copy = CALayer(layer: self) | ||
| let copy = self.copy() as! CALayer |
| @@ -230,7 +246,7 @@ open class CALayer { | |||
|
|
|||
| /// returns a non animating copy of the layer | |||
| func createPresentation() -> CALayer { | |||
There was a problem hiding this comment.
should this be -> Self actually?
There was a problem hiding this comment.
It's -> Any on iOS (NSObject.copy()), so I'd prefer to keep it the same here.
| extension UIScreen { | ||
| /// Android only: the radius of the display's rounded corners, which iOS has no public API for. | ||
| public var displayCornerRadius: CGFloat { | ||
| let radius: JavaFloat = (try? jni.call("getDisplayCornerRadius", on: getSDLView())) ?? 0 | ||
| return CGFloat(radius) | ||
| } | ||
| } |
There was a problem hiding this comment.
move this to player, its android only API
| super.init(layer: layer) | ||
| } | ||
|
|
||
| open override func copy() -> Any { |
There was a problem hiding this comment.
i think the original API truly does return Any but I don't think anyone will be mad if we make this -> Self instead, which would be my slight preference
|
|
||
| extension CGRect { | ||
| /// The smallest rect containing all the points, or `.zero` without any. | ||
| init<Points: Sequence<CGPoint>>(boundingPoints points: Points) { |
There was a problem hiding this comment.
this is a kind of unusual way to write this. the modern way would be init(boundingPoints points: some Sequence<CGPoint>) I think – should be 100% equivalent but easier to read and reason about?
| @@ -0,0 +1,17 @@ | |||
| #if os(Android) | |||
| public final class NSNumber: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral { | |||
There was a problem hiding this comment.
hmm ok I don't love that we've half implemented this API and that we'd convert Ints to Double. At the extremes this would lose precision in ways that are hard to reason about. I think NSNumber's storage should probably be modelled as an internal enum
public final class NSNumber ... {
internal enum Storage {
case double(Double), int(Int), float(Float)
}
}
?
I wouldn't block the PR because of this but I think it might be preferable to not lose numerical precision on large/small ints here.
Also, I think NSNumber really is a class on Apple platforms, but that has potentially dramatic performance downsides. Especially since this is a final class with immutable storage, I would consider writing this as a struct instead – semantically it should be the same, without the potential ref counting and heap allocation footguns
| @@ -1,4 +1,4 @@ | |||
| @MainActor | |||
| @preconcurrency @MainActor | |||
There was a problem hiding this comment.
this needs some explanation I think?
| case cancelled | ||
| case failed | ||
|
|
||
| public static let recognized = UIGestureRecognizerState.ended |
|
|
||
| public weak var delegate: UIGestureRecognizerDelegate? | ||
| internal weak var trackedTouch: UITouch? | ||
| public internal(set) weak var view: UIView? |
There was a problem hiding this comment.
elsewhere (above) we stopped setting view on the gesture recognizer. not sure if that was intentional or correct?
| if isSequenceOver { | ||
| resetToPossible() | ||
| } else { | ||
| UIGestureRecognizer.awaitingReset.append(self) |
There was a problem hiding this comment.
this reads strangely (because it sounds like it's a single recognizer awaiting reset, whereas this is clearly an array or other collection).
…s up with how it's drawn once opaque
| #endif | ||
|
|
||
| @MainActor // UIImage is MainActor but CGImage is not | ||
| @preconcurrency @MainActor // UIImage is MainActor but CGImage is not |
There was a problem hiding this comment.
this also needs a comment I think?
| } | ||
|
|
||
| @MainActor | ||
| @preconcurrency @MainActor |
| } | ||
|
|
||
| extension UIScreen { | ||
| /// Android only: the radius of the display's rounded corners, which iOS has no public API for. |
There was a problem hiding this comment.
so let's write this #if os(Android)?
There was a problem hiding this comment.
i will remove this API entirely from here and add it to our app instead. It does not belong here.
| private var delayedViewPhases: [UITouchPhase] = [] | ||
|
|
||
| /// Hands the touch's current phase to its view, unless a recognizer cancelled it or delays it. | ||
| func deliverToView() { |
There was a problem hiding this comment.
this API sounds misleading. it should be something like maybeDeliverToView?
ephemer
left a comment
There was a problem hiding this comment.
Overall it looks good, would be great if you could address/fix the issues raised here wherever possible before merge. Thanks for working on this!
… explain @preconcurrency and sdlRenderContent, and leave UIFont isolated as before
|
Thanks for the great feedback @ephemer ! I will clean this up before merge |
…hickness set, pause deceleration with isDecelerating, and trim comments
…d, border and shadow only round the masked corners as on iOS
Type of change: Feature, bug fixes
Motivation (current vs expected behavior)
Brings gesture recognition, animations and layer rendering closer to UIKit on iOS, and adds APIs that were missing.
require(toFail:), long press,gestureRecognizerShouldBegin, scroll view deceleration callbacksCAShapeLayerwith dashed line paths,UIBezierPathlines,UIView.windowand rect conversion (nilis the window),willMove(toSuperview:),UIEvent.touches(for:),location(ofTouch:in:),UILabel.intrinsicContentSize, transform, rect and inset helpers,UIColor(white:alpha:),UIImage.withTintColor,CATransactiontiming functions,CALayer.maskedCorners,NSNumberon AndroidUIView.centeris the layer positionUIView.animaterestores the outer animationcontentsCenterdraws contents as a nine-sliceallowsGroupOpacity, on by default as on iOS, composites a translucent subtree once, so overlapping sublayers don't show through each otherPlease check if the PR fulfills these requirements