diff --git a/ios/RNMBX/ShapeAnimators/RNMBXChangeLineOffsetsShapeAnimatorModule.mm b/ios/RNMBX/ShapeAnimators/RNMBXChangeLineOffsetsShapeAnimatorModule.mm index b103459c7d..569fe13b3f 100644 --- a/ios/RNMBX/ShapeAnimators/RNMBXChangeLineOffsetsShapeAnimatorModule.mm +++ b/ios/RNMBX/ShapeAnimators/RNMBXChangeLineOffsetsShapeAnimatorModule.mm @@ -25,26 +25,71 @@ - (dispatch_queue_t)methodQueue { return RCTGetUIManagerQueue(); } -RCT_EXPORT_METHOD(create:(nonnull NSNumber*)tag lineString: (nonnull NSArray*)coordinates startOffset: (nonnull NSNumber*)startOffset endOffset: (nonnull NSNumber*)endOffset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - resolve([[ChangeLineOffsetsShapeAnimator createWithTag:tag coordinates:coordinates startOffset:startOffset endOffset:endOffset] getTag]); +// Must match NativeRNMBXChangeLineOffsetsShapeAnimatorModuleSpec exactly (codegen uses NSInteger/double). +- (void)generate:(NSInteger)tag + coordinates:(NSArray *)coordinates + startOffset:(double)startOffset + endOffset:(double)endOffset + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject +{ + ChangeLineOffsetsShapeAnimator *animator = + [ChangeLineOffsetsShapeAnimator createWithTag:@(tag) + coordinates:coordinates + startOffset:@(startOffset) + endOffset:@(endOffset)]; + if (animator == nil) { + reject(@"RNMBXChangeLineOffsetsShapeAnimatorModule", @"Failed to generate animator", nil); + return; + } + resolve([animator getTag]); } -RCT_EXPORT_METHOD(setLineString:(nonnull NSNumber*)tag coordinates: (nonnull NSArray*)coordinates startOffset: (nonnull NSNumber*)startOffset endOffset: (nonnull NSNumber*)endOffset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - [ChangeLineOffsetsShapeAnimator setLineStringWithTag:tag coordinates:coordinates startOffset:startOffset endOffset:endOffset resolve:resolve reject:reject]; +- (void)setLineString:(NSInteger)tag + coordinates:(NSArray *)coordinates + startOffset:(double)startOffset + endOffset:(double)endOffset + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject +{ + [ChangeLineOffsetsShapeAnimator setLineStringWithTag:@(tag) + coordinates:coordinates + startOffset:@(startOffset) + endOffset:@(endOffset) + resolve:resolve + reject:reject]; } -RCT_EXPORT_METHOD(setStartOffset:(nonnull NSNumber*)tag offset: (nonnull NSNumber*)offset durationMs: (nonnull NSNumber*)durationMs resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - [ChangeLineOffsetsShapeAnimator setStartOffsetWithTag:tag offset:offset durationMs:durationMs resolve:resolve reject:reject]; +- (void)setStartOffset:(NSInteger)tag + offset:(double)offset + duration:(double)duration + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject +{ + [ChangeLineOffsetsShapeAnimator setStartOffsetWithTag:@(tag) + offset:@(offset) + durationMs:@(duration) + resolve:resolve + reject:reject]; } -RCT_EXPORT_METHOD(setEndOffset:(nonnull NSNumber*)tag offset: (nonnull NSNumber*)offset durationMs: (nonnull NSNumber*)durationMs resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - [ChangeLineOffsetsShapeAnimator setEndOffsetWithTag:tag offset:offset durationMs:durationMs resolve:resolve reject:reject]; +- (void)setEndOffset:(NSInteger)tag + offset:(double)offset + duration:(double)duration + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject +{ + [ChangeLineOffsetsShapeAnimator setEndOffsetWithTag:@(tag) + offset:@(offset) + durationMs:@(duration) + resolve:resolve + reject:reject]; } - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { - return std::make_shared(params); + return std::make_shared(params); } @end diff --git a/ios/RNMBX/ShapeAnimators/RNMBXMovePointShapeAnimatorModule.mm b/ios/RNMBX/ShapeAnimators/RNMBXMovePointShapeAnimatorModule.mm index 20f8f2eb8b..82db955f61 100644 --- a/ios/RNMBX/ShapeAnimators/RNMBXMovePointShapeAnimatorModule.mm +++ b/ios/RNMBX/ShapeAnimators/RNMBXMovePointShapeAnimatorModule.mm @@ -25,18 +25,38 @@ - (dispatch_queue_t)methodQueue { return RCTGetUIManagerQueue(); } -RCT_EXPORT_METHOD(generate:(nonnull NSNumber*)tag startCoordinate: (nonnull NSArray*)startCoordinate resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - resolve([[MovePointShapeAnimator generateWithTag:tag startCoordinate:startCoordinate] getTag]); +// Must match NativeRNMBXMovePointShapeAnimatorModuleSpec exactly (codegen uses NSInteger/double). +- (void)generate:(NSInteger)tag + coordinate:(NSArray *)coordinate + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject +{ + MovePointShapeAnimator *animator = + [MovePointShapeAnimator generateWithTag:@(tag) startCoordinate:coordinate]; + if (animator == nil) { + reject(@"RNMBXMovePointShapeAnimatorModule", @"Failed to generate animator", nil); + return; + } + resolve([animator getTag]); } -RCT_EXPORT_METHOD(moveTo:(nonnull NSNumber*)tag coordinate: (nonnull NSArray*)coordinate durationMs: (nonnull NSNumber*)durationMs resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - [MovePointShapeAnimator moveToTag:tag coordinate:coordinate durationMs:durationMs resolve:resolve reject:reject]; +- (void)moveTo:(NSInteger)tag + coordinate:(NSArray *)coordinate + duration:(double)duration + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject +{ + [MovePointShapeAnimator moveToTag:@(tag) + coordinate:coordinate + durationMs:@(duration) + resolve:resolve + reject:reject]; } - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { - return std::make_shared(params); + return std::make_shared(params); } @end diff --git a/ios/RNMBX/ShapeAnimators/RNMBXMovePointShapeAnimatorModule.swift b/ios/RNMBX/ShapeAnimators/RNMBXMovePointShapeAnimatorModule.swift index 1a5e7296e4..2599529793 100644 --- a/ios/RNMBX/ShapeAnimators/RNMBXMovePointShapeAnimatorModule.swift +++ b/ios/RNMBX/ShapeAnimators/RNMBXMovePointShapeAnimatorModule.swift @@ -93,23 +93,28 @@ extension MovePointShapeAnimator { } @objc - public static func moveTo(tag: NSNumber, coordinate: NSArray, durationMs: NSNumber, resolve: RCTPromiseResolveBlock, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) { - guard let lng = coordinate[0] as? Double, let lat = coordinate[1] as? Double else { - reject("\(LOG_TAG): moveTo", "Unable to find animator with tag \(tag)", NSError()) - return - } + public static func moveTo(tag: NSNumber, coordinate: NSArray, durationMs: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) { + DispatchQueue.main.async { + // RN arrays often bridge as NSNumber; `as? Double` fails for NSNumber. + let lngValue = (coordinate[0] as? NSNumber)?.doubleValue ?? (coordinate[0] as? Double) + let latValue = (coordinate[1] as? NSNumber)?.doubleValue ?? (coordinate[1] as? Double) + guard let lng = lngValue, let lat = latValue else { + reject("\(LOG_TAG): moveTo", "Invalid coordinate for tag \(tag)", NSError()) + return + } + + guard let animator = getAnimator(tag: tag) else { + reject("\(LOG_TAG): moveTo", "Unable to find animator with tag \(tag)", NSError()) + return + } + + let targetCoord = LocationCoordinate2D( + latitude: lat, + longitude: lng + ) - guard let animator = getAnimator(tag: tag) else { - reject("\(LOG_TAG): moveTo", "Unable to find animator with tag \(tag)", NSError()) - return + animator.moveTo(coordinate: targetCoord, durationSec: durationMs.doubleValue / 1000) + resolve(tag) } - - let targetCoord = LocationCoordinate2D( - latitude: lat, - longitude: lng - ) - - animator.moveTo(coordinate: targetCoord, durationSec: durationMs.doubleValue / 1000) - resolve(tag) } } diff --git a/ios/RNMBX/ShapeAnimators/ShapeAnimatorCommon.swift b/ios/RNMBX/ShapeAnimators/ShapeAnimatorCommon.swift index d3533a2c90..3953675b01 100644 --- a/ios/RNMBX/ShapeAnimators/ShapeAnimatorCommon.swift +++ b/ios/RNMBX/ShapeAnimators/ShapeAnimatorCommon.swift @@ -63,6 +63,11 @@ public class ShapeAnimatorCommon: NSObject, ShapeAnimator { // - MARK: Lifecycle @objc func refresh() { + guard Thread.isMainThread else { + DispatchQueue.main.async { [weak self] in self?.refresh() } + return + } + if startedAt == nil { startedAt = displayLink?.targetTimestamp.magnitude ?? 0 } diff --git a/ios/RNMBX/ShapeAnimators/ShapeAnimatorManager.swift b/ios/RNMBX/ShapeAnimators/ShapeAnimatorManager.swift index 6f7cc66632..f1d89055ea 100644 --- a/ios/RNMBX/ShapeAnimators/ShapeAnimatorManager.swift +++ b/ios/RNMBX/ShapeAnimators/ShapeAnimatorManager.swift @@ -5,18 +5,24 @@ class ShapeAnimatorManager { typealias Tag = Int + private let lock = NSLock() var animatorByTags: [Tag: ShapeAnimator] = [:] func getShapeAnimatorByTag(tag: Tag) -> ShapeAnimator? { - return animatorByTags[tag] + return get(tag: tag) } + // Keep registration synchronous: ShapeSource.shape looks up by tag in didSet. + // Hopping to main.async races that lookup and can leave the source unbound. + // Lock instead so generate (UIManager queue) and get (main) stay consistent. func register(tag: Tag, animator: ShapeAnimator) { + lock.lock() animatorByTags[tag] = animator + lock.unlock() } func withAnimator(tag: NSNumber, callback: (ShapeAnimator) -> Void) { - if let animator = animatorByTags[tag.intValue] { + if let animator = get(tag: tag.intValue) { callback(animator) } } @@ -26,6 +32,8 @@ class ShapeAnimatorManager { } func get(tag: Tag) -> ShapeAnimator? { + lock.lock() + defer { lock.unlock() } return animatorByTags[tag] } @@ -37,6 +45,9 @@ class ShapeAnimatorManager { if let tag = json["__nativeTag"] as? Int { return get(tag: tag) } + if let tagNumber = json["__nativeTag"] as? NSNumber { + return get(tag: tagNumber.intValue) + } } } catch { Logger.log(level: .error, tag: LOG_TAG, message: "Unable to get animator tag from \(shape): \(error)")