Search code examples
swiftrealmmac-catalyst

Realm writes make NSColorPanel lag


In my Catalyst app, I'm spawning an NSColorPanel with a callback using code from the Mac Helpers repository.

@interface IPDFMacColorPanel : NSObject

+ (void)showColorPanelWithColorChangeHandler:(void(^)(UIColor *color))colorChangeHandler;

/// Hides the color picker panel and removes the colorChangeHandler observer
+ (void)hide;

+ (void)removeColorChangeHandlerObserver;

@end

@interface NSColorPanel_Catalyst : NSObject

+ (instancetype)sharedColorPanel;

- (void)makeKeyAndOrderFront:(id)sender;
- (void)orderFront:(id)sender;
- (void)orderOut:(id)sender;

- (void)setTarget:(id)target;
- (void)setAction:(SEL)action;

- (UIColor *)color;

@end

@interface IPDFMacColorPanel ()

@property (nonatomic,copy) void(^colorChangeHandler)(UIColor *color);

@end

@implementation IPDFMacColorPanel

+ (instancetype)sharedPanel
{
    static IPDFMacColorPanel *panel = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        panel = [IPDFMacColorPanel new];
    });
    return panel;
}

+ (NSColorPanel_Catalyst *)colorPanel
{
    return [NSClassFromString(@"NSColorPanel") performSelector:@selector(sharedColorPanel)];
}

+ (void)showColorPanelWithColorChangeHandler:(void(^)(UIColor *color))colorChangeHandler
{
    IPDFMacColorPanel *observer = [IPDFMacColorPanel sharedPanel];
    observer.colorChangeHandler = colorChangeHandler;

    NSColorPanel_Catalyst *colorPanel = [self colorPanel];
    [colorPanel setTarget:observer];
    [colorPanel setAction:@selector(colorChange:)];
    [colorPanel orderFront:nil];
}

+ (void)hide
{
    [[self colorPanel] orderOut:nil];
    [self removeColorChangeHandlerObserver];
}

+ (void)removeColorChangeHandlerObserver
{
    [IPDFMacColorPanel sharedPanel].colorChangeHandler = nil;
}

- (void)colorChange:(NSColorPanel_Catalyst *)colorPanel
{
    if (self.colorChangeHandler) self.colorChangeHandler(colorPanel.color);
}

@end

I spawn it like this:

IPDFMacColorPanel.show { (color) in
    if let color = color {
        self.saveColor(color)
    }
}

And then write it to Realm:

public func saveColor(_ color: UIColor) {
    try! realm.write {
        let converted = ColorUtility.convert(color: color)
        if let green = converted["green"] {
            self.restaurant.green = green
        }
        if let red = converted["red"] {
            self.restaurant.red = red
        }
        if let blue = converted["blue"] {
            self.restaurant.blue = blue
        }
        if let alpha = converted["alpha"] {
            self.restaurant.alpha = alpha
        }
    }
}

However, what happens is, because I get so many callbacks, Realm is writing a lot of colors, making the color panel lag. Any ideas how I can solve this?


Solution

  • Coalesce the writes on the Objective-C side.

    // new property - add after `@interface IPDFMacColorPanel` line
    @property (nonatomic,copy) UIColor *pendingNewColor;
    
    - (void)colorChange:(NSColorPanel_Catalyst *)colorPanel
    {
        if (self.pendingNewColor) {
            self.pendingNewColor = colorPanel.color;
        } else {
            self.pendingNewColor = colorPanel.color;
            [self performSelector:@selector(doUpdateColor:) withObject:nil afterDelay:0.4];
        }
    }
    
    - (void)doUpdateColor:(id)ignore {
        UIColor *color = self.pendingNewColor;
        self.pendingNewColor = nil;
        if (self.colorChangeHandler) self.colorChangeHandler(color);
    }
    

    When the color is changed, if a pending color exists, it assumes that the doUpdateColor: message will be sent within 0.4 seconds and just updates the pending color, otherwise it sets the pending color and schedules the doUpdateColor: message to be sent.

    This solution trades away some apparent responsiveness. You can also do something similar on the receiving Swift side, by storing the color somewhere and just not writing it to Realm for a short while, which would maintain immediate responsiveness, but if something was using the color through reading the just-written values from Realm, there would still be a delay there.