craft-native 0.0.184 → 0.0.186
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/templates/CraftNativeScreen.swift +156 -73
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3303,7 +3303,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
3303
3303
|
`);
|
|
3304
3304
|
}
|
|
3305
3305
|
// package.json
|
|
3306
|
-
var version = "0.0.
|
|
3306
|
+
var version = "0.0.186";
|
|
3307
3307
|
|
|
3308
3308
|
// bin/cli.ts
|
|
3309
3309
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -577,6 +577,9 @@ private final class CraftNativeVideoView: UIView, AVPlayerViewControllerDelegate
|
|
|
577
577
|
// A muted video makes no sound, so it leaves the session as it is.
|
|
578
578
|
if muted { releaseSession() } else { claimSession() }
|
|
579
579
|
playerController.showsPlaybackControls = controls
|
|
580
|
+
// The system's controls are for VoiceOver too; a bare video stays one element.
|
|
581
|
+
playerController.view.accessibilityElementsHidden = !controls
|
|
582
|
+
isUserInteractionEnabled = true
|
|
580
583
|
playerController.videoGravity = resizeMode == "cover" ? .resizeAspectFill
|
|
581
584
|
: resizeMode == "stretch" ? .resize
|
|
582
585
|
: .resizeAspect
|
|
@@ -5687,6 +5690,13 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5687
5690
|
return handler
|
|
5688
5691
|
}
|
|
5689
5692
|
|
|
5693
|
+
/// Views that take their own touches whatever handlers the screen gives
|
|
5694
|
+
/// them: a scroll view, a modal, and a video's system controls (with
|
|
5695
|
+
/// interaction off, its play, scrubber and full screen never answered).
|
|
5696
|
+
private func ownsTouches(_ view: UIView) -> Bool {
|
|
5697
|
+
view is UIScrollView || view is CraftNativeModalView || view is CraftNativeVideoView
|
|
5698
|
+
}
|
|
5699
|
+
|
|
5690
5700
|
private func updatePressHandler(_ handler: String?, for view: UIView) {
|
|
5691
5701
|
let id = ObjectIdentifier(view)
|
|
5692
5702
|
let handler = handler?.isEmpty == false ? handler : nil
|
|
@@ -5701,7 +5711,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5701
5711
|
}
|
|
5702
5712
|
// A container keeps receiving touches without a handler of its own,
|
|
5703
5713
|
// or every control inside it (and the screen root) would be dead.
|
|
5704
|
-
if !(view
|
|
5714
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = actionable || longPressHandlers[id] != nil || view is CraftNativeFlowView }
|
|
5705
5715
|
}
|
|
5706
5716
|
|
|
5707
5717
|
private func updatePressFeedback(_ pressIn: String?, _ pressOut: String?, for view: UIView) {
|
|
@@ -5710,7 +5720,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5710
5720
|
if let pressOut, !pressOut.isEmpty { pressOutHandlers[id] = pressOut } else { pressOutHandlers.removeValue(forKey: id) }
|
|
5711
5721
|
guard pressInHandlers[id] != nil || pressOutHandlers[id] != nil else {
|
|
5712
5722
|
if let recognizer = pressFeedbackRecognizers.removeValue(forKey: id) { view.removeGestureRecognizer(recognizer) }
|
|
5713
|
-
if !(view
|
|
5723
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = handlers[id] != nil || linkTargets[id] != nil || longPressHandlers[id] != nil || view is CraftNativeFlowView }
|
|
5714
5724
|
return
|
|
5715
5725
|
}
|
|
5716
5726
|
if pressFeedbackRecognizers[id] == nil {
|
|
@@ -5722,14 +5732,14 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5722
5732
|
pressFeedbackRecognizers[id] = recognizer
|
|
5723
5733
|
view.addGestureRecognizer(recognizer)
|
|
5724
5734
|
}
|
|
5725
|
-
if !(view
|
|
5735
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = true }
|
|
5726
5736
|
}
|
|
5727
5737
|
|
|
5728
5738
|
private func updateLongPressHandler(_ handler: String?, for view: UIView) {
|
|
5729
5739
|
let id = ObjectIdentifier(view)
|
|
5730
5740
|
if let handler, !handler.isEmpty {
|
|
5731
5741
|
longPressHandlers[id] = handler
|
|
5732
|
-
if !(view
|
|
5742
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = true }
|
|
5733
5743
|
if longPressRecognizers[id] == nil {
|
|
5734
5744
|
let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(viewLongPressed(_:)))
|
|
5735
5745
|
longPressRecognizers[id] = recognizer
|
|
@@ -5740,7 +5750,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5740
5750
|
if let recognizer = longPressRecognizers.removeValue(forKey: id) {
|
|
5741
5751
|
view.removeGestureRecognizer(recognizer)
|
|
5742
5752
|
}
|
|
5743
|
-
if !(view
|
|
5753
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = handlers[id] != nil || linkTargets[id] != nil || view is CraftNativeFlowView }
|
|
5744
5754
|
}
|
|
5745
5755
|
}
|
|
5746
5756
|
|
|
@@ -6336,10 +6346,14 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
6336
6346
|
// reads: its label names the group, as a tab strip or a pager's
|
|
6337
6347
|
// name, and never stands in for its content. As an element of its
|
|
6338
6348
|
// own, a labelled pager hid every page from VoiceOver.
|
|
6349
|
+
// A video with its system controls is a group too: VoiceOver reaches
|
|
6350
|
+
// play, the scrubber, skipping and full screen inside it.
|
|
6351
|
+
let controlledVideo = type == "Video" && (props["controls"] as? Bool) == true
|
|
6339
6352
|
let container = ["ScrollView", "FlatList", "SectionList"].contains(type)
|
|
6340
6353
|
|| ["tablist", "radiogroup", "list", "menu", "menubar", "toolbar"].contains(role ?? "")
|
|
6354
|
+
|| controlledVideo
|
|
6341
6355
|
view.isAccessibilityElement = !container && role != "none" && (view.accessibilityLabel != nil || role != nil || ["Text", "Button", "Link", "Checkbox", "Image", "ImageBackground", "Video", "TextInput", "Picker", "DatePicker", "Switch", "Slider"].contains(type))
|
|
6342
|
-
view.accessibilityContainerType = container && view.accessibilityLabel != nil ? .semanticGroup : .none
|
|
6356
|
+
view.accessibilityContainerType = container && (view.accessibilityLabel != nil || controlledVideo) ? .semanticGroup : .none
|
|
6343
6357
|
var traits: UIAccessibilityTraits = []
|
|
6344
6358
|
switch role ?? type.lowercased() {
|
|
6345
6359
|
case "tablist": traits.insert(.tabBar)
|
|
@@ -6925,10 +6939,15 @@ final class CraftNativeAnimationRecord {
|
|
|
6925
6939
|
let fontSize: CGFloat?
|
|
6926
6940
|
let textColor: UIColor?
|
|
6927
6941
|
let transitionID: String?
|
|
6942
|
+
/// Where it sat in its parent, to be put back after it faded elsewhere.
|
|
6943
|
+
let index: Int?
|
|
6944
|
+
let arrangedIndex: Int?
|
|
6928
6945
|
|
|
6929
6946
|
init(view: UIView, screen: UIView, transitionID: String?) {
|
|
6930
6947
|
self.view = view
|
|
6931
6948
|
superview = view.superview
|
|
6949
|
+
index = view.superview?.subviews.firstIndex(of: view)
|
|
6950
|
+
arrangedIndex = (view.superview as? UIStackView)?.arrangedSubviews.firstIndex(of: view)
|
|
6932
6951
|
frame = view.frame
|
|
6933
6952
|
frameOnScreen = view.superview?.convert(view.frame, to: screen) ?? view.frame
|
|
6934
6953
|
alpha = view.alpha
|
|
@@ -6947,6 +6966,10 @@ final class CraftNativeAnimationTransaction {
|
|
|
6947
6966
|
var screenBackground: UIColor?
|
|
6948
6967
|
/// Reduce Motion: the screen as it was, crossfaded into the new one.
|
|
6949
6968
|
var picture: UIView?
|
|
6969
|
+
/// Commits the change as the run loop finishes the pass it came in.
|
|
6970
|
+
var observer: CFRunLoopObserver?
|
|
6971
|
+
/// The screen as it was, drawn over it until the animation is set up.
|
|
6972
|
+
var cover: UIView?
|
|
6950
6973
|
|
|
6951
6974
|
init(spec: CraftNativeAnimationSpec, reduceMotion: Bool) {
|
|
6952
6975
|
self.spec = spec
|
|
@@ -6956,7 +6979,11 @@ final class CraftNativeAnimationTransaction {
|
|
|
6956
6979
|
|
|
6957
6980
|
/// An animation under way: finished at once if another change comes first.
|
|
6958
6981
|
final class CraftNativeRunningAnimation {
|
|
6982
|
+
/// The Core Animation key every animation of a change goes under.
|
|
6983
|
+
static let key = "craft.change"
|
|
6959
6984
|
var animators: [UIViewPropertyAnimator] = []
|
|
6985
|
+
/// Live layers carrying an animation of the change, under `key` + a suffix.
|
|
6986
|
+
var layers: [(CALayer, String)] = []
|
|
6960
6987
|
var cleanup: [() -> Void] = []
|
|
6961
6988
|
private var done = false
|
|
6962
6989
|
|
|
@@ -6967,6 +6994,10 @@ final class CraftNativeRunningAnimation {
|
|
|
6967
6994
|
animator.stopAnimation(false)
|
|
6968
6995
|
animator.finishAnimation(at: .end)
|
|
6969
6996
|
}
|
|
6997
|
+
// A live view's own values were never touched: taking the animation
|
|
6998
|
+
// off leaves it exactly as the screen last drew it.
|
|
6999
|
+
for (layer, key) in layers { layer.removeAnimation(forKey: key) }
|
|
7000
|
+
layers.removeAll()
|
|
6970
7001
|
cleanup.forEach { $0() }
|
|
6971
7002
|
cleanup.removeAll()
|
|
6972
7003
|
}
|
|
@@ -6991,16 +7022,40 @@ extension CraftNativeScreenController {
|
|
|
6991
7022
|
let transaction = CraftNativeAnimationTransaction(spec: CraftNativeAnimationSpec(payload), reduceMotion: UIAccessibility.isReduceMotionEnabled)
|
|
6992
7023
|
transaction.screenBackground = view.backgroundColor
|
|
6993
7024
|
if transaction.reduceMotion {
|
|
6994
|
-
|
|
7025
|
+
// Held over the screen until the change is in, then faded away.
|
|
7026
|
+
if view.window != nil, let picture = view.snapshotView(afterScreenUpdates: false) {
|
|
7027
|
+
picture.frame = view.bounds
|
|
7028
|
+
picture.isUserInteractionEnabled = false
|
|
7029
|
+
view.addSubview(picture)
|
|
7030
|
+
transaction.picture = picture
|
|
7031
|
+
}
|
|
6995
7032
|
} else {
|
|
6996
7033
|
view.layoutIfNeeded()
|
|
6997
7034
|
recordAnimationState(renderedRoot, into: transaction)
|
|
7035
|
+
// The screen as it is, held over it until the change's animation
|
|
7036
|
+
// is set up: UIKit can draw the new screen before the run loop's
|
|
7037
|
+
// pass ends, which showed it for a frame before it animated.
|
|
7038
|
+
if view.window != nil, let cover = view.snapshotView(afterScreenUpdates: false) {
|
|
7039
|
+
cover.frame = view.bounds
|
|
7040
|
+
cover.isUserInteractionEnabled = false
|
|
7041
|
+
view.addSubview(cover)
|
|
7042
|
+
transaction.cover = cover
|
|
7043
|
+
}
|
|
6998
7044
|
}
|
|
6999
7045
|
animationTransaction = transaction
|
|
7000
7046
|
CraftNativeAnimationClock.duration = transaction.spec.duration
|
|
7001
7047
|
// The change comes from the same turn of the script (the state it
|
|
7002
|
-
// changed renders in a microtask)
|
|
7003
|
-
|
|
7048
|
+
// changed renders in a microtask), so it is set up as the run loop
|
|
7049
|
+
// finishes this pass, before Core Animation draws it: drawn first, the
|
|
7050
|
+
// new screen showed for a frame and then jumped back to animate. A
|
|
7051
|
+
// change that comes later still animates.
|
|
7052
|
+
let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.beforeWaiting.rawValue, false, 0) { [weak self] _, _ in
|
|
7053
|
+
self?.commitAnimatedChange()
|
|
7054
|
+
}
|
|
7055
|
+
if let observer {
|
|
7056
|
+
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, .commonModes)
|
|
7057
|
+
transaction.observer = observer
|
|
7058
|
+
}
|
|
7004
7059
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in self?.commitAnimatedChange() }
|
|
7005
7060
|
}
|
|
7006
7061
|
|
|
@@ -7020,6 +7075,13 @@ extension CraftNativeScreenController {
|
|
|
7020
7075
|
private func commitAnimatedChange() {
|
|
7021
7076
|
guard let transaction = animationTransaction else { return }
|
|
7022
7077
|
animationTransaction = nil
|
|
7078
|
+
if let observer = transaction.observer {
|
|
7079
|
+
CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer, .commonModes)
|
|
7080
|
+
transaction.observer = nil
|
|
7081
|
+
}
|
|
7082
|
+
// Taken off in the same transaction the animations start in.
|
|
7083
|
+
transaction.cover?.removeFromSuperview()
|
|
7084
|
+
transaction.cover = nil
|
|
7023
7085
|
let spec = transaction.spec
|
|
7024
7086
|
let running = CraftNativeRunningAnimation()
|
|
7025
7087
|
runningAnimation = running
|
|
@@ -7027,9 +7089,6 @@ extension CraftNativeScreenController {
|
|
|
7027
7089
|
|
|
7028
7090
|
if transaction.reduceMotion {
|
|
7029
7091
|
guard let picture = transaction.picture else { running.finish(); return }
|
|
7030
|
-
picture.frame = view.bounds
|
|
7031
|
-
picture.isUserInteractionEnabled = false
|
|
7032
|
-
view.addSubview(picture)
|
|
7033
7092
|
let fade = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) { picture.alpha = 0 }
|
|
7034
7093
|
fade.addCompletion { _ in running.finish() }
|
|
7035
7094
|
running.animators.append(fade)
|
|
@@ -7073,26 +7132,69 @@ extension CraftNativeScreenController {
|
|
|
7073
7132
|
view.addSubview(overlay)
|
|
7074
7133
|
running.cleanup.append { overlay.removeFromSuperview() }
|
|
7075
7134
|
|
|
7076
|
-
|
|
7077
|
-
//
|
|
7135
|
+
// Views that are going are the change's own now: they move into the
|
|
7136
|
+
// overlay where they were and fade there. Put back as they were
|
|
7137
|
+
// afterwards, in case the screen shows them again.
|
|
7078
7138
|
let fadeOut = UIViewPropertyAnimator(duration: spec.duration * 0.5, curve: .easeOut)
|
|
7079
|
-
let
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
for record in removedRoots {
|
|
7139
|
+
let quickFade = UIViewPropertyAnimator(duration: spec.duration * 0.35, curve: .easeOut)
|
|
7140
|
+
let travel = UIViewPropertyAnimator(duration: spec.duration, timingParameters: spec.timing)
|
|
7141
|
+
running.animators = [fadeOut, quickFade, travel]
|
|
7142
|
+
func adopt(_ record: CraftNativeAnimationRecord, fading fade: UIViewPropertyAnimator) {
|
|
7084
7143
|
let going = record.view
|
|
7144
|
+
let resizes = going.translatesAutoresizingMaskIntoConstraints
|
|
7145
|
+
let base = going.transform
|
|
7085
7146
|
going.translatesAutoresizingMaskIntoConstraints = true
|
|
7086
7147
|
going.transform = .identity
|
|
7087
7148
|
going.frame = record.frameOnScreen
|
|
7149
|
+
going.alpha = record.alpha
|
|
7088
7150
|
overlay.addSubview(going)
|
|
7089
|
-
|
|
7151
|
+
fade.addAnimations { going.alpha = 0 }
|
|
7152
|
+
running.cleanup.append {
|
|
7153
|
+
going.removeFromSuperview()
|
|
7154
|
+
going.transform = base
|
|
7155
|
+
going.alpha = record.alpha
|
|
7156
|
+
going.translatesAutoresizingMaskIntoConstraints = resizes
|
|
7157
|
+
going.frame = record.frame
|
|
7158
|
+
// Back in a parent that went with it, as it was: a screen
|
|
7159
|
+
// that shows that subtree again finds it whole.
|
|
7160
|
+
if let parent = record.superview, parent.window == nil {
|
|
7161
|
+
if let stack = parent as? UIStackView, let at = record.arrangedIndex {
|
|
7162
|
+
stack.insertArrangedSubview(going, at: min(at, stack.arrangedSubviews.count))
|
|
7163
|
+
} else if let at = record.index {
|
|
7164
|
+
parent.insertSubview(going, at: min(at, parent.subviews.count))
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
7168
|
+
}
|
|
7169
|
+
for record in removedRoots { adopt(record, fading: fadeOut) }
|
|
7170
|
+
|
|
7171
|
+
// Live views are animated on their layers only, from how they were to
|
|
7172
|
+
// how the screen draws them now: nothing the screen sets on them
|
|
7173
|
+
// meanwhile (the next second on a timer, a style patch) is undone.
|
|
7174
|
+
let fades = spec.duration * 0.6
|
|
7175
|
+
func animate(_ layer: CALayer, _ keyPath: String, from: Any, duration: TimeInterval? = nil, spring: Bool = true) {
|
|
7176
|
+
let animation: CABasicAnimation
|
|
7177
|
+
if spring, duration == nil, spec.curve == "spring" {
|
|
7178
|
+
let bouncy = CASpringAnimation(keyPath: keyPath)
|
|
7179
|
+
bouncy.mass = 1
|
|
7180
|
+
bouncy.stiffness = pow(2 * .pi / spec.duration, 2)
|
|
7181
|
+
bouncy.damping = 2 * sqrt(bouncy.stiffness) * (1 - spec.bounce)
|
|
7182
|
+
bouncy.duration = bouncy.settlingDuration
|
|
7183
|
+
animation = bouncy
|
|
7184
|
+
} else {
|
|
7185
|
+
animation = CABasicAnimation(keyPath: keyPath)
|
|
7186
|
+
animation.duration = duration ?? spec.duration
|
|
7187
|
+
animation.timingFunction = CAMediaTimingFunction(name: spec.curve == "linear" ? .linear : .easeInEaseOut)
|
|
7188
|
+
}
|
|
7189
|
+
animation.fromValue = from
|
|
7190
|
+
let key = "\(CraftNativeRunningAnimation.key).\(keyPath)"
|
|
7191
|
+
layer.add(animation, forKey: key)
|
|
7192
|
+
running.layers.append((layer, key))
|
|
7090
7193
|
}
|
|
7091
7194
|
|
|
7092
|
-
// The background, the screen's own included,
|
|
7195
|
+
// The background, the screen's own included, comes from the old colour.
|
|
7093
7196
|
if let before = transaction.screenBackground, let now = view.backgroundColor, !before.isEqual(now) {
|
|
7094
|
-
view.backgroundColor
|
|
7095
|
-
main.addAnimations { self.view.backgroundColor = now }
|
|
7197
|
+
animate(view.layer, "backgroundColor", from: before.cgColor, spring: false)
|
|
7096
7198
|
}
|
|
7097
7199
|
|
|
7098
7200
|
// Shared elements: the new view starts where the old one was, at its
|
|
@@ -7100,58 +7202,56 @@ extension CraftNativeScreenController {
|
|
|
7100
7202
|
for (old, new) in pairs {
|
|
7101
7203
|
let target = new.superview?.convert(new.frame, to: view) ?? new.frame
|
|
7102
7204
|
let move = morph(from: old.frameOnScreen, to: target, uniform: true)
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
new.
|
|
7106
|
-
|
|
7107
|
-
main.addAnimations { new.transform = base }
|
|
7108
|
-
fadeIn.addAnimations { new.alpha = finalAlpha }
|
|
7205
|
+
animate(new.layer, "transform", from: CATransform3DMakeAffineTransform(new.transform.concatenating(move)))
|
|
7206
|
+
// Crossed quickly, so the two are never both half there for long.
|
|
7207
|
+
animate(new.layer, "opacity", from: 0, duration: spec.duration * 0.35)
|
|
7208
|
+
adopt(old, fading: quickFade)
|
|
7109
7209
|
let going = old.view
|
|
7110
|
-
going.
|
|
7111
|
-
going.transform = .identity
|
|
7112
|
-
going.frame = old.frameOnScreen
|
|
7113
|
-
going.alpha = old.alpha
|
|
7114
|
-
overlay.addSubview(going)
|
|
7115
|
-
main.addAnimations { going.transform = move.inverted() }
|
|
7116
|
-
fadeOut.addAnimations { going.alpha = 0 }
|
|
7117
|
-
running.cleanup.append { new.transform = base; new.alpha = finalAlpha }
|
|
7210
|
+
travel.addAnimations { going.transform = move.inverted() }
|
|
7118
7211
|
}
|
|
7119
7212
|
|
|
7120
7213
|
// New views fade in. A new container holding a shared element keeps it
|
|
7121
7214
|
// in sight: only the rest of it fades, and its colour comes up.
|
|
7215
|
+
func fadeIn(_ added: UIView) {
|
|
7216
|
+
if pairedNew.contains(ObjectIdentifier(added)) { return }
|
|
7217
|
+
guard containsShared(added, pairedNew: pairedNew) else {
|
|
7218
|
+
animate(added.layer, "opacity", from: 0, duration: fades)
|
|
7219
|
+
return
|
|
7220
|
+
}
|
|
7221
|
+
if let colour = added.backgroundColor, colour != .clear {
|
|
7222
|
+
animate(added.layer, "backgroundColor", from: colour.withAlphaComponent(0).cgColor, duration: fades)
|
|
7223
|
+
}
|
|
7224
|
+
added.subviews.forEach(fadeIn)
|
|
7225
|
+
}
|
|
7122
7226
|
for added in inserted where !(added.superview.map { insertedIds.contains(ObjectIdentifier($0)) } ?? false) {
|
|
7123
|
-
|
|
7227
|
+
fadeIn(added)
|
|
7124
7228
|
}
|
|
7125
7229
|
|
|
7126
7230
|
// Views that stay go to their new places and sizes.
|
|
7127
7231
|
for record in records.values where present.contains(ObjectIdentifier(record.view)) {
|
|
7128
7232
|
let stays = record.view
|
|
7129
7233
|
if let label = stays as? UILabel, let before = record.fontSize, abs(before - label.font.pointSize) > 0.5 {
|
|
7130
|
-
let base = label.transform
|
|
7131
7234
|
let target = label.superview?.convert(label.frame, to: view) ?? label.frame
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7235
|
+
let move = morph(from: record.frameOnScreen, to: target, uniform: true)
|
|
7236
|
+
animate(label.layer, "transform", from: CATransform3DMakeAffineTransform(label.transform.concatenating(move)))
|
|
7237
|
+
} else if record.superview === stays.superview, record.frame != stays.frame, stays.transform.isIdentity {
|
|
7238
|
+
animate(stays.layer, "position", from: CGPoint(x: record.frame.midX, y: record.frame.midY))
|
|
7239
|
+
if record.frame.size != stays.frame.size {
|
|
7240
|
+
animate(stays.layer, "bounds.size", from: record.frame.size)
|
|
7241
|
+
}
|
|
7139
7242
|
}
|
|
7140
7243
|
if let before = record.backgroundColor, let now = stays.backgroundColor, !before.isEqual(now) {
|
|
7141
|
-
stays.backgroundColor
|
|
7142
|
-
main.addAnimations { stays.backgroundColor = now }
|
|
7143
|
-
}
|
|
7144
|
-
if let label = stays as? UILabel, let before = record.textColor, !before.isEqual(label.textColor) {
|
|
7145
|
-
let now = label.textColor
|
|
7146
|
-
label.textColor = before
|
|
7147
|
-
UIView.transition(with: label, duration: spec.duration * 0.6, options: [.transitionCrossDissolve, .allowAnimatedContent]) { label.textColor = now }
|
|
7244
|
+
animate(stays.layer, "backgroundColor", from: before.cgColor, spring: false)
|
|
7148
7245
|
}
|
|
7149
7246
|
}
|
|
7150
7247
|
|
|
7151
|
-
|
|
7248
|
+
travel.addCompletion { _ in running.finish() }
|
|
7152
7249
|
fadeOut.startAnimation()
|
|
7153
|
-
|
|
7154
|
-
|
|
7250
|
+
quickFade.startAnimation()
|
|
7251
|
+
travel.startAnimation()
|
|
7252
|
+
// The springs settle a little after `duration`; the views are already
|
|
7253
|
+
// where the screen draws them, so finishing then changes nothing.
|
|
7254
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + spec.duration * 2) { running.finish() }
|
|
7155
7255
|
}
|
|
7156
7256
|
|
|
7157
7257
|
/// The transform that puts a view at `final` where `start` was: moved
|
|
@@ -7166,23 +7266,6 @@ extension CraftNativeScreenController {
|
|
|
7166
7266
|
return CGAffineTransform(translationX: dx, y: dy).scaledBy(x: sx, y: sy)
|
|
7167
7267
|
}
|
|
7168
7268
|
|
|
7169
|
-
private func fadeInInserted(_ view: UIView, pairedNew: Set<ObjectIdentifier>, animator: UIViewPropertyAnimator, running: CraftNativeRunningAnimation) {
|
|
7170
|
-
if pairedNew.contains(ObjectIdentifier(view)) { return }
|
|
7171
|
-
guard containsShared(view, pairedNew: pairedNew) else {
|
|
7172
|
-
let finalAlpha = view.alpha
|
|
7173
|
-
view.alpha = 0
|
|
7174
|
-
animator.addAnimations { view.alpha = finalAlpha }
|
|
7175
|
-
running.cleanup.append { view.alpha = finalAlpha }
|
|
7176
|
-
return
|
|
7177
|
-
}
|
|
7178
|
-
if let colour = view.backgroundColor, colour != .clear {
|
|
7179
|
-
view.backgroundColor = colour.withAlphaComponent(0)
|
|
7180
|
-
animator.addAnimations { view.backgroundColor = colour }
|
|
7181
|
-
running.cleanup.append { view.backgroundColor = colour }
|
|
7182
|
-
}
|
|
7183
|
-
for child in view.subviews { fadeInInserted(child, pairedNew: pairedNew, animator: animator, running: running) }
|
|
7184
|
-
}
|
|
7185
|
-
|
|
7186
7269
|
private func containsShared(_ view: UIView, pairedNew: Set<ObjectIdentifier>) -> Bool {
|
|
7187
7270
|
if pairedNew.contains(ObjectIdentifier(view)) { return true }
|
|
7188
7271
|
return view.subviews.contains { containsShared($0, pairedNew: pairedNew) }
|