tweak storyboard

This commit is contained in:
TrueZhuangjia
2025-03-29 22:03:18 +08:00
committed by TrueZhuanjia
parent cc285d5488
commit 27bb8fd737
13 changed files with 194 additions and 33 deletions
+1 -1
View File
@@ -985,7 +985,7 @@ ICAgICAgICAgICAgICA
</constraints>
<items>
<navigationItem title="Controller &amp; Widget Profiles" id="Q2V-XF-4jq">
<barButtonItem key="leftBarButtonItem" title="Export All Pofiles" width="90" style="plain" id="Bhn-IO-Wlt" userLabel="Export Data">
<barButtonItem key="leftBarButtonItem" title="Export All" width="90" style="plain" id="Bhn-IO-Wlt" userLabel="Export Data">
<connections>
<action selector="exportDataTapped:" destination="Atv-Nj-0qv" id="pwG-sO-9he"/>
</connections>
+5 -4
View File
@@ -13,7 +13,7 @@
<objects>
<viewController id="dgh-JZ-Q7z" customClass="MainFrameViewController" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="Rtu-AT-Alw" customClass="AppCollectionView">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="804"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.3333333432674408" green="0.3333333432674408" blue="0.3333333432674408" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="15" minimumInteritemSpacing="15" id="YcZ-cR-3tK">
@@ -83,7 +83,7 @@
<viewControllerLayoutGuide type="bottom" id="HaZ-6S-ZpN"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="iNk-qF-gIr" customClass="UIScrollView">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<rect key="frame" x="0.0" y="0.0" width="499" height="3500"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Resolution " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8zy-ri-Dqc">
@@ -676,6 +676,7 @@ ICAgICAgICAgICAgIA
<objects>
<navigationController id="ftZ-kC-fxI" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" translucent="NO" id="0ZA-Ec-QgD">
<rect key="frame" x="0.0" y="48" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -749,7 +750,7 @@ ICAgICAgICAgICAgIA
<viewControllerLayoutGuide type="bottom" id="TWA-bF-hnk"/>
</layoutGuides>
<view key="view" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="Mwx-9q-yJJ">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<rect key="frame" x="0.0" y="0.0" width="650" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Jnu-63-bcf" customClass="ToolBarContainerView">
@@ -988,7 +989,7 @@ ICAgICAgICAgICAgIA
</constraints>
<items>
<navigationItem title="Controller &amp; Widget Profiles" id="5pN-Sn-SCN">
<barButtonItem key="leftBarButtonItem" title="Exit" id="D9X-7p-9oc">
<barButtonItem key="leftBarButtonItem" title="Export All" id="D9X-7p-9oc">
<connections>
<action selector="exportDataTapped:" destination="0K8-kQ-xlN" id="Sam-Ct-kqC"/>
</connections>
@@ -37,7 +37,9 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (NSInteger) getIndexOfSelectedProfile;
- (NSMutableArray *) getEncodedProfiles;
- (void) importEncodedProfiles:(NSMutableArray* )profilesEncoded;
- (OnScreenButtonState *)unarchiveButtonStateEncoded:(NSData *)data;
#pragma mark - Setters
+28 -3
View File
@@ -130,6 +130,21 @@ static CGRect streamViewBounds;
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (NSMutableArray *) getEncodedProfiles {
NSMutableArray *profiles = [self getAllProfiles];
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles]; // encode each 'profile' object in the array and add them to a new array
return profilesEncoded;
}
- (void) importEncodedProfiles:(NSMutableArray* )profilesEncoded {
NSMutableArray* localEncodedPofiles = [self getEncodedProfiles];
[localEncodedPofiles removeObjectAtIndex:0];
[profilesEncoded addObjectsFromArray:localEncodedPofiles];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
#pragma mark - Globally Accessible Methods
@@ -349,8 +364,18 @@ static CGRect streamViewBounds;
return NO;
}
- (OnScreenButtonState *)unarchiveButtonStateEncoded:(NSData *)data {
OnScreenButtonState* buttonState;
if (@available(iOS 13.0, *)) {
// 在 iOS 13 及以上使用 unarchivedObjectOfClasses
buttonState = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSString class], [OnScreenButtonState class], nil]
fromData:data
error:nil];
} else {
// 在 iOS 12 及以下使用 unarchiveObjectWithData(旧方法)
buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass: [OnScreenButtonState class] fromData:data error:nil];
}
return buttonState;
}
@end
+8 -7
View File
@@ -369,7 +369,7 @@ static float L3_Y;
[_activeCustomOscButtonPositionDict removeAllObjects]; //reset the Dict.
NSLog(@"_activeCustomOscButtonPositionDict update: STARTOVER");
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
OnScreenButtonState *buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
if(!buttonState.isHidden && [validPositionButtonNames containsObject:buttonState.name] && (buttonState.buttonType == LegacyOscButton || [profilesManager getIndexOfSelectedProfile] == 0 ) ){
[_activeCustomOscButtonPositionDict setObject:[NSValue valueWithCGPoint:buttonState.position] forKey:buttonState.name]; // we got a buttonname -> position dict here
@@ -725,8 +725,7 @@ static float L3_Y;
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
OnScreenButtonState *buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if ([buttonState.name isEqualToString:@"dPad"]) {
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
D_PAD_CENTER_X = buttonState.position.x;
@@ -742,7 +741,7 @@ static float L3_Y;
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; // returns the currently selected OSCProfile
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
OnScreenButtonState *buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
if ([buttonState.name isEqualToString:@"leftStickBackground"]) {
LS_CENTER_X = buttonState.position.x;
@@ -767,8 +766,9 @@ static float L3_Y;
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
OnScreenButtonState *buttonStateDecoded = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
OnScreenButtonState *buttonStateDecoded = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
for (CALayer *buttonLayer in self.OSCButtonLayers) { // iterate through each button layer on screen and position and hide/unhide each according to the instructions of its associated 'buttonState'
if ([buttonLayer.name isEqualToString:buttonStateDecoded.name]) {
if ([buttonLayer.name isEqualToString:@"upButton"] == NO &&
@@ -827,7 +827,8 @@ static float L3_Y;
bool defaultProfileSelected = [profilesManager getIndexOfSelectedProfile] == 0;
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
OnScreenButtonState *buttonStateDecoded = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
OnScreenButtonState* buttonStateDecoded = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
for (CALayer *buttonLayer in self.OSCButtonLayers) { // iterate through each button layer on screen
// Here we deal with resizing single layer controllers only
if ([buttonLayer.name isEqualToString:buttonStateDecoded.name]) {
+1 -1
View File
@@ -400,7 +400,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
if(!OnScreenWidgetView.editMode){ // in edit mode, keyboard widget view will be updated within layoutool view controller.
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
OnScreenButtonState* buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if(buttonState.buttonType == CustomOnScreenWidget){
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:buttonState.name buttonLabel:buttonState.alias shape:buttonState.widgetShape]; //reconstruct widgetView
//--------------------------------------------------
+2
View File
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleGetInfoString</key>
<string></string>
<key>CADisableMinimumFrameDuration</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
@@ -893,6 +893,29 @@
}
}
},
"Failed to import profiles" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "布局导入失败"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "布局导入失败"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "布局导入失败"
}
}
}
},
"Failed to quit app. If this app was started by another device, you'll need to quit from that device." : {
"extractionState" : "manual",
"localizations" : {
@@ -1791,6 +1814,29 @@
}
}
},
"Pofiles imported" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "布局已导入"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "布局已导入"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "布局已导入"
}
}
}
},
"Poor connection to PC" : {
"extractionState" : "manual",
"localizations" : {
@@ -96,7 +96,8 @@
// _activeCustomOscButtonPositionDict will be updated every time when the osc profile is reloaded
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
OnScreenButtonState* buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
// OnScreenButtonState* buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if(buttonState.buttonType == CustomOnScreenWidget){
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:buttonState.name buttonLabel:buttonState.alias shape:buttonState.widgetShape]; //reconstruct widgetView
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
@@ -17,6 +17,12 @@ NS_ASSUME_NONNULL_BEGIN
- (void)profileViewRefresh;
typedef NS_ENUM(NSUInteger, FileOperation) {
Import,
Export
};
@property (nonatomic, assign) FileOperation currentFileOperation;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, copy) void (^needToUpdateOscLayoutTVC)(void);
@@ -154,7 +154,6 @@ const double NAV_BAR_HEIGHT = 50;
//[self dismissViewControllerAnimated:YES completion:nil];
//[selfparentLayoutOSCViewController]
[self.tableView reloadData]; // table view will be refreshed by calling reloadData
if (self.needToUpdateOscLayoutTVC) { // tells the presenting view controller to lay out the on screen buttons according to the selected profile's instructions
self.needToUpdateOscLayoutTVC();
}
@@ -168,6 +167,7 @@ const double NAV_BAR_HEIGHT = 50;
- (IBAction) exportDataTapped:(id)sender {
// 创建临时占位文件(仅用于提供文件名)
self.currentFileOperation = Export;
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"profiles.bin"];
[[NSData new] writeToFile:tempPath atomically:YES]; // 空文件
@@ -177,15 +177,38 @@ const double NAV_BAR_HEIGHT = 50;
[self presentViewController:picker animated:YES completion:nil];
}
- (IBAction) importDataTapped:(id)sender {
self.currentFileOperation = Import;
// 2. 创建文件选择器
// UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:supportedTypes inMode:UIDocumentPickerModeImport];
// UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.data"] inMode:UIDocumentPickerModeOpen];
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
documentPicker.delegate = self;
documentPicker.allowsMultipleSelection = NO; // 只允许选择一个文件
[self presentViewController:documentPicker animated:YES completion:nil];
}
#pragma mark - UIDocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
if (urls.count == 0) return;
NSURL *destinationURL = urls.firstObject;
NSArray *profiles = [profilesManager getAllProfiles];
NSURL* url = urls.firstObject;
switch(self.currentFileOperation){
case Export:
[self profilesToFile:url];break;
case Import:
[self fileToProfiles:url];break;
default:break;
}
}
- (void)profilesToFile:(NSURL* )destinationURL{
// NSArray *profiles = [profilesManager getAllProfiles];
// 1. 序列化数据
NSError *error;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profiles requiringSecureCoding:YES error:&error];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[profilesManager getEncodedProfiles] requiringSecureCoding:YES error:&error];
if (!data) {
NSLog(@"序列化失败: %@", error);
return;
@@ -200,6 +223,60 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
}
- (void)fileToProfiles:(NSURL* )sourceURL{
bool restoreFailed = false;
if (![sourceURL startAccessingSecurityScopedResource]) {
restoreFailed = true;
}
NSError* error;
NSData* fileData = [NSData dataWithContentsOfURL:sourceURL options:0 error:&error];// 读取数据
[sourceURL stopAccessingSecurityScopedResource]; // 立即释放权限
if (!fileData) {
restoreFailed = true;
}
else NSLog(@"profile file read: %d", (uint32_t)fileData.length);
// 定义需要解码的类集
NSSet *classes = [NSSet setWithObjects: [NSMutableData class], [NSMutableArray class], nil];
NSMutableArray *profilesEncoded;
// 解码原始的NSArray对象,得到包含编码对象的数组
error = nil;
if (@available(iOS 13.0, *)) {
// 在 iOS 13 及以上使用 unarchivedObjectOfClasses
profilesEncoded = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:fileData error:&error];
} else {
// 在 iOS 12 及以下使用 unarchiveObjectWithData(旧方法)
profilesEncoded = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSMutableArray class] fromData:fileData error:&error];
}
restoreFailed = error != nil;
UIAlertController *restoredAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@""] message: [LocalizationHelper localizedStringForKey:@"Pofiles imported"] preferredStyle:UIAlertControllerStyleAlert];
UIAlertController *failedAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@""] message: [LocalizationHelper localizedStringForKey:@"Failed to import profiles"] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"OK"]
style:UIAlertActionStyleDefault
handler:nil];
if(restoreFailed){
[failedAlertController addAction:okAction];
[self presentViewController:failedAlertController animated:YES completion:nil];
}
else{
[profilesManager importEncodedProfiles:profilesEncoded];
[restoredAlertController addAction:okAction];
[self presentViewController:restoredAlertController animated:YES completion:nil];
}
[self profileViewRefresh];
NSLog(@"profile test: %d", (uint32_t)profilesEncoded.count);
if (error) {
NSLog(@"解码失败: %@", error);
return;
}
}
#pragma mark - TableView DataSource
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+2 -2
View File
@@ -536,13 +536,13 @@
}
},
"Bhn-IO-Wlt.title" : {
"comment" : "Class = \"UIBarButtonItem\"; title = \"Export All Pofiles\"; ObjectID = \"Bhn-IO-Wlt\";",
"comment" : "Class = \"UIBarButtonItem\"; title = \"Export All\"; ObjectID = \"Bhn-IO-Wlt\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Export All Pofiles"
"value" : "Export All"
}
},
"zh-Hans" : {
+8 -8
View File
@@ -890,31 +890,31 @@
}
},
"D9X-7p-9oc.title" : {
"comment" : "Class = \"UIBarButtonItem\"; title = \"Exit\"; ObjectID = \"D9X-7p-9oc\";",
"comment" : "Class = \"UIBarButtonItem\"; title = \"Export All\"; ObjectID = \"D9X-7p-9oc\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Exit"
"value" : "Export All"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "退出"
"state" : "translated",
"value" : "导出所有"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "退出"
"state" : "translated",
"value" : "导出所有"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "退出"
"state" : "translated",
"value" : "导出所有"
}
}
}