Commit 8b5f7b67 authored by CZ1004's avatar CZ1004

Merge branch 'develop_0409' into Advertisement

* develop_0409:
  优化相似度判断
  修改pod配置 解决模拟器无法运行问题
  修改代码
  修改链接
  修改追踪权限
  内购界面
  内购界面 修改
  内购 样式修改

# Conflicts:
#	PhoneManager/Class/Session/Pay/ViewController/HomePayViewController.swift
#	PhoneManager/Info.plist
parents 68493861 5712ddc8
......@@ -105,7 +105,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
self.dataSiFileSize = model.titleModelArray[1].allFileSize
self.dataScFileSize = model.otherModelArray[1].allFileSize
// 更新视频数据
let videoCount = model.otherModelArray[0].assets.first?.count
// 查看是否有更新
if videoCount != PhotoAndVideoMananger.mananger.videoAssets.count {
......@@ -186,14 +186,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
PhotoDataManager.manager.saveToFileSystem(model: model)
let dataUpdated = Notification.Name("HomeSimilarScreenshotResourceUpdate")
NotificationCenter.default.post(name: dataUpdated, object: nil)
} completion: { similarGroups in
Print("更新相似截图数据结束")
}
}
let imageCount = model.otherModelArray[4].assets.first?.count
if imageCount != PhotoAndVideoMananger.mananger.otherAssets.count{
Print("更新照片数据")
......@@ -257,7 +257,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Print("更新相似图片数据结束")
}
}
}catch{
Print("获取首页数据失败")
PhotoDataManager.manager.loadDataFromPhotos { model in}
......
......@@ -6,7 +6,7 @@
//
import UIKit
import AppTrackingTransparency
class HomeViewController:BaseViewController {
......@@ -49,7 +49,8 @@ class HomeViewController:BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 调用下追踪权限
checkTrackingAuthorization()
let dataUpdated = Notification.Name("DataUpdatedNotification")
......@@ -391,3 +392,58 @@ class HomeViewController:BaseViewController {
}
}
extension HomeViewController {
// 检查跟踪授权状态
func checkTrackingAuthorization() {
if #available(iOS 14, *) {
let status = ATTrackingManager.trackingAuthorizationStatus
switch status {
case .authorized:
// 用户已授权跟踪
print("用户已授权应用进行跟踪")
case .denied:
// 用户拒绝了跟踪请求
print("用户拒绝了应用的跟踪请求")
case .restricted:
// 由于系统限制,无法跟踪用户
print("由于系统限制,无法跟踪用户")
case .notDetermined:
// 用户尚未对跟踪请求做出决定
print("用户尚未对跟踪请求做出决定,再次请求授权")
requestTrackingAuthorization()
@unknown default:
break
}
} else {
// iOS 14 以下系统不支持 ATT 框架
// 可以执行其他操作
}
}
func requestTrackingAuthorization() {
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
switch status {
case .authorized:
// 用户已授权跟踪
print("用户已授权应用进行跟踪")
case .denied:
// 用户拒绝了跟踪请求
print("用户拒绝了应用的跟踪请求")
case .restricted:
// 由于系统限制,无法跟踪用户
print("由于系统限制,无法跟踪用户")
case .notDetermined:
// 用户尚未对跟踪请求做出决定
print("用户尚未对跟踪请求做出决定")
@unknown default:
break
}
})
} else {
// iOS 14 以下系统不支持 ATT 框架
// 可以执行其他操作
}
}
}
......@@ -159,6 +159,7 @@ class HomeNoAdsViewController: UIViewController, NoAdsStackDataSource {
sview.setTitle(content, for: .normal)
sview.setTitleColor(color, for: .normal)
sview.titleLabel?.font = font
sview.isSelected = false;
sview.sizeToFit()
let attributes: [NSAttributedString.Key: Any] = [
......@@ -168,7 +169,7 @@ class HomeNoAdsViewController: UIViewController, NoAdsStackDataSource {
]
let attributedString = NSAttributedString(string: content, attributes: attributes)
sview.setAttributedTitle(attributedString, for: .normal)
sview.addTarget(self, action: #selector(terms), for: .touchUpInside)
sview.addTarget(self, action: #selector(terms(_:)), for: .touchUpInside)
sview.sizeToFit()
contentScroll.addSubview(sview)
return sview
......@@ -199,6 +200,41 @@ class HomeNoAdsViewController: UIViewController, NoAdsStackDataSource {
return sview
}()
private lazy var privavye_Label: UILabel = {
let priva = UILabel()
priva.font = UIFont.systemFont(ofSize: 14)
priva.textColor = .gray
priva.text = ""
priva.numberOfLines = 0
priva.clipsToBounds = true
priva.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(labelTapped(_:)))
priva.addGestureRecognizer(tap)
contentScroll.addSubview(priva)
return priva
}()
@objc private func labelTapped(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: privavye_Label)
let strs:NSString = "privacy policy"
let frssss = strs.boundingRect(with: CGSizeMake(.infinity, .infinity), options: .truncatesLastVisibleLine, attributes: [NSAttributedString.Key.font : privavye_Label.font], context: nil)
let frame1 = CGRectMake(0, privavye_Label.height-25, CGRectGetWidth(frssss), 25)
let frame2 = CGRectMake(CGRectGetWidth(frssss)+20, privavye_Label.height-25, 90, 25)
if frame1.contains(location) {
Print("点击了隐私")
let vc:PrivacyPolicyWebViewController = PrivacyPolicyWebViewController()
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}else if frame2.contains(location){
Print("点击了terms")
let vc:TermOfUseWebViewController = TermOfUseWebViewController()
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
}
}
private let data:[[String:String]] = [
["icon":"ic_unsel_com","t":"Intelligent cleaning of similar photos"],
["icon":"ic_unsel_com","t":"Unlimited usage times"],
......@@ -265,12 +301,32 @@ extension HomeNoAdsViewController : UIScrollViewDelegate {
HomePayModel.share.restore()
}
@objc func terms() -> Void {
@objc func terms(_ sender:UIButton) -> Void {
DispatchQueue.main.async {[weak self] in
guard let self else {return}
let vc:TermOfUseWebViewController = TermOfUseWebViewController()
vc.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(vc, animated: true)
sender.isSelected = !sender.isSelected
let strs:NSString = sender.isSelected ? "We offer 1-year and lifetime subscriptions. The price is clearly displayed within the application.\n\n·After the purchase confirmation, your iTunes account will be charged.\n\n·Unless the automatic update is turned off at least 24 hours before the end of the current cycle, the subscription will be automatically updated.\n\n·Your account will be renewed within 24 hours before the end of the current cycle cost.\n\n·You can go to the \"Account Settings\" in the iTunes store to manage your subscriptions and turn off auto renewal.\n\n·If provided, if you choose to use our free trial version, any unused portion during the free trial period will become invalid when you purchase a publication subscription, if applicable.\n\n·If you choose not to purchase the AI PhoneManager Pro version, you can continue to use it for free and enjoy PhoneManager.\n\nYour personal data is securely stored in PhoneManager, please make sure to read our \nprivacy policy and terms of use." : ""
let attribtit = NSMutableAttributedString(string:strs as String , attributes: [:])
if strs.length > 3 {
let rang = strs.range(of: "privacy policy")
attribtit.addAttributes([NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 14),NSAttributedString.Key.underlineStyle:NSUnderlineStyle.single.rawValue,.link:"appscheme://private"], range: rang)
let rang1 = strs.range(of: "terms of use")
attribtit.addAttributes([NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 14),NSAttributedString.Key.underlineStyle:NSUnderlineStyle.single.rawValue,.link:"appscheme://terms"], range: rang1)
}
privavye_Label.attributedText = attribtit
self.view.layoutIfNeeded()
DispatchQueue.main.async {
let height = CGRectGetMaxY(self.privavye_Label.frame)
self.contentScroll.contentSize = CGSize(width: 0, height: height + 30)
if self.privavye_Label.attributedText?.length ?? 0 > 10 {
UIView.animate(withDuration: 0.1) {
self.contentScroll.contentOffset = CGPoint(x: 0, y: self.contentScroll.contentSize.height - self.contentScroll.height)
}
}
}
}
}
......@@ -343,8 +399,8 @@ extension HomeNoAdsViewController : UIScrollViewDelegate {
private func setUI() -> Void {
topBackimg.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset(-20)
make.left.right.top.equalToSuperview()
make.height.equalTo(topBackimg.snp.width).multipliedBy(182.0/375.0)
}
closeBtn.snp.makeConstraints { make in
......@@ -401,7 +457,7 @@ extension HomeNoAdsViewController : UIScrollViewDelegate {
buybut.snp.makeConstraints { make in
make.left.right.equalTo(stack)
make.top.equalTo(forever.snp.bottom).offset(20)
make.height.equalTo(46)
make.height.equalTo(65)
}
ppBtn.snp.makeConstraints { make in
make.top.equalTo(buybut.snp.bottom).offset(10)
......@@ -411,12 +467,18 @@ extension HomeNoAdsViewController : UIScrollViewDelegate {
make.centerY.equalTo(ppBtn)
make.right.equalTo(buybut.snp.right)
}
privavye_Label.snp.makeConstraints { make in
make.left.equalTo(ppBtn.snp.left)
make.right.equalTo(restoreBtn.snp.right)
make.top.equalTo(ppBtn.snp.bottom).offset(15)
}
self.type = 0
self.view.layoutIfNeeded()
DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
self.buybut.layer.cornerRadius = CGRectGetHeight(self.buybut.frame) / 2.0
self.scroll.contentSize = CGSize(width: CGRectGetWidth(self.scroll.frame) * 3.0, height: 0)
let height = CGRectGetMaxY(self.restoreBtn.frame)
let height = CGRectGetMaxY(self.privavye_Label.frame)
self.contentScroll.contentSize = CGSize(width: 0, height: height + 30)
}
}
......
......@@ -24,9 +24,12 @@ class HomePayViewController:UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
view.backgroundColor = .white
addViews()
storeKeD()
scroll.snp.makeConstraints { make in
make.left.right.top.bottom.equalToSuperview()
}
}
override func viewWillAppear(_ animated: Bool) {
......@@ -38,17 +41,26 @@ class HomePayViewController:UIViewController {
super.viewDidAppear(animated)
homePayView?.playAnimationWithDelay()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
doneBlock()
}
lazy var scroll: UIScrollView = {
let scroll = UIScrollView()
scroll.backgroundColor = .clear
scroll.contentInsetAdjustmentBehavior = .never
view.addSubview(scroll)
return scroll
}()
private func addViews() {
homePayView = HomePayView(frame: view.bounds)
view.addSubview(homePayView!)
scroll.addSubview(homePayView!)
homePayView?.snp.makeConstraints { make in
make.left.top.equalToSuperview()
make.width.equalTo(scroll.snp.width)
}
homePayView?.callBack = {[weak self] status in
guard let self else {return}
if let operstatus = status as? OperStatus {
......@@ -71,11 +83,21 @@ class HomePayViewController:UIViewController {
self.payTouch()
break
case .swit:
break
case .restore:
self.restoreClick()
break
case .change:
DispatchQueue.main.async {
let contentSize = CGRectGetHeight(self.homePayView?.frame ?? CGRect())
self.scroll.contentSize = CGSizeMake(0, contentSize + (cWindow?.safeAreaInsets.bottom ?? 20) + 5)
if self.homePayView?.privavye_Label.attributedText?.length ?? 0 > 10 {
UIView.animate(withDuration: 0.1) {
self.scroll.contentOffset = CGPoint(x: 0, y: self.scroll.contentSize.height - self.scroll.height)
}
}
}
break
}
}
}
......
......@@ -15,7 +15,7 @@ class PrivacyPolicyWebViewController:BaseWebViewController {
titleView.model.title = "Privacy Policy"
LoadWithUrl(url: "https://sites.google.com/view/test-ios-use/home")
LoadWithUrl(url: "https://app.intelliadvert.com/privacy")
view.backgroundColor = .white
webView.width = ScreenW - 20.RW()
......
......@@ -15,7 +15,7 @@ class TermOfUseWebViewController:BaseWebViewController {
titleView.model.title = "Term of Use"
LoadWithUrl(url: "https://sites.google.com/view/testiosterms/home")
LoadWithUrl(url: "https://app.intelliadvert.com/terms")
view.backgroundColor = .white
webView.width = ScreenW - 20.RW()
......
......@@ -55,6 +55,9 @@ class PhotoAndVideoMananger {
var ids:[String] = []
// 定义
private let hashDistance = 100
func setAssets() {
let fetchOptions = PHFetchOptions()
......@@ -1009,66 +1012,83 @@ class PhotoAndVideoMananger {
// 计算两个哈希值的汉明距离
func hammingDistance(_ hash1: String, _ hash2: String) -> Int {
var distance = 0
for (char1, char2) in zip(hash1, hash2) {
let int1 = Int(String(char1), radix: 16)!
let int2 = Int(String(char2), radix: 16)!
let xor = int1 ^ int2
distance += String(xor, radix: 2).filter { $0 == "1" }.count
}
return distance
// var distance = 0
// for (char1, char2) in zip(hash1, hash2) {
// let int1 = Int(String(char1), radix: 16)!
// let int2 = Int(String(char2), radix: 16)!
// let xor = int1 ^ int2
// distance += String(xor, radix: 2).filter { $0 == "1" }.count
// }
// return distance
guard hash1.count == hash2.count else { return Int.max }
return zip(hash1, hash2).filter { $0 != $1 }.count
}
func groupSimilarImages(assets: [PHAsset], progressCompletion: @escaping ([[AssetModel]]) -> Void, completion: @escaping ([[AssetModel]]) -> Void) {
print("开始处理相似图片")
DispatchQueue.global().async {
print("进入异步任务处理相似图片")
var assetModels: [AssetModel] = []
var hashes: [String: AssetModel] = [:]
var groupedImages: [[AssetModel]] = []
let dispatchGroup = DispatchGroup()
for asset in assets {
_ = asset.pixelWidth * asset.pixelHeight
// 创建 AssetModel
let createDate = asset.creationDate ?? Date()
let model = AssetModel(localIdentifier: asset.localIdentifier, assetSize: self.findAssetSize(asset: asset), createDate: createDate)
assetModels.append(model)
let manager = PHImageManager.default()
manager.requestImage(for: asset, targetSize: CGSize(width: 32, height: 32), contentMode: .aspectFit, options: nil) { (image, _) in
// 请求图像
dispatchGroup.enter()
PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 32, height: 32), contentMode: .aspectFit, options: nil) { (image, _) in
if let image = image {
let hash = self.pHash(for: image)
if hash != "" {
let hash = self.calculateImageHashUsingCoreImage(image)
if !hash.isEmpty {
hashes[hash] = model
}
}
dispatchGroup.leave()
}
}
var usedHashes: Set<String> = []
for (hash1, model1) in hashes {
if usedHashes.contains(hash1) { continue }
var similarModels: [AssetModel] = [model1]
usedHashes.insert(hash1)
for (hash2, model2) in hashes {
if usedHashes.contains(hash2) { continue }
let distance = self.hammingDistance(hash1, hash2)
if distance < 4 { // 汉明距离小于 5 认为相似
similarModels.append(model2)
usedHashes.insert(hash2)
// 等待所有请求完成后进行比较
dispatchGroup.notify(queue: .global()) {
print("获取到全部hash值")
var usedHashes: Set<String> = []
for (hash1, model1) in hashes {
if usedHashes.contains(hash1) { continue }
var similarModels: [AssetModel] = [model1]
usedHashes.insert(hash1)
for (hash2, model2) in hashes {
if usedHashes.contains(hash2) { continue }
let distance = self.hammingDistance(hash1, hash2)
if distance < self.hashDistance { // 可以根据需求调整阈值
similarModels.append(model2)
usedHashes.insert(hash2)
}
}
if similarModels.count >= 2 {
groupedImages.append(similarModels)
// 每次找到新的相似组,通过 progressCompletion 回调返回当前已处理好的分组数据
print("判断相似", similarModels)
DispatchQueue.main.async {
progressCompletion(groupedImages)
}
}
}
if similarModels.count >= 2 {
groupedImages.append(similarModels)
// 每次找到新的相似组,通过 progressCompletion 回调返回当前已处理好的分组数据
DispatchQueue.main.async {
progressCompletion(groupedImages)
}
DispatchQueue.main.async {
completion(groupedImages)
}
}
DispatchQueue.main.async {
completion(groupedImages)
}
}
}
}
......@@ -1148,3 +1168,69 @@ extension Array {
}
}
}
extension PhotoAndVideoMananger{
// 计算图片的感知哈希值
func calculateImageHashUsingCoreImage(_ image: UIImage) -> String {
guard let cgImage = image.cgImage else { return "" }
// 生成CIImage
let ciImage = CIImage(cgImage: cgImage)
// 创建滤镜:灰度化图像
let filter = CIFilter(name: "CIPhotoEffectNoir")!
filter.setValue(ciImage, forKey: kCIInputImageKey)
guard let outputImage = filter.outputImage else {
return ""
}
// 将输出图像缩放到32x32
let context = CIContext()
let targetSize = CGSize(width: 32, height: 32)
let scaledImage = context.createCGImage(outputImage, from: outputImage.extent)!
let resizedImage = UIImage(cgImage: scaledImage, scale: 1.0, orientation: image.imageOrientation)
// 获取像素数据
guard let pixelData = resizedImage.cgImage?.dataProvider?.data,
let data = CFDataGetBytePtr(pixelData) else {
return ""
}
// 计算灰度像素平均值
var pixels: [UInt8] = Array(repeating: 0, count: 1024) // 32 * 32
for i in 0..<32 {
for j in 0..<32 {
let pixelIndex = (i * 32 + j) * 4 // RGBA
let r = data[pixelIndex]
let g = data[pixelIndex + 1]
let b = data[pixelIndex + 2]
// 使用灰度公式转化
let gray = UInt8(0.299 * Double(r) + 0.587 * Double(g) + 0.114 * Double(b))
pixels[i * 32 + j] = gray
}
}
// 计算平均值
let sum = pixels.reduce(0) { UInt32($0) + UInt32($1) } // 使用 UInt32 来避免溢出
let average = UInt8(sum / UInt32(pixels.count)) // 确保将 sum 转换为 UInt32
// 生成哈希值
var hash = ""
for pixel in pixels {
hash += pixel > average ? "1" : "0"
}
return hash
}
// 计算汉明距离
func calculateHammingDistance(_ hash1: String, _ hash2: String) -> Int {
guard hash1.count == hash2.count else { return Int.max }
return zip(hash1, hash2).filter { $0 != $1 }.count
}
}
......@@ -25,6 +25,7 @@ enum CommonPush {
case swit
case pay
case restore
case change
}
enum tabbarType {
......
......@@ -56,3 +56,47 @@ extension UILabel {
self.width = width
}
}
extension UILabel {
func isTapLocationInTextRange(_ gesture: UITapGestureRecognizer, range: NSRange) -> Bool {
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero)
let textStorage = NSTextStorage(attributedString: self.attributedText ?? NSAttributedString())
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = self.lineBreakMode
textContainer.maximumNumberOfLines = self.numberOfLines
textContainer.size = self.bounds.size
let location = gesture.location(in: self)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let alignmentOffset = self.alignmentOffsetForText(in: textBoundingBox)
let touchLocation = CGPoint(
x: (location.x - alignmentOffset),
y: (location.y - textBoundingBox.minY)
)
let index = layoutManager.characterIndex(
for: touchLocation,
in: textContainer,
fractionOfDistanceBetweenInsertionPoints: nil
)
return NSLocationInRange(index, range)
}
private func alignmentOffsetForText(in rect: CGRect) -> CGFloat {
switch self.textAlignment {
case .left, .natural, .justified:
return 0
case .center:
return (self.bounds.width - rect.width) / 2
case .right:
return self.bounds.width - rect.width
@unknown default:
return 0
}
}
}
......@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>NSUserTrackingUsageDescription</key>
<string>此应用会使用广告标识符来提供个性化广告。</string>
<string>We need your permission to track your usage habits in order to provide a more personalized advertising experience</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
......
......@@ -14,5 +14,13 @@ target 'PhoneManager' do
pod 'SVProgressHUD'
pod 'Google-Mobile-Ads-SDK'
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' # Exclude arm64 for simulator
end
end
end
end
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