iOS cheats support

This commit is contained in:
Lior Halphon 2024-09-07 01:25:11 +03:00
parent 50a56a4b68
commit 92f425655d
20 changed files with 465 additions and 20 deletions

View File

@ -1166,7 +1166,7 @@ static bool is_path_writeable(const char *path)
}
}
GB_load_battery(&_gb, self.savPath.UTF8String);
GB_load_cheats(&_gb, self.chtPath.UTF8String);
GB_load_cheats(&_gb, self.chtPath.UTF8String, true);
dispatch_async(dispatch_get_main_queue(), ^{
[self.cheatWindowController cheatsUpdated];
});

View File

@ -6,7 +6,7 @@ static id nop(id self, SEL _cmd)
return nil;
}
static double blah(id self, SEL _cmd)
static double minSize(id self, SEL _cmd)
{
return 80.0;
}
@ -18,7 +18,7 @@ static double blah(id self, SEL _cmd)
// Prevent collapsing toolbar items into menu items, they don't work in that form
method_setImplementation(class_getInstanceMethod(self, @selector(menuFormRepresentation)), (IMP)nop);
// Prevent over-agressive collapsing of the Printer Feed menu
method_setImplementation(class_getInstanceMethod(NSClassFromString(@"NSToolbarTitleView"), @selector(minSize)), (IMP)blah);
method_setImplementation(class_getInstanceMethod(NSClassFromString(@"NSToolbarTitleView"), @selector(minSize)), (IMP)minSize);
}
@end

View File

@ -141,6 +141,13 @@ void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
free((void *)cheat);
}
void GB_remove_all_cheats(GB_gameboy_t *gb)
{
while (gb->cheats) {
GB_remove_cheat(gb, gb->cheats[0]);
}
}
const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
@ -261,13 +268,13 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des
#define CHEAT_MAGIC 'SBCh'
void GB_load_cheats(GB_gameboy_t *gb, const char *path)
int GB_load_cheats(GB_gameboy_t *gb, const char *path, bool replace_existing)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
FILE *f = fopen(path, "rb");
if (!f) {
return;
return errno;
}
uint32_t magic = 0;
@ -276,17 +283,17 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path)
fread(&struct_size, sizeof(struct_size), 1, f);
if (magic != LE32(CHEAT_MAGIC) && magic != BE32(CHEAT_MAGIC)) {
GB_log(gb, "The file is not a SameBoy cheat database");
return;
return -1;
}
if (struct_size != sizeof(GB_cheat_t)) {
GB_log(gb, "This cheat database is not compatible with this version of SameBoy");
return;
return -1;
}
// Remove all cheats first
while (gb->cheats) {
GB_remove_cheat(gb, gb->cheats[0]);
if (replace_existing) {
GB_remove_all_cheats(gb);
}
GB_cheat_t cheat;
@ -299,7 +306,7 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path)
GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled);
}
return;
return 0;
}
int GB_save_cheats(GB_gameboy_t *gb, const char *path)

View File

@ -11,9 +11,10 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat, const char *desc
const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled);
const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size);
void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat);
void GB_remove_all_cheats(GB_gameboy_t *gb);
bool GB_cheats_enabled(GB_gameboy_t *gb);
void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled);
void GB_load_cheats(GB_gameboy_t *gb, const char *path);
int GB_load_cheats(GB_gameboy_t *gb, const char *path, bool replace_existing);
int GB_save_cheats(GB_gameboy_t *gb, const char *path);
#ifdef GB_INTERNAL

View File

@ -226,9 +226,7 @@ void GB_free(GB_gameboy_t *gb)
#endif
GB_rewind_reset(gb);
#ifndef GB_DISABLE_CHEATS
while (gb->cheats) {
GB_remove_cheat(gb, gb->cheats[0]);
}
GB_remove_all_cheats(gb);
#endif
#ifndef GB_DISABLE_CHEAT_SEARCH
GB_cheat_search_reset(gb);

View File

@ -923,7 +923,9 @@ restart:;
char cheat_path[path_length + 5];
replace_extension(filename, path_length, cheat_path, ".cht");
GB_load_cheats(&gb, cheat_path);
// Remove all cheats before loading, so they're cleared even if loading fails.
GB_remove_all_cheats(&gb);
GB_load_cheats(&gb, cheat_path, false);
end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING, "Warning", previous);

BIN
iOS/CheatsTemplate@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
iOS/CheatsTemplate@3x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

7
iOS/GBCheatsController.h Normal file
View File

@ -0,0 +1,7 @@
#import <UIKit/UIKit.h>
#import <Core/gb.h>
@interface GBCheatsController : UITableViewController
- (instancetype)initWithGameBoy:(GB_gameboy_t *)gb;
@end

350
iOS/GBCheatsController.m Normal file
View File

