Commit 5ea0eb91 authored by yqz's avatar yqz

设置界面 和 其它优化

parent ed1cc5a8
......@@ -2,7 +2,7 @@
// LockSreen.swift
// LockSreen
//
// Created by edy on 2025/5/12.
// Created by edy on 2025/5/19.
//
import WidgetKit
......@@ -115,3 +115,4 @@ struct lockArcShape: Shape {
return path
}
}
......@@ -2,12 +2,13 @@
// LockSreenBundle.swift
// LockSreen
//
// Created by edy on 2025/5/12.
// Created by edy on 2025/5/19.
//
import WidgetKit
import SwiftUI
let kind: String = "LockSreen"
let kind1: String = "LockSreen1"
......@@ -56,3 +57,4 @@ struct LockBothSreen: Widget {
.supportedFamilies([.accessoryRectangular])
}
}
......@@ -11,6 +11,9 @@
0496DEF32D9E3F58005B2834 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04BD915D2D9D68AD00055CEB /* WidgetKit.framework */; };
0496DEF42D9E3F58005B2834 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04BD915F2D9D68AD00055CEB /* SwiftUI.framework */; };
0496DEFF2D9E3F59005B2834 /* widgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0496DEF22D9E3F57005B2834 /* widgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
049F146C2DDB1F64007B5A9B /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04BD915D2D9D68AD00055CEB /* WidgetKit.framework */; };
049F146D2DDB1F64007B5A9B /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04BD915F2D9D68AD00055CEB /* SwiftUI.framework */; };
049F14782DDB1F66007B5A9B /* LockSreenExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 049F146B2DDB1F64007B5A9B /* LockSreenExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
04BBB4E62DC0748F00D7E3AB /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04BBB4E52DC0748F00D7E3AB /* StoreKit.framework */; };
04CF31702DA7E78F001C87CA /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04BD7A442DA3BA1700A24C4B /* Intents.framework */; };
04CF31772DA7E790001C87CA /* ChargeShow.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 04CF316F2DA7E78F001C87CA /* ChargeShow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
......@@ -28,6 +31,13 @@
remoteGlobalIDString = 0496DEF12D9E3F57005B2834;
remoteInfo = widgetExtension;
};
049F14762DDB1F66007B5A9B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = EB388E532D8A61A800629B0D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 049F146A2DDB1F64007B5A9B;
remoteInfo = LockSreenExtension;
};
04CF31752DA7E78F001C87CA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = EB388E532D8A61A800629B0D /* Project object */;
......@@ -56,6 +66,7 @@
files = (
0496DEFF2D9E3F59005B2834 /* widgetExtension.appex in Embed Foundation Extensions */,
04CF31772DA7E790001C87CA /* ChargeShow.appex in Embed Foundation Extensions */,
049F14782DDB1F66007B5A9B /* LockSreenExtension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
......@@ -65,6 +76,8 @@
/* Begin PBXFileReference section */
0496DEF22D9E3F57005B2834 /* widgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = widgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
0496DF042D9E3FA7005B2834 /* widgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = widgetExtension.entitlements; sourceTree = "<group>"; };
049F146B2DDB1F64007B5A9B /* LockSreenExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = LockSreenExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
049F147F2DDB210B007B5A9B /* LockSreenExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LockSreenExtension.entitlements; sourceTree = "<group>"; };
04BBB4E52DC0748F00D7E3AB /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
04BD7A442DA3BA1700A24C4B /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
04BD7A4F2DA3BA1700A24C4B /* IntentsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntentsUI.framework; path = System/Library/Frameworks/IntentsUI.framework; sourceTree = SDKROOT; };
......@@ -94,6 +107,13 @@
);
target = 0496DEF12D9E3F57005B2834 /* widgetExtension */;
};
049F147C2DDB1F66007B5A9B /* Exceptions for "LockSreen" folder in "LockSreenExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 049F146A2DDB1F64007B5A9B /* LockSreenExtension */;
};
04CF317B2DA7E790001C87CA /* Exceptions for "ChargeShow" folder in "ChargeShow" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
......@@ -120,6 +140,14 @@
path = widget;
sourceTree = "<group>";
};
049F146E2DDB1F64007B5A9B /* LockSreen */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
049F147C2DDB1F66007B5A9B /* Exceptions for "LockSreen" folder in "LockSreenExtension" target */,
);
path = LockSreen;
sourceTree = "<group>";
};
04CF31712DA7E78F001C87CA /* ChargeShow */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
......@@ -148,6 +176,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
049F14682DDB1F64007B5A9B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
049F146D2DDB1F64007B5A9B /* SwiftUI.framework in Frameworks */,
049F146C2DDB1F64007B5A9B /* WidgetKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
04CF316C2DA7E78F001C87CA /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
......@@ -196,11 +233,13 @@
EB388E522D8A61A800629B0D = {
isa = PBXGroup;
children = (
049F147F2DDB210B007B5A9B /* LockSreenExtension.entitlements */,
04CF317C2DA7E7BE001C87CA /* Intents.intentdefinition */,
0496DF042D9E3FA7005B2834 /* widgetExtension.entitlements */,
EB388E5D2D8A61A800629B0D /* PhoneManager */,
0496DEF52D9E3F58005B2834 /* widget */,
04CF31712DA7E78F001C87CA /* ChargeShow */,
049F146E2DDB1F64007B5A9B /* LockSreen */,
EB388E5C2D8A61A800629B0D /* Products */,
CB2ACD1E9442B4500087E831 /* Pods */,
27ECDADD9059AB5043B8E1E9 /* Frameworks */,
......@@ -213,6 +252,7 @@
EB388E5B2D8A61A800629B0D /* PhoneManager.app */,
0496DEF22D9E3F57005B2834 /* widgetExtension.appex */,
04CF316F2DA7E78F001C87CA /* ChargeShow.appex */,
049F146B2DDB1F64007B5A9B /* LockSreenExtension.appex */,
);
name = Products;
sourceTree = "<group>";
......@@ -240,6 +280,28 @@
productReference = 0496DEF22D9E3F57005B2834 /* widgetExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
049F146A2DDB1F64007B5A9B /* LockSreenExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 049F14792DDB1F66007B5A9B /* Build configuration list for PBXNativeTarget "LockSreenExtension" */;
buildPhases = (
049F14672DDB1F64007B5A9B /* Sources */,
049F14682DDB1F64007B5A9B /* Frameworks */,
049F14692DDB1F64007B5A9B /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
049F146E2DDB1F64007B5A9B /* LockSreen */,
);
name = LockSreenExtension;
packageProductDependencies = (
);
productName = LockSreenExtension;
productReference = 049F146B2DDB1F64007B5A9B /* LockSreenExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
04CF316E2DA7E78F001C87CA /* ChargeShow */ = {
isa = PBXNativeTarget;
buildConfigurationList = 04CF31782DA7E790001C87CA /* Build configuration list for PBXNativeTarget "ChargeShow" */;
......@@ -278,6 +340,7 @@
dependencies = (
0496DEFE2D9E3F59005B2834 /* PBXTargetDependency */,
04CF31762DA7E78F001C87CA /* PBXTargetDependency */,
049F14772DDB1F66007B5A9B /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
EB388E5D2D8A61A800629B0D /* PhoneManager */,
......@@ -300,6 +363,9 @@
0496DEF12D9E3F57005B2834 = {
CreatedOnToolsVersion = 16.2;
};
049F146A2DDB1F64007B5A9B = {
CreatedOnToolsVersion = 16.2;
};
04CF316E2DA7E78F001C87CA = {
CreatedOnToolsVersion = 16.2;
};
......@@ -330,6 +396,7 @@
EB388E5A2D8A61A800629B0D /* PhoneManager */,
0496DEF12D9E3F57005B2834 /* widgetExtension */,
04CF316E2DA7E78F001C87CA /* ChargeShow */,
049F146A2DDB1F64007B5A9B /* LockSreenExtension */,
);
};
/* End PBXProject section */
......@@ -342,6 +409,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
049F14692DDB1F64007B5A9B /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
04CF316D2DA7E78F001C87CA /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
......@@ -425,6 +499,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
049F14672DDB1F64007B5A9B /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
04CF316B2DA7E78F001C87CA /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
......@@ -449,6 +530,11 @@
target = 0496DEF12D9E3F57005B2834 /* widgetExtension */;
targetProxy = 0496DEFD2D9E3F59005B2834 /* PBXContainerItemProxy */;
};
049F14772DDB1F66007B5A9B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 049F146A2DDB1F64007B5A9B /* LockSreenExtension */;
targetProxy = 049F14762DDB1F66007B5A9B /* PBXContainerItemProxy */;
};
04CF31762DA7E78F001C87CA /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 04CF316E2DA7E78F001C87CA /* ChargeShow */;
......@@ -525,6 +611,72 @@
};
name = Release;
};
049F147A2DDB1F66007B5A9B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = LockSreenExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6K23946NQ5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = LockSreen/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = LockSreen;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.app.phonemanager.lockSreenWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
049F147B2DDB1F66007B5A9B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = LockSreenExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6K23946NQ5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = LockSreen/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = LockSreen;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.app.phonemanager.lockSreenWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
04CF31792DA7E790001C87CA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
......@@ -813,6 +965,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
049F14792DDB1F66007B5A9B /* Build configuration list for PBXNativeTarget "LockSreenExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
049F147A2DDB1F66007B5A9B /* Debug */,
049F147B2DDB1F66007B5A9B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
04CF31782DA7E790001C87CA /* Build configuration list for PBXNativeTarget "ChargeShow" */ = {
isa = XCConfigurationList;
buildConfigurations = (
......
......@@ -48,6 +48,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
AdvManager.shared.initAdertisementSDK()
PMEmailManager.shareManager.restore()
SettingConfiguration.share.initData()
HapticManager.share.setupHapticEngine()
// 相册基本资源加载
PhotoManager.shared.config()
......
//
// PMAboutUsController.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import UIKit
import MessageUI
class PMAboutUsController: BaseViewController, MFMailComposeViewControllerDelegate {
@IBOutlet weak var despLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
titleView.model.title = "About Us"
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setConfigs()
addClick()
}
private func setConfigs() -> Void {
var mutableText:NSMutableAttributedString = NSMutableAttributedString(attributedString: despLabel.attributedText ?? NSMutableAttributedString(string: ""))
let paragraph = NSMutableParagraphStyle()
paragraph.lineSpacing = 2
mutableText.addAttributes([NSAttributedString.Key.paragraphStyle : paragraph], range: NSRange(location: 0, length: mutableText.length))
if let rang = mutableText.string.range(of: "OUR APPROACH") {
let startIndex = rang.lowerBound
let endIndex = rang.upperBound
let start = mutableText.string.distance(from: mutableText.string.startIndex, to: startIndex)
let end = mutableText.string.distance(from: mutableText.string.startIndex, to: endIndex)
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .bold)], range: NSRange(location: start, length: end - start))
}
if let rang = mutableText.string.range(of: "apps.columbusdev.com") {
let startIndex = rang.lowerBound
let endIndex = rang.upperBound
let start = mutableText.string.distance(from: mutableText.string.startIndex, to: startIndex)
let end = mutableText.string.distance(from: mutableText.string.startIndex, to: endIndex)
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14, weight: .regular),NSAttributedString.Key.foregroundColor:UIColor.colorWithHex(hexStr: "#0082FF"),.underlineStyle:NSUnderlineStyle.single.rawValue,.underlineColor:UIColor.colorWithHex(hexStr: "#0082FF")],
range: NSRange(location: start, length: end - start))
}
if let rang = mutableText.string.range(of: "ligaili1217@163.com") {
let startIndex = rang.lowerBound
let endIndex = rang.upperBound
let start = mutableText.string.distance(from: mutableText.string.startIndex, to: startIndex)
let end = mutableText.string.distance(from: mutableText.string.startIndex, to: endIndex)
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14, weight: .regular),NSAttributedString.Key.foregroundColor:UIColor.colorWithHex(hexStr: "#0082FF"),.underlineStyle:NSUnderlineStyle.single.rawValue,.underlineColor:UIColor.colorWithHex(hexStr: "#0082FF")],
range: NSRange(location: start, length: end - start))
}
despLabel.attributedText = mutableText
}
private func addClick() -> Void {
let click = ["apps.columbusdev.com","ligaili1217@163.com"]
despLabel.AddTapped(click) {[weak self] text in
guard let self = self else { return }
if text == click[0] {
guard let url = URL(string: "https://apps.columbusdev.com/") else { return }
UIApplication.shared.open(url)
}else{
self.sendMail()
}
}
}
private func sendMail() -> Void {
let email = "ligaili1217@163.com"
let subject = ""//邮件主题
let body = ""//邮件正文
let defaultURL = URL(string: "mailto:\(email)?subject=\(subject)&body=\(body)")
let application = UIApplication.shared
if let url = defaultURL,application.canOpenURL(url) {
application.open(url)
}else{
print("Unable to send email")
PMAlert(title: nil, messsage: "", action: ["Ok"])
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: (any Error)?) {
controller.dismiss(animated: true)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PMAboutUsController" customModule="PhoneManager" customModuleProvider="target">
<connections>
<outlet property="despLabel" destination="zuX-UN-ngF" id="z6D-sl-gOi"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tXM-qs-7LP">
<rect key="frame" x="16" y="139" width="361" height="679"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TrU-Y1-jr0">
<rect key="frame" x="0.0" y="0.0" width="361" height="583.33333333333337"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zuX-UN-ngF">
<rect key="frame" x="16" y="16" width="329" height="551.33333333333337"/>
<mutableString key="text">OUR APPROACH
Curiosity drives our exploration. Dedication fuels our efforts.Collaboration binds us together. At the core of everything we do are the people we serve.

We persevere through challenges and find joy in our shared progress. We recognize that collective wisdom surpasses individual insights.

We respect every voice, understanding that diverse perspectives enrich our work. We approach each task with open minds and a hunger to learn.

We know that excellence is achieved through consistent, incremental growth. We value clarity and simplicity in all we create. We stay flexible, ready to pivot when needed.

We welcome change as an opportunity to evolve and keep moving forward. We understand setbacks are stepping stones to success. We take risks, learn from failures, and use them as motivation to improve. We're committed to our unique way of working and making a difference. 

apps.columbusdev.com
ligaili1217@163.com

©2025 2025 Columbus Trading. All rights reserved.</mutableString>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.94901960784313721" green="0.96470588235294119" blue="0.9882352941176471" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="zuX-UN-ngF" firstAttribute="leading" secondItem="TrU-Y1-jr0" secondAttribute="leading" constant="16" id="GRf-GR-BeP"/>
<constraint firstAttribute="trailing" secondItem="zuX-UN-ngF" secondAttribute="trailing" constant="16" id="NQJ-41-oOR"/>
<constraint firstItem="zuX-UN-ngF" firstAttribute="top" secondItem="TrU-Y1-jr0" secondAttribute="top" constant="16" id="cXh-Xx-LFC"/>
<constraint firstAttribute="bottom" secondItem="zuX-UN-ngF" secondAttribute="bottom" constant="16" id="sCh-Wo-z7z"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="Radius">
<real key="value" value="12"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="TrU-Y1-jr0" secondAttribute="bottom" id="2Yf-R2-5ee"/>
<constraint firstAttribute="trailing" secondItem="TrU-Y1-jr0" secondAttribute="trailing" id="3P5-fh-O54"/>
<constraint firstItem="TrU-Y1-jr0" firstAttribute="top" secondItem="tXM-qs-7LP" secondAttribute="top" id="SSk-ev-Zpu"/>
<constraint firstItem="TrU-Y1-jr0" firstAttribute="centerX" secondItem="tXM-qs-7LP" secondAttribute="centerX" id="lV6-id-DLi"/>
<constraint firstItem="TrU-Y1-jr0" firstAttribute="leading" secondItem="tXM-qs-7LP" secondAttribute="leading" id="qgC-EG-fwb"/>
</constraints>
</scrollView>
</subviews>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="tXM-qs-7LP" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="80" id="7gF-2c-7Yc"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="tXM-qs-7LP" secondAttribute="bottom" id="PVp-uI-af2"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="tXM-qs-7LP" secondAttribute="trailing" constant="16" id="lbt-x9-4qq"/>
<constraint firstItem="tXM-qs-7LP" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="16" id="lu9-Zo-4Sa"/>
</constraints>
<point key="canvasLocation" x="131" y="-12"/>
</view>
</objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
......@@ -138,7 +138,7 @@ extension ContactBackupDetailViewController:UITableViewDelegate,UITableViewDataS
let sectionTitle = sectionTitles[indexPath.section]
let contact = sectionedContacts[sectionTitle]?[indexPath.row]
cell.model = contact
cell.nameLabel.text = contact?.fullName
cell.nameLabel.text = contact?.fullName.count ?? 0 > 0 ? contact?.fullName : "Untitled contact person"
return cell
}
......
......@@ -140,7 +140,7 @@ extension ContactAllView : UITableViewDataSource,UITableViewDelegate {
let sectionTitle = sectionTitles[indexPath.section]
let contact = sectionedContacts[sectionTitle]?[indexPath.row]
cell.model = contact
cell.nameLabel.text = contact?.fullName
cell.nameLabel.text = contact?.fullName.count ?? 0 > 0 ? contact?.fullName : "Untitled contact person"
if self.selectedContacts.contains(where: { $0.identifier == contact!.identifier }) {
cell.selectButton.isSelected = true
}else {
......
......@@ -159,7 +159,7 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomContactAllViewTableViewCell", for: indexPath) as! CustomContactAllViewTableViewCell
let contact = self.dataSourceModel[indexPath.row]
cell.model = contact
cell.nameLabel.text = contact.fullName
cell.nameLabel.text = contact.fullName.count > 0 ? contact.fullName : "Untitled contact person"
if self.selectedContacts.contains(where: { $0.identifier == contact.identifier }) {
cell.selectButton.isSelected = true
}else {
......
//
// PMEmailSupportCell.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import UIKit
class PMEmailSupportCell: UITableViewCell {
static let id = "PMEmailSupportCell"
@IBOutlet weak var esLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
var data:FAQDataModel? {
didSet{
esLabel.text = data?.problem ?? ""
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="123" id="KGk-i7-Jjw" customClass="PMEmailSupportCell" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="358" height="123"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="358" height="123"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4SX-Xi-udm">
<rect key="frame" x="0.0" y="0.0" width="358" height="113"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="How can I recover deleted photos or videos?" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6bs-U1-uqM">
<rect key="frame" x="16" y="16" width="286" height="81"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="24" id="KBB-uU-vBe"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_left_setting_grey" translatesAutoresizingMaskIntoConstraints="NO" id="eLs-7W-DBt">
<rect key="frame" x="318" y="46.666666666666664" width="20" height="19.999999999999993"/>
<constraints>
<constraint firstAttribute="width" constant="20" id="5pq-Lw-7Bh"/>
<constraint firstAttribute="height" constant="20" id="EcB-PY-v2P"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" red="0.94901960784313721" green="0.96470588235294119" blue="0.9882352941176471" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="eLs-7W-DBt" firstAttribute="centerY" secondItem="4SX-Xi-udm" secondAttribute="centerY" id="C0l-FW-4EX"/>
<constraint firstItem="6bs-U1-uqM" firstAttribute="top" secondItem="4SX-Xi-udm" secondAttribute="top" constant="16" id="GDj-00-bNe"/>
<constraint firstAttribute="trailing" secondItem="eLs-7W-DBt" secondAttribute="trailing" constant="20" id="HAG-83-qxo"/>
<constraint firstAttribute="bottom" secondItem="6bs-U1-uqM" secondAttribute="bottom" constant="16" id="aE0-zU-2xe"/>
<constraint firstItem="6bs-U1-uqM" firstAttribute="leading" secondItem="4SX-Xi-udm" secondAttribute="leading" constant="16" id="hZh-1Y-B5K"/>
<constraint firstItem="eLs-7W-DBt" firstAttribute="leading" secondItem="6bs-U1-uqM" secondAttribute="trailing" constant="16" id="nGx-nB-DAR"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="Radius">
<real key="value" value="12"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="4SX-Xi-udm" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="ClS-G3-1nM"/>
<constraint firstItem="4SX-Xi-udm" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="doE-qG-Em3"/>
<constraint firstAttribute="bottom" secondItem="4SX-Xi-udm" secondAttribute="bottom" constant="10" id="g8u-yZ-V1p"/>
<constraint firstAttribute="trailing" secondItem="4SX-Xi-udm" secondAttribute="trailing" id="iXi-hr-caF"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<color key="backgroundColor" red="0.94901960784313721" green="0.96470588235294119" blue="0.9882352941176471" alpha="1" colorSpace="calibratedRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="Radius">
<real key="value" value="12"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="esLabel" destination="6bs-U1-uqM" id="o8k-8Q-cLR"/>
</connections>
<point key="canvasLocation" x="158.77862595419847" y="16.549295774647888"/>
</tableViewCell>
</objects>
<resources>
<image name="icon_left_setting_grey" width="20" height="20"/>
</resources>
</document>
//
// PMEmailSupportController.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import UIKit
import MessageUI
class PMEmailSupportController: BaseViewController {
@IBOutlet weak var emailTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
titleView.model.title = "Email support"
setup()
}
func setup() -> Void {
emailTableView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview().inset(marginLR)
make.top.equalTo(titleView.snp.bottom).offset(10)
}
emailTableView.sectionHeaderHeight = 40
if #available(iOS 15.0, *) {
emailTableView.sectionHeaderTopPadding = 0
} else {
// Fallback on earlier versions
}
emailTableView.register(UINib(nibName: PMEmailSupportCell.id, bundle: nil), forCellReuseIdentifier: PMEmailSupportCell.id)
emailTableView.showsVerticalScrollIndicator = false
let file = Bundle.main.bundleURL.appendingPathComponent("PMEmailSupportData.json")
do{
let dictData = try Data(contentsOf: file)
dataSource = try JSONDecoder().decode([FAQDataModel].self, from: dictData)
}catch{ }
}
var dataSource:[FAQDataModel] = [] {
didSet {
self.emailTableView.reloadData()
}
}
}
extension PMEmailSupportController : UITableViewDelegate,UITableViewDataSource,MFMailComposeViewControllerDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PMEmailSupportCell.id, for: indexPath) as! PMEmailSupportCell
cell.selectionStyle = .none
cell.data = dataSource[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = UILabel()
header.text = "Please select a request from the following list."
header.textColor = UIColor.colorWithHex(hexStr: "#999999")
header.font = UIFont.systemFont(ofSize: 12)
header.backgroundColor = .white
return header
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let data = dataSource[indexPath.row]
if data.problem != "Other" {
let support = PMEmailSupportDetailController()
support.data = data
self.navigationController?.pushViewController(support, animated: true)
}else{
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setSubject("My PhoneManager Experience")
mail.setToRecipients(["ligaili1217@163.com"]) // 收件人地址
let body = String(format: "\n\n%@. iOS %@.%@\n%@", UIDevice.current.detailedModel,UIDevice.current.systemVersion,UIDevice.current.version(),UIDevice.current.identifierForVendor?.uuidString ?? "")
mail.setMessageBody(body, isHTML: false)
self.present(mail, animated: true, completion: nil)
} else {
print("无法发送邮件")
PMAlert(title: nil, messsage: "Unable to send email", action: ["Ok"])
}
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: (any Error)?) {
controller.dismiss(animated: true)
}
}
extension UIDevice {
var detailedModel: String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
return machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
}
func version() -> String {
let infoDictionary = Bundle.main.infoDictionary
let appVersion = infoDictionary?["CFBundleShortVersionString"] as? String ?? "" // 版本号
let buildNumber = infoDictionary?["CFBundleVersion"] as? String ?? "" // 构建号
return String(format: "App version:%@(%@)",appVersion ,buildNumber)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PMEmailSupportController" customModule="PhoneManager" customModuleProvider="target">
<connections>
<outlet property="emailTableView" destination="MCG-EC-mOS" id="PTm-bW-gx1"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" translatesAutoresizingMaskIntoConstraints="NO" id="MCG-EC-mOS">
<rect key="frame" x="15" y="80" width="363" height="691"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<connections>
<outlet property="dataSource" destination="-1" id="sPO-YK-o5T"/>
<outlet property="delegate" destination="-1" id="VjH-qj-Jd5"/>
</connections>
</tableView>
</subviews>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<point key="canvasLocation" x="117" y="-11"/>
</view>
</objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
//
// PMEmailSupportDetailController.swift
// PhoneManager
//
// Created by edy on 2025/5/19.
//
import UIKit
class PMEmailSupportDetailController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
setuup()
}
var data:FAQDataModel?
private func setuup() -> Void {
scroll.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview().inset(marginLR)
make.top.equalTo(titleView.snp.bottom).offset(20)
}
tl.snp.makeConstraints { make in
make.left.top.equalToSuperview()
make.width.equalToSuperview()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tl.text = data?.problem ?? ""
let attribued = addattribed()
self.view.layoutIfNeeded()
let bound = attribued.boundingRect(with: CGSizeMake(view.width - marginLR * 2 - 16 * 2, .infinity), options: .usesLineFragmentOrigin, context: nil)
textView.attributedText = attribued
textView.snp.makeConstraints { make in
make.left.right.equalTo(tl)
make.top.equalTo(tl.snp.bottom).offset(10)
make.height.equalTo(bound.height+(marginLR * 2 + 16 * 2 + 10))
}
DispatchQueue.main.async {
self.scroll.contentSize = CGSizeMake(0, CGRectGetMaxY(self.textView.frame))
}
}
private lazy var tl: UILabel = {
let l = UILabel()
l.font = UIFont.systemFont(ofSize: 14, weight: .bold)
l.textColor = UIColor.colorWithHex(hexStr: "#333333")
l.numberOfLines = 0
scroll.addSubview(l)
return l
}()
private lazy var textView: UITextView = {
let tv = UITextView()
tv.isUserInteractionEnabled = false
tv.Radius = 12
tv.backgroundColor = UIColor.colorWithHex(hexStr: "#F2F6FC")
tv.textContainerInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
scroll.addSubview(tv)
return tv
}()
private lazy var scroll: UIScrollView = {
let s = UIScrollView()
s.showsVerticalScrollIndicator = false
s.contentInsetAdjustmentBehavior = .never
view.addSubview(s)
return s
}()
private func addattribed() -> NSMutableAttributedString {
let mutableText = NSMutableAttributedString(string: data?.answer ?? "")
let param = NSMutableParagraphStyle()
param.lineSpacing = 5
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14, weight: .regular),.paragraphStyle:param], range: NSRange(location: 0, length: mutableText.length))
let rangs = data?.bold ?? []
let famat = NSSet(array: rangs)
for text in famat {
if let rang = mutableText.string.range(of: text as! String) {
let startIndex = rang.lowerBound
let endIndex = rang.upperBound
let start = mutableText.string.distance(from: mutableText.string.startIndex, to: startIndex)
let end = mutableText.string.distance(from: mutableText.string.startIndex, to: endIndex)
if end < (data?.answer.count ?? 0) {
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14, weight: .bold)], range: NSRange(location: start, length: end - start))
}
}
}
return mutableText
}
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PMEmailSupportDetailController" customModuleProvider="target">
<connections>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
</view>
</objects>
</document>
[
{
"problem":"How to Recover Deleted Photos or Videos?",
"answer":"If you accidentally delete photos or videos through PhoneManager cleanup, iOS will temporarily store them in the \"Recently Deleted\" album for 30 days (they still occupy storage space during this period). To recover: Open the Photos app on your iPhone, go to the \"Recently Deleted\" album, tap \"Select\" in the top-right corner, check the files you need, then tap \"Recover\"; for batch recovery, directly tap \"Recover All\". Note that this album contains both manually deleted files and PhoneManager-cleaned content, and the system will permanently erase them after 30 days. Act promptly to avoid data loss.",
"bold":["\"Recently Deleted\" album","30 days","Photos","\"Select\"","\"Recover All\"","both manually deleted files and PhoneManager-cleaned content","permanently erase them after 30 days"]
},
{
"problem":"Why Isn’t Storage Space Freed Up After Cleanup?",
"answer":"Due to iOS system limitations, photos/videos deleted via PhoneManager are temporarily stored in the \"Recently Deleted\" album for 30 days (they continue to occupy storage space during this period). To free up space immediately, manually empty the album: Open the Photos app on your iPhone, go to the \"Recently Deleted\" album, tap \"Select\" > \"Delete All\" in the top-right corner to permanently remove files. Note that this action will delete both manually removed content and PhoneManager-cleaned files, and data cannot be recovered. Ensure no important files remain before proceeding.",
"bold":["\"Recently Deleted\" album" , "30 days","Photos","\"Select\"" , "\"Delete All\"","both manually removed content and PhoneManager-cleaned files","data cannot be recovered"]
},
{
"problem":"How to Cancel Subscription?",
"answer":"Since subscription management and payment processes are exclusively controlled by Apple to ensure account security, cancel your PhoneManager subscription via these steps: Open iPhone Settings, tap your Apple ID profile at the top, go to Subscriptions, select PhoneManager, then tap Cancel Subscription. This will stop auto-renewal, but you can continue using the service until the current subscription period ends.",
"bold":["exclusively controlled by Apple","Apple ID profile","Subscriptions","PhoneManager","Cancel Subscription"]
},
{
"problem":"Why Can't I Allow PhoneManager to Access My Photos?",
"answer":"If the \"Allow Photo Access\" option is missing in settings, it may be due to Screen Time restrictions enabling permission controls. Adjust as follows: Open iPhone Settings, go to Screen Time > Content & Privacy Restrictions > Photos, and ensure Allow Changes is enabled. Afterward, force close and restart PhoneManager to reactivate the permission request. This action will not compromise existing photo data security—permission adjustments are solely for optimizing app functionality interactions.",
"bold":["Screen Time restrictions","iPhone Settings","Screen Time > Content & Privacy Restrictions > Photos","Allow Changes","will not compromise existing photo data security"]
},
{
"problem":"Other",
"answer":"",
"bold":[]
}
]
......@@ -43,27 +43,9 @@ class PMFAQCell: UITableViewCell {
}
private func addattribed() -> NSMutableAttributedString {
var mutableText = NSMutableAttributedString(string: data?.answer ?? "")
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14, weight: .semibold)], range: NSRange(location: 0, length: mutableText.length))
let rangs = ["PhoneManager",
"face recognition",
"similar photo comparison",
"edited/favorited status",
"Best Result",
"only with iPhones running iOS 14 or later",
"App Store's subscription terms and conditions",
"Settings",
"Apple ID profile","Subscriptions",
"Cancel Subscription",
"automatic scanning and analysis",
"only on recent additions or changes","Similar",
"within seconds of each other",
"same subject/person","consistent background","Best Result",
"favorites","Other","do not fit predefined classifications","interactive swiping mechanism",
"left","right","tap any item","creation date","red checkmark icon",
"Delete","Keep List","Move to Trash","trash bin icon","swiping feature","Videos","swipe left",
"swipe right","resource-intensive","by size or creation date","smart algorithms","final approval",
"explicit approval","offline processing","no personal data or photo content","Keep All","Settings icon","Media Library","Unkeep"]
let mutableText = NSMutableAttributedString(string: data?.answer ?? "")
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14, weight: .regular)], range: NSRange(location: 0, length: mutableText.length))
let rangs = data?.bold ?? []
let famat = NSSet(array: rangs)
for text in famat {
if let rang = mutableText.string.range(of: text as! String) {
......@@ -71,7 +53,9 @@ class PMFAQCell: UITableViewCell {
let endIndex = rang.upperBound
let start = mutableText.string.distance(from: mutableText.string.startIndex, to: startIndex)
let end = mutableText.string.distance(from: mutableText.string.startIndex, to: endIndex)
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14, weight: .bold)], range: NSRange(location: start, length: end))
if end < (data?.answer.count ?? 0) {
mutableText.addAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14, weight: .bold)], range: NSRange(location: start, length: end - start))
}
}
}
return mutableText
......
......@@ -13,55 +13,69 @@
},
{
"problem":"How Does PhoneManager Choose the Best Photos?",
"answer":"PhoneManager employs advanced algorithms incorporating face recognition, similar photo comparison, and evaluation of edited/favorited status to analyze your gallery. Based on these criteria, it dynamically flags one photo in a group as the Best Result. While the selection prioritizes technical quality and metadata insights, we strongly recommend reviewing these suggestions to ensure they align with your personal preferences, as human judgment may differ from algorithmic evaluations"
"answer":"PhoneManager employs advanced algorithms incorporating face recognition, similar photo comparison, and evaluation of edited/favorited status to analyze your gallery. Based on these criteria, it dynamically flags one photo in a group as the Best Result. While the selection prioritizes technical quality and metadata insights, we strongly recommend reviewing these suggestions to ensure they align with your personal preferences, as human judgment may differ from algorithmic evaluations",
"bold":["PhoneManager","face recognition, similar photo comparison","edited/favorited status","Best Result"]
},
{
"problem":"Can I Use PhoneManager on All My Other Apple Devices?",
"answer":"PhoneManager is currently compatible only with iPhones running iOS 14 or later. The number of devices covered under your subscription/license is governed by the App Store's subscription terms and conditions. Support for additional Apple devices (e.g., iPads, Macs) is unavailable at this time."
"answer":"PhoneManager is currently compatible only with iPhones running iOS 14 or later. The number of devices covered under your subscription/license is governed by the App Store's subscription terms and conditions. Support for additional Apple devices (e.g., iPads, Macs) is unavailable at this time.",
"bold":["PhoneManager","only with iPhones running iOS 14 or later","App Store's subscription terms and conditions"]
},
{
"problem":"How Can I Cancel My Subscription?",
"answer":"To cancel your PhoneManager subscription, follow Apple's standard subscription management process: Open Settings on your iPhone, tap your Apple ID profile at the top, select Subscriptions, then choose PhoneManager. Tap the Cancel Subscription button at the bottom of the screen. Note that this action stops automatic renewal but preserves access until the end of your current billing cycle."
"answer":"To cancel your PhoneManager subscription, follow Apple's standard subscription management process: Open Settings on your iPhone, tap your Apple ID profile at the top, select Subscriptions, then choose PhoneManager. Tap the Cancel Subscription button at the bottom of the screen. Note that this action stops automatic renewal but preserves access until the end of your current billing cycle.",
"bold":["PhoneManager","Settings","Apple ID profile","Subscriptions","Cancel Subscription"]
},
{
"problem":"How Do I Scan the Photos in My Photos Library?",
"answer":"PhoneManager initiates automatic scanning and analysis of your photo library upon first launch. For large collections, this process may require significant time and device resources. Please allow the scan to complete uninterrupted—results will display once finished. Subsequent scans optimize efficiency by focusing only on recent additions or changes, leveraging cached data from the initial scan. While first-time usage demands patience, future interactions will be faster and resource-light."
"answer":"PhoneManager initiates automatic scanning and analysis of your photo library upon first launch. For large collections, this process may require significant time and device resources. Please allow the scan to complete uninterrupted—results will display once finished. Subsequent scans optimize efficiency by focusing only on recent additions or changes, leveraging cached data from the initial scan. While first-time usage demands patience, future interactions will be faster and resource-light.",
"bold":["PhoneManager","automatic scanning and analysis","only on recent additions or changes"]
},
{
"problem":"What Is Categorized as “Similar”?",
"answer":"The “Similar” category groups photos captured within seconds of each other, featuring the same subject/person in a consistent background with minor variations (e.g., poses, angles). For instance, multiple shots of someone posing differently in front of a landmark. To simplify cleanup, PhoneManager uses advanced algorithms analyzing factors like clarity and composition to flag one photo as the Best Result and suggests removing others. Photos marked as favorites are automatically excluded from selection. Before confirming cleanup, review suggested groups to ensure alignment with your preferences."
"answer":"The “Similar” category groups photos captured within seconds of each other, featuring the same subject/person in a consistent background with minor variations (e.g., poses, angles). For instance, multiple shots of someone posing differently in front of a landmark. To simplify cleanup, PhoneManager uses advanced algorithms analyzing factors like clarity and composition to flag one photo as the Best Result and suggests removing others. Photos marked as favorites are automatically excluded from selection. Before confirming cleanup, review suggested groups to ensure alignment with your preferences.",
"bold":["Similar","within seconds of each other","same subject/person","consistent background","PhoneManager","Best Result","favorites"]
},
{
"problem":"What Is Categorized as \"Other\"?",
"answer":"The “Other” category includes photos that do not fit predefined classifications (e.g., duplicates, similar shots). Unlike automated suggestions in other groups, PhoneManager refrains from labeling items here as \"good\" or \"bad.\" Instead, it employs an interactive swiping mechanism—swipe left to mark photos for deletion or right to retain them—giving you full control over curation. Prioritize reviewing this category manually to ensure no unintended files are removed."
"answer":"The “Other” category includes photos that do not fit predefined classifications (e.g., duplicates, similar shots). Unlike automated suggestions in other groups, PhoneManager refrains from labeling items here as \"good\" or \"bad.\" Instead, it employs an interactive swiping mechanism—swipe left to mark photos for deletion or right to retain them—giving you full control over curation. Prioritize reviewing this category manually to ensure no unintended files are removed.",
"bold":["Other","do not fit predefined classifications","interactive swiping mechanism","PhoneManager","left","right"]
},
{
"problem":"How Can I Review All Photos in a Certain Category?",
"answer":"To review photos within a category, tap any item to enlarge it for closer inspection. In the “Similar” category, photos are grouped into sets organized by creation date—scroll vertically to browse all sets and tap one to examine its contents. For subcategories without grouped sets, scroll freely through individual photos and tap to view details. Use this process to thoroughly evaluate suggestions before finalizing cleanup decisions."
"answer":"To review photos within a category, tap any item to enlarge it for closer inspection. In the “Similar” category, photos are grouped into sets organized by creation date—scroll vertically to browse all sets and tap one to examine its contents. For subcategories without grouped sets, scroll freely through individual photos and tap to view details. Use this process to thoroughly evaluate suggestions before finalizing cleanup decisions.",
"bold":["tap any item","Similar","creation date"]
},
{
"problem":"How Can I Remove Unwanted Photos?",
"answer":"PhoneManager automatically flags suggested photos for deletion with a red checkmark icon based on algorithmic analysis. To proceed, select the marked items and tap Delete—but ensure any photos you wish to retain are moved to your Keep List first. For manual review in the “Similar” category, open a grouped set, inspect the app’s Best Result recommendation, and tap Move to Trash to approve deletions. To override suggestions, unmark photos you want to keep (or add them to your Keep List), re-mark unwanted items, then finalize by tapping the trash bin icon and confirming Delete. Always verify selections, as deletions are permanent."
"answer":"PhoneManager automatically flags suggested photos for deletion with a red checkmark icon based on algorithmic analysis. To proceed, select the marked items and tap Delete—but ensure any photos you wish to retain are moved to your Keep List first. For manual review in the “Similar” category, open a grouped set, inspect the app’s Best Result recommendation, and tap Move to Trash to approve deletions. To override suggestions, unmark photos you want to keep (or add them to your Keep List), re-mark unwanted items, then finalize by tapping the trash bin icon and confirming Delete. Always verify selections, as deletions are permanent.",
"bold":["PhoneManager","red checkmark icon","Delete","Keep List","Similar","Best Result","Move to Trash","trash bin icon"]
},
{
"problem":"How Do I Use the Swiping Feature?",
"answer":"The swiping feature in PhoneManager provides an efficient way to review photos in the Videos and Other categories. Simply swipe left on a photo to mark it for removal or swipe right to add it to your Keep List. This intuitive gesture-based system streamlines the cleanup process while ensuring you retain full control over final decisions."
"answer":"The swiping feature in PhoneManager provides an efficient way to review photos in the Videos and Other categories. Simply swipe left on a photo to mark it for removal or swipe right to add it to your Keep List. This intuitive gesture-based system streamlines the cleanup process while ensuring you retain full control over final decisions.",
"bold":["PhoneManager","swiping feature","Videos","Other","swipe left","swipe right","Keep List"]
},
{
"problem":"Can I Clean My Videos Using PhoneManager?",
"answer":"Yes, PhoneManager supports video cleanup by organizing all videos under the Videos category. While similarity-based sorting for videos is resource-intensive, you can manually sort them by size or creation date and use the swipe-left gesture to mark items for deletion or swipe-right to retain them in your Keep List. This streamlined approach balances technical constraints with user control, allowing efficient curation of large video libraries."
"answer":"Yes, PhoneManager supports video cleanup by organizing all videos under the Videos category. While similarity-based sorting for videos is resource-intensive, you can manually sort them by size or creation date and use the swipe-left gesture to mark items for deletion or swipe-right to retain them in your Keep List. This streamlined approach balances technical constraints with user control, allowing efficient curation of large video libraries.",
"bold":["PhoneManager","resource-intensive","Videos","by size or creation date","swipe left","swipe right","Keep List"]
},
{
"problem":"Should I Review All the Photos PhoneManager Scanned?",
"answer":"While PhoneManager employs smart algorithms to group photos and suggest Best Result recommendations, we strongly advise reviewing all flagged items before deletion. The app streamlines organization and prioritizes accuracy, but personal preferences may differ from algorithmic logic. Retain full control by verifying selections—your final approval ensures no essential photos are inadvertently removed."
"answer":"While PhoneManager employs smart algorithms to group photos and suggest Best Result recommendations, we strongly advise reviewing all flagged items before deletion. The app streamlines organization and prioritizes accuracy, but personal preferences may differ from algorithmic logic. Retain full control by verifying selections—your final approval ensures no essential photos are inadvertently removed.",
"bold":["PhoneManager","smart algorithms","Best Result","final approval"]
},
{
"problem":"Is PhoneManager Safe? How Can I Trust PhoneManager?",
"answer":"PhoneManager prioritizes safety and user control: All cleanup actions require explicit approval, and no files are deleted without your final confirmation. The app operates with offline processing (no photo uploads) and uses permissions solely to analyze local metadata (e.g., edits, favorites) for suggestions. While its algorithms streamline decisions, your manual review ensures alignment with personal preferences. Additionally, no personal data or photo content is shared externally—only anonymous technical logs are generated for app optimization. Trust is built through transparency and your retained authority over every deletion."
"answer":"PhoneManager prioritizes safety and user control: All cleanup actions require explicit approval, and no files are deleted without your final confirmation. The app operates with offline processing (no photo uploads) and uses permissions solely to analyze local metadata (e.g., edits, favorites) for suggestions. While its algorithms streamline decisions, your manual review ensures alignment with personal preferences. Additionally, no personal data or photo content is shared externally—only anonymous technical logs are generated for app optimization. Trust is built through transparency and your retained authority over every deletion.",
"bold":["PhoneManager","explicit approval","offline processing","no personal data or photo content"]
},
{
"problem":"How Do I Add Photos to My Keep List?",
"answer":"To add photos/videos to your Keep List, open a grouped set in the “Similar” category and tap the Keep All button (lower-left corner) to save the entire collection. For individual items in the Videos or “Other” categories, simply swipe right to add them to your Keep List. To review or edit saved content, access your Keep List through the Settings icon (upper-right corner of the Media Library). If you wish to remove items, select the files within the Keep List and tap Unkeep (upper-right corner) to return them to the scan results. All changes take effect immediately, ensuring seamless control over your curated collection."
"answer":"To add photos/videos to your Keep List, open a grouped set in the “Similar” category and tap the Keep All button (lower-left corner) to save the entire collection. For individual items in the Videos or “Other” categories, simply swipe right to add them to your Keep List. To review or edit saved content, access your Keep List through the Settings icon (upper-right corner of the Media Library). If you wish to remove items, select the files within the Keep List and tap Unkeep (upper-right corner) to return them to the scan results. All changes take effect immediately, ensuring seamless control over your curated collection.",
"bold":["Keep List","Similar","Keep All","Videos","Other","swipe right","Settings icon","Media Library","Unkeep"]
},
]
......@@ -71,7 +71,7 @@ extension PMFAQController : UITableViewDelegate,UITableViewDataSource {
let current = openSet
openSet = indexPath.row
indexpaths = [indexPath]
if current > 0 {
if current >= 0 {
indexpaths.append(IndexPath(row: current, section: 0))
}
}
......@@ -82,6 +82,7 @@ extension PMFAQController : UITableViewDelegate,UITableViewDataSource {
struct FAQDataModel : Codable {
var problem:String
var answer:String
var bold:[String]?
init(problem: String, answer: String) {
self.problem = problem
self.answer = answer
......
......@@ -405,8 +405,6 @@ class PhotoRemoveViewController: BaseViewController {
if actualTranslation.x < 0 {
// 删除操作,先存到单利
self.vibrate()
saveDataToSigtonTrash()
saveDataToDBAndSigtonTrash()
}
......
......@@ -249,22 +249,28 @@ class SecretViewController: BaseViewController {
AdvManager.shared.finisedCallBack = {
self.AddAction = SecretActionView()
self.AddAction.show();
self.view.showBlur()
self.AddAction.callback = { idx in
self.view.hideBlur()
self.AddImagePicker(idx)
}
}
}else {
self.AddAction = SecretActionView()
self.view.showBlur()
self.AddAction.show();
self.AddAction.callback = { idx in
self.view.hideBlur()
self.AddImagePicker(idx)
}
}
}else {
self.AddAction = SecretActionView()
self.view.showBlur()
self.AddAction.show();
self.AddAction.callback = { idx in
self.view.hideBlur()
self.AddImagePicker(idx)
}
}
......@@ -320,6 +326,7 @@ extension SecretViewController : UICollectionViewDelegate,UICollectionViewDataSo
cell.isSelect = selectArray.contains((indexPath.row))
cell.imageText = dataSource[indexPath.row]
cell.callback = { [weak self] in
self?.vibrate()
self?.selectImgVideo(indexPath.row)
}
return cell
......
......@@ -8,17 +8,21 @@
import UIKit
import SnapKit
class SecretActionView: UIViewController {
class SecretActionView: UIViewController ,UIViewControllerTransitioningDelegate {
private var selectedViewBottomConstraint: Constraint?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .colorWithHex(hexStr: "#000000", alpha: 0.5)
view.backgroundColor = .clear//.colorWithHex(hexStr: "#000000", alpha: 0.5)
self.transitioningDelegate = self;
setUI()
addTapAction()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return customDismissAnimator()
}
/// 添加背景点击消失
func addTapAction(){
......@@ -80,6 +84,7 @@ class SecretActionView: UIViewController {
var callback:((_ idx:Int)->Void)?
@objc private func actionTouch(_ sender:UIButton) -> Void {
self.dismiss(animated: true)
guard callback != nil else {
return
......@@ -89,13 +94,17 @@ class SecretActionView: UIViewController {
}
@objc private func touchDismiss(){
UIView.animate(withDuration: 0.2) {
// 更新约束
self.selectedViewBottomConstraint?.update(offset: 1000)
self.view.layoutIfNeeded()
}completion: { _ in
// UIView.animate(withDuration: 0.2) {
// // 更新约束
// self.selectedViewBottomConstraint?.update(offset: 1000)
// self.view.layoutIfNeeded()
// }completion: { _ in
self.dismiss(animated: true)
guard callback != nil else {
return
}
callback!(0x12)
// }
}
......@@ -136,7 +145,7 @@ class SecretActionView: UIViewController {
let carma = UIButton(type: .custom)
carma.setImage(UIImage(named: "ic_video_secret"), for: .normal)
carma.backgroundColor = .colorWithHex(hexStr: "#F2F6FC")
carma.titleLabel?.font = UIFont.systemFont(ofSize: 16)
carma.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
carma.contentHorizontalAlignment = .leading
carma.layer.cornerRadius = 12
carma.tag = 0x10;
......@@ -153,7 +162,7 @@ class SecretActionView: UIViewController {
let Photo = UIButton(type: .custom)
Photo.setImage(UIImage(named: "ic_photo_secret"), for: .normal)
Photo.backgroundColor = .colorWithHex(hexStr: "#F2F6FC")
Photo.titleLabel?.font = UIFont.systemFont(ofSize: 16)
Photo.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
Photo.contentHorizontalAlignment = .leading
Photo.layer.cornerRadius = 12
Photo.tag = 0x11;
......
......@@ -179,8 +179,23 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
vibrate()
break
case settingLabels.Widgets.rawValue:
var callblock:(()->Void) = {[weak self] in
guard let self = self else { return }
let widget = WidgetViewController()
self.navigationController?.pushViewController(widget, animated: true)
}
if AdvManager.shared.advTimeAfterInAPP <= 0{
if IAPManager.share.isSubscribed == false {
AdvManager.shared.showInterstitialAd(vc: self)
}else{
callblock()
}
}else {
callblock()
}
AdvManager.shared.finisedCallBack = {
callblock()
}
break
case settingLabels.FollowonInstagram.rawValue:
guard let url = URL(string: "https://www.instagram.com/phone.manager.app/") else { return }
......@@ -209,6 +224,8 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
break
case settingLabels.EmailSupport.rawValue:
let emailSupport = PMEmailSupportController()
self.navigationController?.pushViewController(emailSupport, animated: true)
break
case settingLabels.FAQ.rawValue:
......@@ -216,6 +233,8 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
self.navigationController?.pushViewController(faq, animated: true)
break
case settingLabels.AboutUs.rawValue:
let AboutUs = PMAboutUsController()
self.navigationController?.pushViewController(AboutUs, animated: true)
break
case settingLabels.PrivacyPolicy.rawValue:
jumpWeb(detailModel.title)
......@@ -225,7 +244,6 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
}
}
// MARK: - 隐私空间PIN
private func secretspace() -> Void {
if SettingConfiguration.share.config.secret?.count ?? 0 == 4 {
......@@ -312,13 +330,27 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
}
alert.show()
}else{
let callblock:(()->Void) = { [weak self] in
guard let self = self else { return }
let vc:EmailLoginController = EmailLoginController()
vc.state = .home
self.navigationController?.pushViewController(vc, animated: true)
self.tableView.reloadData()
}
if AdvManager.shared.advTimeAfterInAPP <= 0{
if IAPManager.share.isSubscribed == false {
AdvManager.shared.showInterstitialAd(vc: self)
}else{
callblock()
}
}else {
callblock()
}
AdvManager.shared.finisedCallBack = {
callblock()
}
}
}
// MARK: - 评分
private func review() -> Void {
......@@ -333,7 +365,7 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
// MARK: - 分享
private func PhoneShare() -> Void {
let items: [Any] = [ URL(string: "itms-apps://itunes.apple.com/app/id1530333201")! ]
let items: [Any] = ["Want to organize your photo library and free up storage space? Try our app’s smart cleanup feature now!",URL(string: "https://apps.apple.com/app/id1530333201")! ]
let shareVc = UIActivityViewController(
activityItems: items,
applicationActivities: nil
......
......@@ -24,7 +24,7 @@ class PMWidgetExampleController: UIViewController ,UIScrollViewDelegate{
var current = 0 {
didSet{
if current == 0 {
if current <= 0 {
ReturnAction.isHidden = true
}else{
ReturnAction.isHidden = false
......@@ -34,6 +34,20 @@ class PMWidgetExampleController: UIViewController ,UIScrollViewDelegate{
NextActions.setTitle("Ok", for: .normal)
}
PMWidgetPage.currentPage = current
}
}
var type = 0 {
didSet{
PMWidgetMainScroll.isHidden = !(type == 0)
PMWidgetLockScroll.isHidden = type == 0
current = 0
animation()
}
}
private func animation() -> Void {
if type == 0 {
let orx = CGFloat(current) * self.PMWidgetMainScroll.width
UIView.animate(withDuration: 0.3) {
......@@ -46,16 +60,6 @@ class PMWidgetExampleController: UIViewController ,UIScrollViewDelegate{
}
}
}
}
var type = 0 {
didSet{
PMWidgetMainScroll.isHidden = !(type == 0)
PMWidgetLockScroll.isHidden = type == 0
current = 0
}
}
override func viewDidLoad() {
super.viewDidLoad()
......@@ -69,6 +73,7 @@ class PMWidgetExampleController: UIViewController ,UIScrollViewDelegate{
PMWidgetMainScroll.delegate = self;
PMWidgetLockScroll.delegate = self
type = 0
self.navigationController?.isNavigationBarHidden = true
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
......@@ -84,6 +89,7 @@ class PMWidgetExampleController: UIViewController ,UIScrollViewDelegate{
private func ScrollToCurrent(_ scroll:UIScrollView) -> Void {
let orx = scroll.contentOffset.x / scroll.width
PMWidgetPage.currentPage = Int(round(orx))
current = Int(round(orx))
if orx == 0 {
ReturnAction.isHidden = true
}else{
......@@ -111,11 +117,13 @@ class PMWidgetExampleController: UIViewController ,UIScrollViewDelegate{
@IBAction func BottomActions(_ sender: UIButton) {
if sender == ReturnAction {
self.current -= 1
animation()
}else{
if self.current == 4 {
self.dismiss(animated: true)
}else{
self.current += 1
animation()
}
}
}
......
......@@ -13,6 +13,7 @@ class PMWidgetGuideStartController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
}
@IBAction func StartNextAction(_ sender: Any) {
......
......@@ -19,7 +19,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Jx3-dI-rXt">
<rect key="frame" x="352" y="69" width="26" height="42"/>
<rect key="frame" x="352" y="69" width="26" height="53"/>
<inset key="contentEdgeInsets" minX="10" minY="10" maxX="0.0" maxY="10"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_widget_XXXXXXx"/>
......@@ -28,20 +28,29 @@
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add widgets to the home screen and the lock screen" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kbM-qf-WH2">
<rect key="frame" x="36" y="131" width="321" height="43"/>
<rect key="frame" x="36" y="142" width="321" height="43"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="43" id="cTr-dF-GVf"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jAN-Lj-ySf">
<rect key="frame" x="23" y="194" width="347" height="43"/>
<rect key="frame" x="23" y="205" width="347" height="43"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="43" id="gWB-Me-UTE"/>
</constraints>
<string key="text">Easily monitor your battery and storage space with convenient widgets, providing convenience for your home screen and lock screen!</string>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
<color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_widget_guideus" translatesAutoresizingMaskIntoConstraints="NO" id="Gk6-53-p7B">
<rect key="frame" x="34" y="257" width="325" height="442"/>
<rect key="frame" x="34" y="268" width="325" height="442"/>
<constraints>
<constraint firstAttribute="width" secondItem="Gk6-53-p7B" secondAttribute="height" multiplier="25:34" id="JRq-xy-nP7"/>
</constraints>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FzB-PE-uL0">
<rect key="frame" x="16" y="740" width="361" height="58"/>
......@@ -68,13 +77,13 @@
<constraint firstItem="jAN-Lj-ySf" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="23" id="0hg-ZZ-cgL"/>
<constraint firstItem="Gk6-53-p7B" firstAttribute="top" secondItem="jAN-Lj-ySf" secondAttribute="bottom" constant="20" id="5K8-RY-RRE"/>
<constraint firstItem="jAN-Lj-ySf" firstAttribute="top" secondItem="kbM-qf-WH2" secondAttribute="bottom" constant="20" id="5cu-aJ-ud2"/>
<constraint firstItem="Gk6-53-p7B" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="34" id="6tN-4g-d9M"/>
<constraint firstAttribute="trailing" secondItem="jAN-Lj-ySf" secondAttribute="trailing" constant="23" id="9jt-yG-2C3"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="Gk6-53-p7B" secondAttribute="trailing" constant="34" id="GSF-eJ-WeK"/>
<constraint firstItem="FzB-PE-uL0" firstAttribute="top" secondItem="Gk6-53-p7B" secondAttribute="bottom" constant="30" id="Ibh-ZB-Pcv"/>
<constraint firstItem="kbM-qf-WH2" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="36" id="Ie5-Fi-9zn"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="FzB-PE-uL0" secondAttribute="trailing" constant="16" id="O1A-bz-tvz"/>
<constraint firstItem="FzB-PE-uL0" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="16" id="Vpm-N7-5Mh"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="FzB-PE-uL0" secondAttribute="bottom" constant="20" id="Xmg-Oj-olO"/>
<constraint firstItem="Gk6-53-p7B" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="c7j-De-NMq"/>
<constraint firstItem="kbM-qf-WH2" firstAttribute="top" secondItem="Jx3-dI-rXt" secondAttribute="bottom" constant="20" id="fkG-Qv-2W9"/>
<constraint firstAttribute="trailing" secondItem="kbM-qf-WH2" secondAttribute="trailing" constant="36" id="g6U-Wh-rBz"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="Jx3-dI-rXt" secondAttribute="trailing" constant="15" id="k0K-LP-VIv"/>
......
......@@ -63,10 +63,12 @@ class WidgetViewController: BaseViewController {
DispatchQueue.main.asyncAfter(deadline: .now()+1.5) {
save.dismiss(animated: true) {
self.dismiss(animated: true)
if self.isJumpExample == false {
self.jumpGuidePage()
}
}
}
}
var isJumpExample:Bool {
set {
......@@ -90,15 +92,14 @@ class WidgetViewController: BaseViewController {
}else{
isJumpExample = true
let guide = PMWidgetGuideStartController()
guide.modalPresentationStyle = .overFullScreen
let guideM = UINavigationController(rootViewController: guide)
guideM.modalPresentationStyle = .overFullScreen
guide.callblock = {[weak self] in
guide.dismiss(animated: true)
guard let self = self else { return }
let example = PMWidgetExampleController()
example.modalPresentationStyle = .overFullScreen
self.present(example, animated: true)
guide.navigationController?.pushViewController(example, animated: true)
}
self.present(guide, animated: true)
self.present(guideM, animated: true)
}
}
......
......@@ -172,6 +172,7 @@ extension EmailFilterController : UITableViewDelegate,UITableViewDataSource {
cell.callblock = {[weak self] select in
guard let self = self else { return }
self.filter.date = indexPath.row
self.vibrate()
tableView.reloadData()
}
cell.selectionStyle = .none
......
......@@ -57,7 +57,80 @@ extension UILabel {
}
}
var handleTextTapKey = "handleTextTapKey"
var ClickLabelTextsJey = "ClickLabelTextsJey"
extension UILabel {
private var handleTextTap:((String)->Void)? {
set {
objc_setAssociatedObject(self, &handleTextTapKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, &handleTextTapKey) as? ((String) -> Void)
}
}
private var ClickLabelTexts:[String]? {
set {
objc_setAssociatedObject(self, &ClickLabelTextsJey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, &ClickLabelTextsJey) as? [String]
}
}
public func AddTapped(_ ClickStrs:[String],handle:@escaping((String)->Void)) -> Void {
if ClickLabelTexts?.count ?? 0 > 0 {
return
}
let tap = UITapGestureRecognizer(target: self, action: #selector(labelTapped(_:)))
self.addGestureRecognizer(tap)
self.isUserInteractionEnabled = true
ClickLabelTexts = ClickStrs
handleTextTap = handle
}
func frameForStringRange(_ range: NSRange) -> CGRect? {
guard let attributedText = attributedText else { return nil }
let textStorage = NSTextStorage(attributedString: attributedText)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
layoutManager.characterRange(
forGlyphRange: range,
actualGlyphRange: &glyphRange
)
return layoutManager.boundingRect(
forGlyphRange: glyphRange,
in: textContainer
)
}
@objc private func labelTapped(_ gesture: UITapGestureRecognizer) {
self.layoutIfNeeded()
// 判断点击范围
for text in (ClickLabelTexts ?? []) {
if let range = self.text?.range(of: text) {
let nsRange = NSRange(range, in: self.text ?? "")
guard var subStringFrame = self.frameForStringRange(nsRange) else { return }
let locationInLabel = gesture.location(in: self)
if CGRectContainsPoint(subStringFrame, locationInLabel) {
handleTextTap?(text)
}
}
}
}
func isTapLocationInTextRange(_ gesture: UITapGestureRecognizer, range: NSRange) -> Bool {
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero)
......
......@@ -7,6 +7,8 @@
import Foundation
import AudioToolbox
import CoreHaptics
extension Int {
......@@ -122,15 +124,15 @@ extension CGFloat {
extension NSObject {
public func vibrate(_ all:Bool? = false) -> Void {
public func vibrate(_ all:Bool? = nil) -> Void {
if all != nil {
DispatchQueue.main.async {
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
HapticManager.share.triggerHapticFeedback()
}
}else{
if SettingConfiguration.share.config.vibration ?? false {
DispatchQueue.main.async {
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
HapticManager.share.triggerHapticFeedback()
}
}
}
......@@ -153,3 +155,38 @@ extension UISwitch {
}
}
class HapticManager {
static let share = HapticManager()
private var engine: CHHapticEngine?
init() {
setupHapticEngine()
}
func setupHapticEngine() {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
do {
engine = try CHHapticEngine()
try engine?.start()
generator.prepare()
} catch {
print("引擎初始化失败: \(error)")
}
}
private lazy var generator: UIImpactFeedbackGenerator = {
let genter = UIImpactFeedbackGenerator(style: .heavy)
genter.prepare()
return genter
}()
// 触发震动反馈
func triggerHapticFeedback() {
generator.impactOccurred()
}
}
......@@ -19,6 +19,8 @@
<string>684306808588-cl093f79dogls1a608bh8oclk3ia0rig.apps.googleusercontent.com</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>wechat</string>
<string>weixin</string>
<string>google</string>
<string>googlemail</string>
<string>googlegmail</string>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment