Skip to content

Instantly share code, notes, and snippets.

@stefanceriu
Last active June 21, 2024 05:12
Show Gist options
  • Save stefanceriu/5ff0c67e98ae44612857cd17fd4377d1 to your computer and use it in GitHub Desktop.
Save stefanceriu/5ff0c67e98ae44612857cd17fd4377d1 to your computer and use it in GitHub Desktop.
Enabled iOS style UICollectionView multiple selection on Catalyst applications
//
// UICollectionView+CTX.h
// EFClass-Mac
//
// Created by Stefan Ceriu on 29/11/2019.
// Copyright © 2019 EF Education First. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface UICollectionView (CTX)
@end
//
// UICollectionView+CTX.m
// EFClass-Mac
//
// Created by Stefan Ceriu on 29/11/2019.
// Copyright © 2019 EF Education First. All rights reserved.
//
#import "UICollectionView+CTX.h"
#import <objc/runtime.h>
static void * CTXSelectedItemIndexPathPropertyKey = &CTXSelectedItemIndexPathPropertyKey;
@interface UICollectionView ()
@property (nonatomic, strong) NSIndexPath *ctx_selectedItemIndexPath;
@end
@implementation UICollectionView (CTX)
+ (void)load
{
#if TARGET_OS_MACCATALYST
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CTXSwizzleInstanceMethod([self class], NSSelectorFromString(@"_userSelectItemAtIndexPath:"), @selector(ctx_userSelectItemAtIndexPath:));
CTXSwizzleInstanceMethod([self class], NSSelectorFromString(@"_deselectAllAnimated:notifyDelegate:"), @selector(ctx_deselectAllAnimated:notifyDelegate:));
CTXSwizzleInstanceMethod([self class], NSSelectorFromString(@"_deselectItemAtIndexPath:animated:notifyDelegate:"),
@selector(ctx_deselectItemAtIndexPath:animated:notifyDelegate:));
});
#endif
}
- (void)ctx_userSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.ctx_selectedItemIndexPath = indexPath;
[self ctx_userSelectItemAtIndexPath:indexPath];
}
- (void)ctx_deselectAllAnimated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate
{
if(self.ctx_selectedItemIndexPath && [self.indexPathsForSelectedItems containsObject:self.ctx_selectedItemIndexPath]) {
if([self.delegate respondsToSelector:@selector(collectionView:shouldDeselectItemAtIndexPath:)]) {
if([self.delegate collectionView:self shouldDeselectItemAtIndexPath:self.ctx_selectedItemIndexPath]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self ctx_deselectItemAtIndexPath:self.ctx_selectedItemIndexPath animated:animated notifyDelegate:YES];
self.ctx_selectedItemIndexPath = nil;
});
}
}
}
}
- (void)ctx_deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate
{
[self ctx_deselectItemAtIndexPath:indexPath animated:animated notifyDelegate:notifyDelegate];
}
- (NSIndexPath *)ctx_selectedItemIndexPath
{
return objc_getAssociatedObject(self, CTXSelectedItemIndexPathPropertyKey);
}
- (void)setCtx_selectedItemIndexPath:(NSIndexPath *)unicorn {
objc_setAssociatedObject(self, CTXSelectedItemIndexPathPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
static void CTXSwizzleInstanceMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
if (class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
@Megatron1000
Copy link

This was working great for me but something seems to have changed recently. I've just run my app on Xcode 13.3.1 and macOS 12.3.1 and func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { is no longer being called when this swizzling is being done. I also ran into problems with indexPathsForSelectedItems returning multiple values on CollectionViews where allowsMultipleSelection is false.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment