Skip to content

Instantly share code, notes, and snippets.

@theoknock
Created January 16, 2022 14:07
Show Gist options
  • Save theoknock/463d17942cb6b36ca97bc69e17b6843c to your computer and use it in GitHub Desktop.
Save theoknock/463d17942cb6b36ca97bc69e17b6843c to your computer and use it in GitHub Desktop.
Touch handling that reuses the same call regardless of touch phase, and also reuses the same UITouch object passed to touchesBegan throughout the entire touch sequence. Uses bitwise operators to simultaneously detect a given phase and set a button property.
static void (^(^(^touch_handler_init)(UIView *))(UITouch *))(void) = ^ (UIView * view) {
CGRect contextRect = view.bounds;
float minX = (float)CGRectGetMinX(contextRect);
float midX = (float)CGRectGetMidX(contextRect);
float mdnX = (float)(((int)midX & (int)minX) + (((int)midX ^ (int)minX) >> 1)); // Average of two floats
.
.
.
return ^ (UITouch * touch) {
static CGPoint touch_point;
static CGFloat touch_angle;
static unsigned int touch_property;
return ^{
touch_point = [touch preciseLocationInView:view];
touch_angle = atan2(touch_point.y - maxY, touch_point.x - maxX) * (180.0 / M_PI);
if (touch_angle < 0.0) touch_angle += 360.0;
touch_property = (unsigned int)round(rescale(touch_angle, 180.0, 270.0, 0.0, 4.0));
filter(buttons)(^ (UIButton * _Nonnull button, unsigned int index) {
[button setSelected:!(UITouchPhaseEnded ^ touch.phase) & !(touch_property ^ button.tag)];
[button setHighlighted:(UITouchPhaseEnded ^ touch.phase) & !(touch_property ^ button.tag)];
[button setCenter:^{
float angle = rescale(button.tag, 0, 4, 180.0, 270.0);
float radians = degreesToRadians(angle);
float radius = sqrt(pow(touch_point.x - maxX, 2.0) +
pow(touch_point.y - maxY, 2.0));
radius = fmaxf(midX, fminf(radius, maxX));
CGFloat x = maxX - radius * -cos(radians);
CGFloat y = maxY - radius * -sin(radians);
return CGPointMake(x, y);
}()];
});
};
};
};
static void (^(^touch_handler)(UITouch *))(void);
static void (^handle_touch)(void);
map(buttons)(^ UIButton * (unsigned int index) {
UIButton * button;
[button = [UIButton new] setTag:index];
[button setImage:[UIImage systemImageNamed:@"questionmark.circle" withConfiguration:[[UIImageSymbolConfiguration configurationWithPointSize:42] configurationByApplyingConfiguration:[UIImageSymbolConfiguration configurationPreferringMulticolor]]] forState:UIControlStateNormal];
[button setImage:[UIImage systemImageNamed:@"questionmark.circle.fill" withConfiguration:[[UIImageSymbolConfiguration configurationWithPointSize:42] configurationByApplyingConfiguration:[UIImageSymbolConfiguration configurationPreferringMulticolor]]] forState:UIControlStateHighlighted];
[button setImage:[UIImage systemImageNamed:@"exclamationmark.circle.fill" withConfiguration:[[UIImageSymbolConfiguration configurationWithPointSize:42] configurationByApplyingConfiguration:[UIImageSymbolConfiguration configurationPreferringMulticolor]]] forState:UIControlStateSelected];
[button sizeToFit];
[button setUserInteractionEnabled:FALSE];
void (^eventHandlerBlockTouchUpInside)(void) = ^{
};
objc_setAssociatedObject(button, @selector(invoke), eventHandlerBlockTouchUpInside, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[button addTarget:eventHandlerBlockTouchUpInside action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
float angle = 270.0 - ((90.0 / 4.0) * button.tag);
[button setCenter:[[UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMaxX(self.view.bounds), CGRectGetMaxY(self.view.bounds)) radius:CGRectGetMidX(self.view.bounds) startAngle:degreesToRadians(angle) endAngle:degreesToRadians(angle) clockwise:FALSE] currentPoint]];
return button;
});
touch_handler = touch_handler_init(self.view);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// set once per touch
dispatch_barrier_async(dispatch_get_main_queue(), ^{ (handle_touch = touch_handler(touches.anyObject))(); });
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// reuses UITouch object temporarily retained, above
dispatch_barrier_async(dispatch_get_main_queue(), ^{ handle_touch(); });
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// reuses UITouch object temporarily retained, above
dispatch_barrier_async(dispatch_get_main_queue(), ^{ handle_touch(); });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment