From b89f3ba912f81f6e1b9bd6447c64e935516755bd Mon Sep 17 00:00:00 2001 From: wutschel Date: Sat, 1 Nov 2025 16:36:56 +0100 Subject: [PATCH 1/9] Fix handling settings reporting as "slider" Map "slider" to SettingTypeSlider. Set "maximum" to 100% for format "percentage", if maximum is not defined or set to 0. Make sure also "step" < 1 is possible to be processed. --- XBMC Remote/SettingsValuesViewController.m | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index 5c45239b6..296a81d09 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -94,6 +94,13 @@ - (id)initWithFrame:(CGRect)frame withItem:(id)item { xbmcSetting = SettingTypeSlider; storeSliderValue = [self.detailItem[@"value"] intValue]; } + else if ([itemControls[@"type"] isEqualToString:@"slider"] && settingOptions == nil) { + xbmcSetting = SettingTypeSlider; + storeSliderValue = [self.detailItem[@"value"] intValue]; + if ([itemControls[@"format"] isEqualToString:@"percentage"] && ![self.detailItem[@"maximum"] intValue]) { + self.detailItem[@"maximum"] = @100; + } + } else if ([itemControls[@"type"] isEqualToString:@"edit"]) { xbmcSetting = SettingTypeInput; } @@ -792,8 +799,8 @@ - (void)stopUpdateSlider:(UISlider*)slider { } - (void)sliderAction:(OBSlider*)slider { - float newStep = roundf(slider.value / [self.detailItem[@"step"] intValue]); - float newValue = newStep * [self.detailItem[@"step"] intValue]; + float newStep = roundf(slider.value / [self.detailItem[@"step"] floatValue]); + float newValue = newStep * [self.detailItem[@"step"] floatValue]; if (!FLOAT_EQUAL_ZERO(newValue - storeSliderValue)) { storeSliderValue = newValue; UILabel *sliderLabel = [[slider superview] viewWithTag:SETTINGS_CELL_SLIDER_LABEL]; From 99bc692bdd318a8d57225ccf8b67dd36cedc601b Mon Sep 17 00:00:00 2001 From: wutschel Date: Sat, 1 Nov 2025 18:45:04 +0100 Subject: [PATCH 2/9] Show the option label and not the value --- XBMC Remote/SettingsValuesViewController.m | 38 ++++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index 296a81d09..6464a5d79 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -536,22 +536,23 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP case SettingTypeDefault: case SettingTypeMultiselect: if (self.detailItem[@"value"] != nil) { + NSString *delimiter; + NSArray *settingsArray; if ([self.detailItem[@"value"] isKindOfClass:[NSArray class]]) { - NSString *delimiter = self.detailItem[@"delimiter"]; - if (delimiter == nil) { - delimiter = @", "; - } - else { - delimiter = [NSString stringWithFormat:@"%@ ", delimiter]; - } - NSArray *settingsArray = self.detailItem[@"value"]; + delimiter = self.detailItem[@"delimiter"]; + delimiter = delimiter ? [NSString stringWithFormat:@"%@ ", delimiter] : @", "; + NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:nil ascending:YES]; + settingsArray = self.detailItem[@"value"]; settingsArray = [settingsArray sortedArrayUsingDescriptors:@[descriptor]]; - cellLabel.text = [settingsArray componentsJoinedByString:delimiter]; } else { - cellLabel.text = [NSString stringWithFormat:@"%@", self.detailItem[@"value"]]; + delimiter = @""; + settingsArray = @[self.detailItem[@"value"]]; } + settingsArray = [self convertValueListToLabelList:settingsArray]; + cellLabel.text = [settingsArray componentsJoinedByString:delimiter]; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cellLabel.text = cellLabel.text.length ? cellLabel.text : descriptionString; @@ -599,6 +600,23 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP } } +- (NSArray*)convertValueListToLabelList:(NSArray*)valueList { + NSArray *optionList = self.detailItem[@"definition"][@"options"]; + if (!optionList) { + return valueList; + } + NSMutableArray *labelList = [[NSMutableArray alloc] initWithCapacity:valueList.count]; + for (id value in valueList) { + for (id option in optionList) { + if (([value isKindOfClass:[NSNumber class]] && [value intValue] == [option[@"value"] intValue]) || + ([value isKindOfClass:[NSString class]] && [value isEqualToString:option[@"value"]])) { + [labelList addObject:option[@"label"]]; + } + } + } + return labelList; +} + - (void)setAutomaticLabelHeight:(UILabel*)label { CGRect frame = label.frame; frame.size.height = [Utilities getSizeOfLabel:label].height; From 6f5594ed54f2073f5d1d5ee83bfcf3dc52259faf Mon Sep 17 00:00:00 2001 From: wutschel Date: Sun, 2 Nov 2025 08:26:58 +0100 Subject: [PATCH 3/9] Fix formatting for custom button label Only show ": " in case the second string is neither nil nor "". --- XBMC Remote/SettingsValuesViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index 6464a5d79..f5038e5a0 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -242,7 +242,7 @@ - (NSString*)getActionButtonTitle { NSString *stringFormat = @": %i"; switch (xbmcSetting) { case SettingTypeList: - subTitle = [NSString stringWithFormat:@": %@", settingOptions[longPressRow.row][@"label"]]; + subTitle = [NSString stringWithFormat:@"%@", settingOptions[longPressRow.row][@"label"]]; break; case SettingTypeSlider: @@ -256,7 +256,7 @@ - (NSString*)getActionButtonTitle { default: break; } - return [NSString stringWithFormat:@"%@%@", self.detailItem[@"label"], subTitle]; + return [NSString stringWithFormat:@"%@%@%@", self.detailItem[@"label"], subTitle.length ? @": " : @"", subTitle ?: @""]; } - (void)addActionButton:(UIAlertController*)alertCtrl { From d3cad0e39b48006ee477073287a18d5f596d107b Mon Sep 17 00:00:00 2001 From: wutschel Date: Sun, 2 Nov 2025 09:42:22 +0100 Subject: [PATCH 4/9] Prepare to handle float/integer as slider label - Introduce SettingValueType - Introduce helper method to read settingValueType from item - Introduce helper method to read settingOptions from item - Move setting variable defaults to beginning of initWithFrame --- XBMC Remote/SettingsValuesViewController.h | 16 +++++++++ XBMC Remote/SettingsValuesViewController.m | 41 ++++++++++++++++------ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.h b/XBMC Remote/SettingsValuesViewController.h index b338123d2..6108273ed 100644 --- a/XBMC Remote/SettingsValuesViewController.h +++ b/XBMC Remote/SettingsValuesViewController.h @@ -18,6 +18,20 @@ typedef NS_ENUM(NSInteger, SettingType) { SettingTypeUnsupported, }; +typedef NS_ENUM(NSInteger, SettingValueType) { + SettingValueTypeBoolean, + SettingValueTypeInteger, + SettingValueTypeNumber, + SettingValueTypeString, + SettingValueTypeAction, + SettingValueTypeList, + SettingValueTypePath, + SettingValueTypeAddon, + SettingValueTypeDate, + SettingValueTypeTime, + SettingValueTypeUnknown, +}; + @interface SettingsValuesViewController : UIViewController { CGFloat cellHeight; NSMutableArray *settingOptions; @@ -33,6 +47,8 @@ typedef NS_ENUM(NSInteger, SettingType) { UILabel *scrubbingRate; UILabel *footerDescription; BOOL fromItself; + SettingValueType settingValueType; + NSDictionary *valueTypeLookup; } - (id)initWithFrame:(CGRect)frame withItem:(id)item; diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index f5038e5a0..bd5254620 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -40,8 +40,22 @@ @implementation SettingsValuesViewController - (id)initWithFrame:(CGRect)frame withItem:(id)item { if (self = [super init]) { - + self.detailItem = item; self.view.frame = frame; + cellHeight = CELL_HEIGHT_DEFAULT; + + valueTypeLookup = @{ + @"boolean": @(SettingValueTypeBoolean), + @"integer": @(SettingValueTypeInteger), + @"number": @(SettingValueTypeNumber), + @"string": @(SettingValueTypeString), + @"action": @(SettingValueTypeAction), + @"list": @(SettingValueTypeList), + @"path": @(SettingValueTypePath), + @"addon": @(SettingValueTypeAddon), + @"date": @(SettingValueTypeDate), + @"time": @(SettingValueTypeTime), + }; UIImageView *imageBackground = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"appViewBackground"]]; imageBackground.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; @@ -53,20 +67,12 @@ - (id)initWithFrame:(CGRect)frame withItem:(id)item { activityIndicator.center = CGPointMake(frame.size.width / 2, frame.size.height / 2); activityIndicator.hidesWhenStopped = YES; [self.view addSubview:activityIndicator]; - - self.detailItem = item; - - cellHeight = CELL_HEIGHT_DEFAULT; - - settingOptions = self.detailItem[@"options"]; - if (![settingOptions isKindOfClass:[NSArray class]]) { - settingOptions = nil; - } itemControls = self.detailItem[@"control"]; + settingOptions = [self readSettingOptions]; + settingValueType = [self readSettingValueType]; xbmcSetting = SettingTypeDefault; - if ([itemControls[@"format"] isEqualToString:@"boolean"]) { xbmcSetting = SettingTypeSwitch; } @@ -377,6 +383,19 @@ - (NSString*)getStringFormatFromItem:(id)item defaultFormat:(NSString*)defaultFo return defaultFormat; } +- (int)readSettingValueType { + id valueType = valueTypeLookup[self.detailItem[@"type"]]; + return valueType ? [valueType intValue] : SettingValueTypeUnknown; +} + +- (NSMutableArray*)readSettingOptions { + NSArray *options = self.detailItem[@"options"]; + if (![options isKindOfClass:[NSArray class]]) { + return nil; + } + return [options mutableCopy]; +} + #pragma mark - Table view data source - (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath { From 39c9e6fa5239450afdf6bdc447633f65fa407568 Mon Sep 17 00:00:00 2001 From: wutschel Date: Sun, 2 Nov 2025 08:33:02 +0100 Subject: [PATCH 5/9] Make slider label consider settingValueType Introduce getStringForSliderItem which can process both float and integer format. Still uses special handling Kodi < 18.0 and >= 18.0. --- XBMC Remote/SettingsValuesViewController.m | 43 +++++++++++++--------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index bd5254620..600f53736 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -98,11 +98,11 @@ - (id)initWithFrame:(CGRect)frame withItem:(id)item { } else if ([itemControls[@"type"] isEqualToString:@"spinner"] && settingOptions == nil) { xbmcSetting = SettingTypeSlider; - storeSliderValue = [self.detailItem[@"value"] intValue]; + storeSliderValue = [self.detailItem[@"value"] floatValue]; } else if ([itemControls[@"type"] isEqualToString:@"slider"] && settingOptions == nil) { xbmcSetting = SettingTypeSlider; - storeSliderValue = [self.detailItem[@"value"] intValue]; + storeSliderValue = [self.detailItem[@"value"] floatValue]; if ([itemControls[@"format"] isEqualToString:@"percentage"] && ![self.detailItem[@"maximum"] intValue]) { self.detailItem[@"maximum"] = @100; } @@ -112,7 +112,7 @@ - (id)initWithFrame:(CGRect)frame withItem:(id)item { } else if ([itemControls[@"type"] isEqualToString:@"list"] && settingOptions == nil) { xbmcSetting = SettingTypeSlider; - storeSliderValue = [self.detailItem[@"value"] intValue]; + storeSliderValue = [self.detailItem[@"value"] floatValue]; } else { self.navigationItem.title = self.detailItem[@"label"]; @@ -245,15 +245,13 @@ - (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer { - (NSString*)getActionButtonTitle { NSString *subTitle = @""; - NSString *stringFormat = @": %i"; switch (xbmcSetting) { case SettingTypeList: subTitle = [NSString stringWithFormat:@"%@", settingOptions[longPressRow.row][@"label"]]; break; case SettingTypeSlider: - stringFormat = [self getStringFormatFromItem:itemControls defaultFormat:stringFormat]; - subTitle = [NSString stringWithFormat:stringFormat, (int)storeSliderValue]; + subTitle = [self getStringForSliderItem:itemControls value:storeSliderValue]; break; case SettingTypeUnsupported: @@ -373,16 +371,33 @@ - (void)setSettingValue:(id)value sender:(id)sender { #pragma mark - Helper -- (NSString*)getStringFormatFromItem:(id)item defaultFormat:(NSString*)defaultFormat { +- (NSString*)getFormatString:(NSString*)format { + // Default format does not provide any units + NSString *defaultFormat = settingValueType == SettingValueTypeNumber ? @"%.2f" : @"%i"; + // Workaround!! Before Kodi 18.x an older format ("%i ms") was used. The new format ("{0:d} ms") needs // an updated parser. Until this is implemented just display the value itself, without the unit. - NSString *format = item[@"formatlabel"]; if (format.length > 0 && AppDelegate.instance.serverVersion < 18) { return format; } return defaultFormat; } +- (NSString*)getStringForSliderItem:(id)item value:(float)value { + NSString *format = [self getFormatString:item[@"formatlabel"]]; + NSString *stringResult; + switch (settingValueType) { + case SettingValueTypeNumber: + stringResult = [NSString stringWithFormat:format, value]; + break; + case SettingValueTypeInteger: + default: + stringResult = [NSString stringWithFormat:format, (int)value]; + break; + } + return stringResult; +} + - (int)readSettingValueType { id valueType = valueTypeLookup[self.detailItem[@"type"]]; return valueType ? [valueType intValue] : SettingValueTypeUnknown; @@ -432,7 +447,6 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP onoff.hidden = YES; textInputField.hidden = YES; - NSString *stringFormat = @"%i"; NSString *descriptionString = [NSString stringWithFormat:@"%@", self.detailItem[@"genre"]]; descriptionString = [descriptionString stringByReplacingOccurrencesOfString:@"[CR]" withString:@"\n"]; descriptionString = [Utilities stripBBandHTML:descriptionString]; @@ -502,8 +516,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:descriptionLabel]; - stringFormat = [self getStringFormatFromItem:itemControls defaultFormat:stringFormat]; - sliderLabel.text = [NSString stringWithFormat:stringFormat, [self.detailItem[@"value"] intValue]]; + sliderLabel.text = [self getStringForSliderItem:itemControls value:[self.detailItem[@"value"] floatValue]]; sliderLabel.frame = CGRectMake(CGRectGetMinX(sliderLabel.frame), CGRectGetMaxY(descriptionLabel.frame) + 2 * PADDING_VERTICAL, CGRectGetWidth(sliderLabel.frame), @@ -512,7 +525,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP slider.minimumValue = [self.detailItem[@"minimum"] intValue]; slider.maximumValue = [self.detailItem[@"maximum"] intValue]; - slider.value = [self.detailItem[@"value"] intValue]; + slider.value = [self.detailItem[@"value"] floatValue]; slider.frame = CGRectMake(CGRectGetMinX(slider.frame), CGRectGetMaxY(sliderLabel.frame) + PADDING_VERTICAL, CGRectGetWidth(slider.frame), @@ -841,11 +854,7 @@ - (void)sliderAction:(OBSlider*)slider { if (!FLOAT_EQUAL_ZERO(newValue - storeSliderValue)) { storeSliderValue = newValue; UILabel *sliderLabel = [[slider superview] viewWithTag:SETTINGS_CELL_SLIDER_LABEL]; - if (sliderLabel) { - NSString *stringFormat = @"%i"; - stringFormat = [self getStringFormatFromItem:itemControls defaultFormat:stringFormat]; - sliderLabel.text = [NSString stringWithFormat:stringFormat, (int)storeSliderValue]; - } + sliderLabel.text = [self getStringForSliderItem:itemControls value:storeSliderValue]; } scrubbingRate.text = LOCALIZED_STR(([NSString stringWithFormat:@"Scrubbing %@", @(slider.scrubbingSpeed)])); } From 60924ab3c3f77a06486ea0a85c0ce8d5292ce5b9 Mon Sep 17 00:00:00 2001 From: wutschel Date: Sun, 2 Nov 2025 11:19:30 +0100 Subject: [PATCH 6/9] Refactor settings processing Identify the app's internal setting type by delivered "type", "format" and "options". Showing navbar title depends on setting type and is handled in layoutCell. --- XBMC Remote/SettingsValuesViewController.m | 82 ++++++++++++---------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index 600f53736..29a9691e5 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -73,54 +73,55 @@ - (id)initWithFrame:(CGRect)frame withItem:(id)item { settingValueType = [self readSettingValueType]; xbmcSetting = SettingTypeDefault; - if ([itemControls[@"format"] isEqualToString:@"boolean"]) { + if ([itemControls[@"type"] isEqualToString:@"toggle"]) { xbmcSetting = SettingTypeSwitch; } - else if ([itemControls[@"multiselect"] boolValue] && ![settingOptions isKindOfClass:[NSArray class]]) { - xbmcSetting = SettingTypeMultiselect; - self.detailItem[@"value"] = [self.detailItem[@"value"] mutableCopy]; - } - else if ([itemControls[@"format"] isEqualToString:@"addon"]) { - xbmcSetting = SettingTypeList; - self.navigationItem.title = self.detailItem[@"label"]; - settingOptions = [NSMutableArray new]; - [self retrieveXBMCData:@"Addons.GetAddons" - parameters:[NSDictionary dictionaryWithObjectsAndKeys: - self.detailItem[@"addontype"], @"type", - @YES, @"enabled", - @[@"name"], @"properties", - nil] - itemKey:@"addons"]; - } - else if ([itemControls[@"format"] isEqualToString:@"action"] || [itemControls[@"format"] isEqualToString:@"path"]) { - self.navigationItem.title = self.detailItem[@"label"]; - xbmcSetting = SettingTypeUnsupported; - } - else if ([itemControls[@"type"] isEqualToString:@"spinner"] && settingOptions == nil) { - xbmcSetting = SettingTypeSlider; - storeSliderValue = [self.detailItem[@"value"] floatValue]; + else if ([itemControls[@"type"] isEqualToString:@"button"]) { + if ([itemControls[@"format"] isEqualToString:@"addon"]) { + xbmcSetting = SettingTypeList; + settingOptions = [NSMutableArray new]; + [self retrieveXBMCData:@"Addons.GetAddons" + parameters:[NSDictionary dictionaryWithObjectsAndKeys: + self.detailItem[@"addontype"], @"type", + @YES, @"enabled", + @[@"name"], @"properties", + nil] + itemKey:@"addons"]; + } + else if ([itemControls[@"format"] isEqualToString:@"action"] || [itemControls[@"format"] isEqualToString:@"path"]) { + xbmcSetting = SettingTypeUnsupported; + } } - else if ([itemControls[@"type"] isEqualToString:@"slider"] && settingOptions == nil) { - xbmcSetting = SettingTypeSlider; - storeSliderValue = [self.detailItem[@"value"] floatValue]; - if ([itemControls[@"format"] isEqualToString:@"percentage"] && ![self.detailItem[@"maximum"] intValue]) { - self.detailItem[@"maximum"] = @100; + else if ([itemControls[@"type"] isEqualToString:@"spinner"] || [itemControls[@"type"] isEqualToString:@"slider"]) { + if (!settingOptions) { + xbmcSetting = SettingTypeSlider; + storeSliderValue = [self.detailItem[@"value"] floatValue]; + if ([itemControls[@"format"] isEqualToString:@"percentage"] && ![self.detailItem[@"maximum"] intValue]) { + self.detailItem[@"maximum"] = @100; + } + } + else if (settingOptions.count > 0) { + xbmcSetting = SettingTypeList; } } else if ([itemControls[@"type"] isEqualToString:@"edit"]) { xbmcSetting = SettingTypeInput; } - else if ([itemControls[@"type"] isEqualToString:@"list"] && settingOptions == nil) { - xbmcSetting = SettingTypeSlider; - storeSliderValue = [self.detailItem[@"value"] floatValue]; + else if ([itemControls[@"type"] isEqualToString:@"list"]) { + if ([itemControls[@"multiselect"] boolValue] && !settingOptions) { + xbmcSetting = SettingTypeMultiselect; + self.detailItem[@"value"] = [self.detailItem[@"value"] mutableCopy]; + } + else if (!settingOptions) { + xbmcSetting = SettingTypeSlider; + storeSliderValue = [self.detailItem[@"value"] floatValue]; + } + else if (settingOptions.count > 0) { + xbmcSetting = SettingTypeList; + } } else { - self.navigationItem.title = self.detailItem[@"label"]; - if ([settingOptions isKindOfClass:[NSArray class]]) { - if (settingOptions.count > 0) { - xbmcSetting = SettingTypeList; - } - } + NSLog(@"Unexpected setting controls type / format: '%@' / '%@'", itemControls[@"type"], itemControls[@"format"]); } NSString *footerMessage; @@ -481,6 +482,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP break; case SettingTypeList: + self.navigationItem.title = self.detailItem[@"label"]; cellLabel.text = [NSString stringWithFormat:@"%@", settingOptions[indexPath.row][@"label"]]; if ([self.detailItem[@"value"] isKindOfClass:[NSArray class]]) { if ([self.detailItem[@"value"] containsObject:settingOptions[indexPath.row][@"value"]]) { @@ -565,8 +567,8 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP cellHeight = CGRectGetMaxY(textInputField.frame) + PADDING_VERTICAL; break; - case SettingTypeDefault: case SettingTypeMultiselect: + self.navigationItem.title = self.detailItem[@"label"]; if (self.detailItem[@"value"] != nil) { NSString *delimiter; NSArray *settingsArray; @@ -600,6 +602,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP break; case SettingTypeUnsupported: + self.navigationItem.title = self.detailItem[@"label"]; cell.selectionStyle = UITableViewCellSelectionStyleNone; cellLabel.text = descriptionString; @@ -613,6 +616,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP cellHeight = CGRectGetMaxY(cellLabel.frame) + PADDING_VERTICAL; break; + case SettingTypeDefault: default: if (self.detailItem[@"value"] != nil) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; From 6ed47a42c4a62e93d9c61e7a4956822369810c47 Mon Sep 17 00:00:00 2001 From: wutschel Date: Sun, 2 Nov 2025 13:09:18 +0100 Subject: [PATCH 7/9] Show units for fmt-like string format Extract the unit from fmt-like string formats. This improves the user experience, but avoids a full fmt integration. --- XBMC Remote/SettingsValuesViewController.m | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index 29a9691e5..81bc1b817 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -376,12 +376,25 @@ - (NSString*)getFormatString:(NSString*)format { // Default format does not provide any units NSString *defaultFormat = settingValueType == SettingValueTypeNumber ? @"%.2f" : @"%i"; - // Workaround!! Before Kodi 18.x an older format ("%i ms") was used. The new format ("{0:d} ms") needs - // an updated parser. Until this is implemented just display the value itself, without the unit. - if (format.length > 0 && AppDelegate.instance.serverVersion < 18) { - return format; + // Identify fmt-like format string + if (format.length && [format rangeOfString:@"{"].location != NSNotFound && [format rangeOfString:@"}"].location != NSNotFound) { + // Gather range for unit which is added after last "}" + NSRange range; + range.location = [format rangeOfString:@"}" options:NSBackwardsSearch].location + 1; + range.length = format.length - range.location; + + // Extract unit and make percent character is formatted correctly + NSString *unit = [format substringWithRange:range]; + unit = [unit stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; + + // Build std format string, appending the fmt format string's unit + format = [defaultFormat stringByAppendingString:unit]; + } + // Fallback to default in case no format is defined or we missed to identify fmt-style format (which is used for Kodi 18 and later) + else if (!format.length || AppDelegate.instance.serverVersion >= 18) { + format = defaultFormat; } - return defaultFormat; + return format; } - (NSString*)getStringForSliderItem:(id)item value:(float)value { From 8d2e575d0d27dff90871cd02c8d4f0c20feb1f78 Mon Sep 17 00:00:00 2001 From: wutschel Date: Sun, 2 Nov 2025 21:44:07 +0100 Subject: [PATCH 8/9] Used fixed cell layout to make sure cellHeight is correclty calculated Setting cells have different height, depending on their content and mode. To calculate this cell height correctly before heightForRowAtIndexPath is called, the whole layout needs to use the final size, which is fixed and represented by cellWidth. To keep this width, do not use autoresizing. For better centralization of the code, change dimensions only in configureCell and create all UI elements only using new. --- XBMC Remote/SettingsValuesViewController.m | 78 +++++++++------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index 81bc1b817..4e405cf00 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -443,10 +443,12 @@ - (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger) return numRows; } -- (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath { +- (void)configureCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath { cell.backgroundColor = [Utilities getSystemGray6]; cell.tintColor = [Utilities getSystemBlue]; cell.accessoryType = UITableViewCellAccessoryNone; + + CGFloat cellWidth = IS_IPHONE ? GET_MAINSCREEN_WIDTH : STACKSCROLL_WIDTH; UILabel *cellLabel = (UILabel*)[cell viewWithTag:SETTINGS_CELL_LABEL]; UILabel *descriptionLabel = (UILabel*)[cell viewWithTag:SETTINGS_CELL_DESCRIPTION]; @@ -474,12 +476,12 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP cellLabel.numberOfLines = 0; cellLabel.frame = CGRectMake(PADDING_HORIZONTAL, PADDING_VERTICAL, - cell.bounds.size.width - onoff.frame.size.width - 3 * PADDING_HORIZONTAL, + cellWidth - onoff.frame.size.width - 3 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:cellLabel]; onoff.on = [self.detailItem[@"value"] boolValue]; - onoff.frame = CGRectMake(cell.bounds.size.width - onoff.frame.size.width - PADDING_HORIZONTAL, + onoff.frame = CGRectMake(cellWidth - onoff.frame.size.width - PADDING_HORIZONTAL, (CGRectGetHeight(cellLabel.frame) - CGRectGetHeight(onoff.frame)) / 2 + CGRectGetMinY(cellLabel.frame), CGRectGetWidth(onoff.frame), CGRectGetHeight(onoff.frame)); @@ -487,7 +489,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP descriptionLabel.text = descriptionString; descriptionLabel.frame = CGRectMake(PADDING_HORIZONTAL, CGRectGetMaxY(cellLabel.frame) + PADDING_VERTICAL, - cell.bounds.size.width - 2 * PADDING_HORIZONTAL, + cellWidth - 2 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:descriptionLabel]; @@ -505,6 +507,11 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP else if ([settingOptions[indexPath.row][@"value"] isEqual:self.detailItem[@"value"]]) { cell.accessoryType = UITableViewCellAccessoryCheckmark; } + cellLabel.frame = CGRectMake(PADDING_HORIZONTAL, + PADDING_VERTICAL, + cellWidth - 2 * PADDING_HORIZONTAL, + LABEL_HEIGHT_DEFAULT); + cellHeight = CELL_HEIGHT_DEFAULT; break; case SettingTypeSlider: @@ -518,7 +525,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP cellLabel.numberOfLines = 0; cellLabel.frame = CGRectMake(PADDING_HORIZONTAL, PADDING_VERTICAL, - cell.bounds.size.width - 2 * PADDING_HORIZONTAL, + cellWidth - 2 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:cellLabel]; @@ -527,24 +534,24 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP descriptionLabel.numberOfLines = 0; descriptionLabel.frame = CGRectMake(PADDING_HORIZONTAL, CGRectGetMaxY(cellLabel.frame) + PADDING_VERTICAL, - cell.bounds.size.width - 2 * PADDING_HORIZONTAL, + cellWidth - 2 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:descriptionLabel]; sliderLabel.text = [self getStringForSliderItem:itemControls value:[self.detailItem[@"value"] floatValue]]; - sliderLabel.frame = CGRectMake(CGRectGetMinX(sliderLabel.frame), + sliderLabel.frame = CGRectMake(SLIDER_PADDING, CGRectGetMaxY(descriptionLabel.frame) + 2 * PADDING_VERTICAL, - CGRectGetWidth(sliderLabel.frame), + cellWidth - 2 * SLIDER_PADDING, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:sliderLabel]; slider.minimumValue = [self.detailItem[@"minimum"] intValue]; slider.maximumValue = [self.detailItem[@"maximum"] intValue]; slider.value = [self.detailItem[@"value"] floatValue]; - slider.frame = CGRectMake(CGRectGetMinX(slider.frame), + slider.frame = CGRectMake(SLIDER_PADDING, CGRectGetMaxY(sliderLabel.frame) + PADDING_VERTICAL, - CGRectGetWidth(slider.frame), - CGRectGetHeight(slider.frame)); + cellWidth - 2 * SLIDER_PADDING, + SLIDER_HEIGHT); cellHeight = CGRectGetMaxY(slider.frame) + 2 * PADDING_VERTICAL; break; @@ -558,7 +565,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP cellLabel.numberOfLines = 0; cellLabel.frame = CGRectMake(PADDING_HORIZONTAL, PADDING_VERTICAL, - cell.bounds.size.width - 2 * PADDING_HORIZONTAL, + cellWidth - 2 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:cellLabel]; @@ -567,15 +574,15 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP descriptionLabel.numberOfLines = 0; descriptionLabel.frame = CGRectMake(PADDING_HORIZONTAL, CGRectGetMaxY(cellLabel.frame) + PADDING_VERTICAL, - cell.bounds.size.width - 2 * PADDING_HORIZONTAL, + cellWidth - 2 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:descriptionLabel]; textInputField.text = [NSString stringWithFormat:@"%@", self.detailItem[@"value"]]; - textInputField.frame = CGRectMake(CGRectGetMinX(textInputField.frame), + textInputField.frame = CGRectMake(SLIDER_PADDING, CGRectGetMaxY(descriptionLabel.frame) + PADDING_VERTICAL, - CGRectGetWidth(textInputField.frame), - CGRectGetHeight(textInputField.frame)); + cellWidth - 2 * SLIDER_PADDING, + TEXTFIELD_HEIGHT); cellHeight = CGRectGetMaxY(textInputField.frame) + PADDING_VERTICAL; break; @@ -606,7 +613,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP cellLabel.numberOfLines = 0; cellLabel.frame = CGRectMake(PADDING_HORIZONTAL, PADDING_VERTICAL, - cell.bounds.size.width - 2 * PADDING_HORIZONTAL, + cellWidth - 2 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:cellLabel]; @@ -622,7 +629,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP cellLabel.numberOfLines = 0; cellLabel.frame = CGRectMake(PADDING_HORIZONTAL, PADDING_VERTICAL, - cell.bounds.size.width - 2 * PADDING_HORIZONTAL, + cellWidth - 2 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:cellLabel]; @@ -639,7 +646,7 @@ - (void)layoutCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexP cellLabel.numberOfLines = 0; cellLabel.frame = CGRectMake(PADDING_HORIZONTAL, PADDING_VERTICAL, - cell.bounds.size.width - 2 * PADDING_HORIZONTAL, + cellWidth - 2 * PADDING_HORIZONTAL, LABEL_HEIGHT_DEFAULT); [self setAutomaticLabelHeight:cellLabel]; @@ -677,29 +684,21 @@ - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSI UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:tableCellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tableCellIdentifier]; - UILabel *cellLabel = [[UILabel alloc] initWithFrame:CGRectMake(PADDING_HORIZONTAL, - (CELL_HEIGHT_DEFAULT - LABEL_HEIGHT_DEFAULT) / 2, - cell.frame.size.width - 2 * PADDING_HORIZONTAL, - LABEL_HEIGHT_DEFAULT)]; + UILabel *cellLabel = [UILabel new]; cellLabel.tag = SETTINGS_CELL_LABEL; cellLabel.font = [UIFont systemFontOfSize:16]; cellLabel.adjustsFontSizeToFitWidth = YES; cellLabel.minimumScaleFactor = FONT_SCALING_MIN; cellLabel.textColor = [Utilities get1stLabelColor]; cellLabel.highlightedTextColor = [Utilities get1stLabelColor]; - cellLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [cell.contentView addSubview:cellLabel]; UISwitch *onoff = [UISwitch new]; onoff.tag = SETTINGS_CELL_ONOFF_SWITCH; - onoff.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; [onoff addTarget:self action:@selector(toggleSwitch:) forControlEvents:UIControlEventValueChanged]; [cell.contentView addSubview:onoff]; - UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(PADDING_HORIZONTAL, - 0, - cell.frame.size.width - 2 * PADDING_HORIZONTAL, - LABEL_HEIGHT_DEFAULT)]; + UILabel *descriptionLabel = [UILabel new]; descriptionLabel.tag = SETTINGS_CELL_DESCRIPTION; descriptionLabel.font = [UIFont systemFontOfSize:14]; descriptionLabel.adjustsFontSizeToFitWidth = YES; @@ -707,18 +706,13 @@ - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSI descriptionLabel.numberOfLines = 0; descriptionLabel.textColor = [Utilities get2ndLabelColor]; descriptionLabel.highlightedTextColor = [Utilities get2ndLabelColor]; - descriptionLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [cell.contentView addSubview:descriptionLabel]; - OBSlider *slider = [[OBSlider alloc] initWithFrame:CGRectMake(SLIDER_PADDING, - 0, - cell.frame.size.width - 2 * SLIDER_PADDING, - SLIDER_HEIGHT)]; + OBSlider *slider = [OBSlider new]; slider.tag = SETTINGS_CELL_SLIDER; slider.backgroundColor = UIColor.clearColor; slider.minimumTrackTintColor = KODI_BLUE_COLOR; slider.continuous = YES; - slider.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [slider addTarget:self action:@selector(sliderAction:) forControlEvents:UIControlEventValueChanged]; [slider addTarget:self action:@selector(stopUpdateSlider:) forControlEvents:UIControlEventEditingDidEnd]; [slider addTarget:self action:@selector(stopUpdateSlider:) forControlEvents:UIControlEventTouchCancel]; @@ -727,10 +721,7 @@ - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSI [slider addTarget:self action:@selector(startUpdateSlider:) forControlEvents:UIControlEventTouchDown]; [cell.contentView addSubview:slider]; - UILabel *sliderLabel = [[UILabel alloc] initWithFrame:CGRectMake(SLIDER_PADDING, - 0, - cell.frame.size.width - 2 * SLIDER_PADDING, - LABEL_HEIGHT_DEFAULT)]; + UILabel *sliderLabel = [UILabel new]; sliderLabel.tag = SETTINGS_CELL_SLIDER_LABEL; sliderLabel.font = [UIFont systemFontOfSize:14]; sliderLabel.adjustsFontSizeToFitWidth = YES; @@ -738,13 +729,9 @@ - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSI sliderLabel.textAlignment = NSTextAlignmentCenter; sliderLabel.textColor = [Utilities get2ndLabelColor]; sliderLabel.highlightedTextColor = [Utilities get2ndLabelColor]; - sliderLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [cell.contentView addSubview:sliderLabel]; - UITextField *textInputField = [[UITextField alloc] initWithFrame:CGRectMake(SLIDER_PADDING, - 0, - cell.frame.size.width - 2 * SLIDER_PADDING, - TEXTFIELD_HEIGHT)]; + UITextField *textInputField = [UITextField new]; textInputField.tag = SETTINGS_CELL_TEXTFIELD; textInputField.borderStyle = UITextBorderStyleRoundedRect; textInputField.textAlignment = NSTextAlignmentCenter; @@ -756,10 +743,9 @@ - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSI textInputField.clearButtonMode = UITextFieldViewModeWhileEditing; textInputField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; textInputField.delegate = self; - textInputField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [cell.contentView addSubview:textInputField]; } - [self layoutCell:cell forRowAtIndexPath:indexPath]; + [self configureCell:cell forRowAtIndexPath:indexPath]; return cell; } From 7d31af7498f231f2b725f66c77472b30900bf571 Mon Sep 17 00:00:00 2001 From: wutschel Date: Mon, 10 Nov 2025 22:19:18 +0100 Subject: [PATCH 9/9] fixup: braces --- XBMC Remote/SettingsValuesViewController.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/XBMC Remote/SettingsValuesViewController.m b/XBMC Remote/SettingsValuesViewController.m index 4e405cf00..2d1cb593d 100644 --- a/XBMC Remote/SettingsValuesViewController.m +++ b/XBMC Remote/SettingsValuesViewController.m @@ -377,10 +377,12 @@ - (NSString*)getFormatString:(NSString*)format { NSString *defaultFormat = settingValueType == SettingValueTypeNumber ? @"%.2f" : @"%i"; // Identify fmt-like format string - if (format.length && [format rangeOfString:@"{"].location != NSNotFound && [format rangeOfString:@"}"].location != NSNotFound) { + NSInteger openBraceLoc = [format rangeOfString:@"{"].location; + NSInteger closeBraceLoc = [format rangeOfString:@"}" options:NSBackwardsSearch].location; + if (format.length && openBraceLoc != NSNotFound && closeBraceLoc != NSNotFound && openBraceLoc < closeBraceLoc) { // Gather range for unit which is added after last "}" NSRange range; - range.location = [format rangeOfString:@"}" options:NSBackwardsSearch].location + 1; + range.location = closeBraceLoc + 1; range.length = format.length - range.location; // Extract unit and make percent character is formatted correctly