@ -0,0 +1,350 @@
#import "GBCheatsController.h"
#import "GBROMManager.h"
#import "UIToolbar+disableCompact.h"
#import <CoreServices/CoreServices.h>
#import <objc/runtime.h>
@interface GBCheatsController()<UIDocumentPickerDelegate>
@end
@implementation GBCheatsController
{
GB_gameboy_t *_gb;
NSIndexPath *_renamingPath;
__weak UITextField *_editingField;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) return 1;
size_t count;
GB_get_cheats(_gb, &count);
self.toolbarItems.firstObject.enabled = count;
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
UISwitch *button = [[UISwitch alloc] init];
cell.accessoryView = button;
const GB_cheat_t *cheat = NULL;
if (indexPath.section == 0) {
button.on = GB_cheats_enabled(_gb);
cell.textLabel.text = @"Enable Cheats";
}
else {
cheat = GB_get_cheats(_gb, NULL)[indexPath.row];
button.on = cheat->enabled;
cell.textLabel.text = @(cheat->description) ?: @"Unnamed Cheat";
button.enabled = GB_cheats_enabled(_gb);
}
id block = ^(){
if (!cheat) {
GB_set_cheats_enabled(_gb, button.on);
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone];
}
else {
GB_update_cheat(_gb, cheat, cheat->description,
cheat->address, cheat->bank,
cheat->value, cheat->old_value, cheat->use_old_value,
button.on);
}
};
objc_setAssociatedObject(cell, "RetainedBlock", block, OBJC_ASSOCIATION_RETAIN);
[button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventValueChanged];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
- (void)addCheat
{
[self setEditing:false animated:true];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle: @"Add Cheat"
message: @"Add a GameShark or Game Genie cheat code"
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = @"Description";
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = @"Cheat Code";
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.keyboardType = UIKeyboardTypeASCIICapable;
}];
[alertController addAction:[UIAlertAction actionWithTitle:@"Add" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
size_t index = [self tableView:self.tableView numberOfRowsInSection:1];
NSString *name = alertController.textFields[0].text;
if (GB_import_cheat(_gb, alertController.textFields[1].text.UTF8String, name.length? name.UTF8String : "Unnamed Cheat", true)) {
[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:1]] withRowAnimation:UITableViewRowAnimationAutomatic];
}
else {
alertController.title = @"Invalid cheat code entered";
[self presentViewController:alertController animated:YES completion:nil];
}
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
+ (UIBarButtonItem *)buttonWithLabel:(NSString *)label
imageWithName:(NSString *)imageName
target:(id)target
action:(SEL)action
{
if (@available(iOS 13.0, *)) {
UIImage *image = [UIImage systemImageNamed:imageName
withConfiguration:[UIImageSymbolConfiguration configurationWithScale:UIImageSymbolScaleLarge]];
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setImage:image forState:UIControlStateNormal];
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
[button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
if (label) {
[button setTitle:label forState:UIControlStateNormal];
[button setTitleColor:button.tintColor forState:UIControlStateNormal];
button.titleEdgeInsets = UIEdgeInsetsMake(0, 4, 0, 0);
button.contentEdgeInsets = UIEdgeInsetsMake(0, 12, 0, 0);
}
[button sizeToFit];
CGRect frame = button.frame;
frame.size.width = ceil(frame.size.width + (label? 4 : 0));
frame.size.height = 28;
button.frame = frame;
return [[UIBarButtonItem alloc] initWithCustomView:button];
}
return [[UIBarButtonItem alloc] initWithTitle:label style:UIBarButtonItemStylePlain target:target action:action];
}
- (void)importCheats
{
[self setEditing:false animated:true];
NSString *chtUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"cht", NULL);
UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[chtUTI]
inMode:UIDocumentPickerModeImport];
if (@available(iOS 13.0, *)) {
picker.shouldShowFileExtensions = true;
}
picker.delegate = self;
[self presentViewController:picker animated:true completion:nil];
return;
}
- (void)exportCheats
{
[self setEditing:false animated:true];
NSString *cheatsFile = [[GBROMManager sharedManager] cheatsFile];
GB_save_cheats(_gb, cheatsFile.UTF8String);
NSURL *url = [NSURL fileURLWithPath:cheatsFile];
UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url]
applicationActivities:nil];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
controller.popoverPresentationController.barButtonItem = self.toolbarItems.firstObject;
}
[self presentViewController:controller
animated:true
completion:nil];
}
- (instancetype)initWithGameBoy:(GB_gameboy_t *)gb
{
UITableViewStyle style = UITableViewStyleGrouped;
if (@available(iOS 13.0, *)) {
style = UITableViewStyleInsetGrouped;
}
self = [super initWithStyle:style];
self.tableView.allowsSelectionDuringEditing = true;
self.navigationItem.rightBarButtonItem = self.editButtonItem;
bool hasSFSymbols = false;
if (@available(iOS 13.0, *)) {
hasSFSymbols = true;
}
self.toolbarItems = @[
hasSFSymbols?
[self.class buttonWithLabel:nil
imageWithName:@"square.and.arrow.up"
target:self
action:@selector(exportCheats)] :
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
target:self
action:@selector(exportCheats)],
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:NULL],
[self.class buttonWithLabel:@"Import"
imageWithName:@"square.and.arrow.down"
target:self
action:@selector(importCheats)],
[self.class buttonWithLabel:@"Add"
imageWithName:@"plus"
target:self
action:@selector(addCheat)],
];
_gb = gb;
return self;
}
- (NSString *)title
{
return @"Cheats";
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return indexPath.section == 1;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section != 1) return;
if (editingStyle != UITableViewCellEditingStyleDelete) return;
const GB_cheat_t *cheat = GB_get_cheats(_gb, NULL)[indexPath.row];
GB_remove_cheat(_gb, cheat);
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:false animated:false];
self.navigationController.toolbar.disableCompactLayout = true;
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url
{
[url startAccessingSecurityScopedResource];
NSString *tempDir = NSTemporaryDirectory();
NSString *newPath = [tempDir stringByAppendingPathComponent:@"import.cht"];
[[NSFileManager defaultManager] copyItemAtPath:url.path toPath:newPath error:nil];
[url stopAccessingSecurityScopedResource];
unsigned count = [self tableView:self.tableView numberOfRowsInSection:1];
void (^load)(bool) = ^(bool replace) {
if (GB_load_cheats(_gb, newPath.UTF8String, replace)) {
[[NSFileManager defaultManager] removeItemAtPath:newPath error:nil];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Import Failed"
message:@"The imported cheats file is invalid."
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertController animated:true completion:nil];
return;
}
[[NSFileManager defaultManager] removeItemAtPath:newPath error:nil];
unsigned newCount = [self tableView:self.tableView numberOfRowsInSection:1];
if (!replace) {
NSMutableArray *paths = [NSMutableArray arrayWithCapacity:newCount - count];
for (unsigned i = count; i < newCount; i++) {
[paths addObject:[NSIndexPath indexPathForRow:i inSection:1]];
}
if (paths.count) {
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
else {
NSMutableArray *paths = [NSMutableArray arrayWithCapacity:abs((signed)newCount - (signed)count)];
for (unsigned i = MIN(newCount, count); i < count || i < newCount; i++) {
[paths addObject:[NSIndexPath indexPathForRow:i inSection:1]];
}
if (newCount > count) {
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic];
}
else {
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic];
}
paths = [NSMutableArray arrayWithCapacity:MIN(newCount, count)];
for (unsigned i = 0; i < count && i < newCount; i++) {
[paths addObject:[NSIndexPath indexPathForRow:i inSection:1]];
}
[self.tableView reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationRight];
}
};
if (count) {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Replace Existing Cheats?"
message:@"Append the newly imported cheats or replace the existing ones?"
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"Append" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
load(false);
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Replace" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
load(true);
}]];
[self presentViewController:alertController animated:true completion:nil];
}
else {
load(true);
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) return;
if (!self.editing) return;
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
CGRect frame = cell.textLabel.frame;
frame.size.width = cell.textLabel.superview.frame.size.width - 8 - frame.origin.x;
UITextField *field = [[UITextField alloc] initWithFrame:frame];
field.font = cell.textLabel.font;
field.text = cell.textLabel.text;
cell.textLabel.text = @"";
[[cell.textLabel superview] addSubview:field];
[field becomeFirstResponder];
[field selectAll:nil];
_renamingPath = indexPath;
[field addTarget:self action:@selector(doneRename:) forControlEvents:UIControlEventEditingDidEnd | UIControlEventEditingDidEndOnExit];
_editingField = field;
}
- (void)doneRename:(UITextField *)sender
{
if (!_renamingPath) return;
const GB_cheat_t *cheat = GB_get_cheats(_gb, NULL)[_renamingPath.row];
GB_update_cheat(_gb, cheat, sender.text.length? sender.text.UTF8String : "Unnamed Cheat",
cheat->address, cheat->bank,
cheat->value, cheat->old_value, cheat->use_old_value,
cheat->enabled);
[self.tableView reloadRowsAtIndexPaths:@[_renamingPath] withRowAnimation:UITableViewRowAnimationNone];
_renamingPath = nil;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
if (!editing && _editingField) {
[self doneRename:_editingField];
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
NSString *cheatsFile = [[GBROMManager sharedManager] cheatsFile];
[[NSFileManager defaultManager] removeItemAtPath:cheatsFile error:nil];
GB_save_cheats(_gb, cheatsFile.UTF8String);
}
@end

View File

@ -61,7 +61,7 @@ static NSString *const tips[] = {
{@"Connect", @"LinkCableTemplate", SelectorString(openConnectMenu), true},
{@"Model", @"ModelTemplate", SelectorString(changeModel)},
{@"States", @"square.stack", SelectorString(openStates), true},
{@"Cheats", @"wand.and.stars", nil}, // TODO
{@"Cheats", @"CheatsTemplate", SelectorString(openCheats), true}, // TODO
{@"Settings", @"gear", SelectorString(openSettings)},
{@"About", @"info.circle", SelectorString(showAbout)},
};
@ -85,10 +85,6 @@ static NSString *const tips[] = {
button.enabled = false;
continue;
}
if (buttons[i].selector == nil) {
button.enabled = false;
continue;
}
SEL selector = NSSelectorFromString(buttons[i].selector);
if (buttons[i].requireRunning && ![GBROMManager sharedManager].currentROM) {
button.enabled = false;

View File

@ -9,6 +9,7 @@
@property (readonly) NSString *romFile;
@property (readonly) NSString *batterySaveFile;
@property (readonly) NSString *autosaveStateFile;
@property (readonly) NSString *cheatsFile;
- (NSString *)stateFile:(unsigned)index;
- (NSString *)romFileForROM:(NSString *)rom;

View File

@ -103,6 +103,17 @@
return [self stateFile:index forROM:_currentROM];
}
- (NSString *)cheatsFileForROM:(NSString *)rom
{
return [self auxilaryFileForROM:rom withExtension:@"cht"];
}
- (NSString *)cheatsFile
{
return [self cheatsFileForROM:_currentROM];
}
- (NSArray<NSString *> *)allROMs
{
NSMutableArray<NSString *> *ret = [NSMutableArray array];

View File

@ -23,6 +23,7 @@ typedef enum {
- (void)openSettings;
- (void)showAbout;
- (void)openConnectMenu;
- (void)openCheats;
- (void)emptyPrinterFeed;
- (void)saveStateToFile:(NSString *)file;
- (bool)loadStateFromFile:(NSString *)file;

View File

@ -14,6 +14,7 @@
#import "GBStatesViewController.h"
#import "GBCheckableAlertController.h"
#import "GBPrinterFeedController.h"
#import "GBCheatsController.h"
#import "GCExtendedGamepad+AllElements.h"
#import "GBZipReader.h"
#import <sys/stat.h>
@ -674,6 +675,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
if (_romLoaded) {
GB_reset(&_gb);
GB_load_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation);
GB_remove_all_cheats(&_gb);
GB_load_cheats(&_gb, [GBROMManager sharedManager].cheatsFile.UTF8String, false);
if (![self loadStateFromFile:[GBROMManager sharedManager].autosaveStateFile]) {
// Newly played ROM, pick the best model
uint8_t *rom = GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, NULL, NULL);
@ -820,6 +823,19 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[self presentViewController:[[GBAboutController alloc] init] animated:true completion:nil];
}
- (void)openCheats
{
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBCheatsController alloc] initWithGameBoy:&_gb]];
UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close"
style:UIBarButtonItemStylePlain
target:self
action:@selector(dismissViewController)];
[controller.visibleViewController.navigationItem setLeftBarButtonItem:close];
[self presentViewController:controller
animated:true
completion:nil];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
[super dismissViewControllerAnimated:flag completion:^() {

View File

@ -176,6 +176,23 @@
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>SameBoy Cheats Database</string>
<key>UTTypeIdentifier</key>
<string>com.github.liji32.sameboy.cheats</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>cht</string>
</array>
</dict>
</dict>
</array>
<key>UIDeviceFamily</key>
<array>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 851 B

After

Width:  |  Height:  |  Size: 927 B

View File

@ -0,0 +1,5 @@
#import <UIKit/UIKit.h>
@interface UIToolbar (disableCompact)
@property bool disableCompactLayout;
@end

View File

@ -0,0 +1,33 @@
#import "UIToolbar+disableCompact.h"
#import <objc/runtime.h>
@implementation UIToolbar (disableCompact)
- (void)setDisableCompactLayout:(bool)disableCompactLayout
{
objc_setAssociatedObject(self, @selector(disableCompactLayout), @(disableCompactLayout), OBJC_ASSOCIATION_RETAIN);
}
- (bool)disableCompactLayout
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (CGSize)sizeThatFitsHook:(CGSize)size
{
CGSize ret = [self sizeThatFitsHook:size];
if (!self.disableCompactLayout) return ret;
if (ret.height < 44) {
ret.height = 44;
}
return ret;
}
+ (void)load
{
method_exchangeImplementations(class_getInstanceMethod(self, @selector(sizeThatFitsHook:)),
class_getInstanceMethod(self, @selector(sizeThatFits:)));
}
@end