@ -51,11 +51,8 @@ struct QRCodeStyleView: View {
// L o g o 选 择
// L o g o 选 择
@ State private var selectedLogo : QRCodeLogo ? = nil
@ State private var selectedLogo : QRCodeLogo ? = nil
@ State private var customLogoImage : UIImage ? = nil
@ State private var customLogoImage : UIImage ? = nil
@ State private var photoPickerItem : Any ? = nil
@ State private var photoLibraryAccessGranted = false
@ State private var photoLibraryAccessGranted = false
@ State private var showingImagePicker = false
@ State private var showingImagePicker = false
@ State private var showingImageCropper = false
@ State private var imageToCrop : UIImage ? = nil
// 生 成 的 二 维 码 图 片
// 生 成 的 二 维 码 图 片
@ State private var qrCodeImage : UIImage ?
@ State private var qrCodeImage : UIImage ?
@ -95,7 +92,7 @@ struct QRCodeStyleView: View {
let cgImage = customLogoImage . cgImage {
let cgImage = customLogoImage . cgImage {
// 使 用 自 定 义 L o g o
// 使 用 自 定 义 L o g o
print ( " 应用自定义Logo, CGImage大小: \( cgImage . width ) x \( cgImage . height ) " )
print ( " 应用自定义Logo, CGImage大小: \( cgImage . width ) x \( cgImage . height ) " )
d . logoTemplate = QRCode . LogoTemplate . CircleCenter ( image : cgImage )
d . logoTemplate = QRCode . LogoTemplate . CircleCenter ( image : cgImage , inset : 0 )
} else if let selectedLogo = selectedLogo ,
} else if let selectedLogo = selectedLogo ,
let logoImage = selectedLogo . image ,
let logoImage = selectedLogo . image ,
let cgImage = logoImage . cgImage {
let cgImage = logoImage . cgImage {
@ -131,19 +128,14 @@ struct QRCodeStyleView: View {
checkPhotoLibraryPermission ( )
checkPhotoLibraryPermission ( )
}
}
. sheet ( isPresented : $ showingImagePicker ) {
. sheet ( isPresented : $ showingImagePicker ) {
ImagePicker { image in
ImagePicker (
imageToCrop = image
onImageSelected : { image in
showingImageCropper = true
customLogoImage = image
}
}
. sheet ( isPresented : $ showingImageCropper ) {
if let imageToCrop = imageToCrop {
ImageCropperView ( image : imageToCrop ) { croppedImage in
customLogoImage = croppedImage
selectedLogo = nil // 清 除 预 设 L o g o 选 择
selectedLogo = nil // 清 除 预 设 L o g o 选 择
self . imageToCrop = nil
} ,
}
shouldProcessImage : true ,
}
targetSize : CGSize ( width : 80 , height : 80 )
)
}
}
}
}
@ -640,193 +632,187 @@ struct QRCodeStyleView: View {
}
}
}
}
// MARK: - 图 片 裁 剪 视 图
struct ImageCropperView : View {
let image : UIImage
// MARK: - 图 片 选 择 器
let onCropComplete : ( UIImage ) -> Void
struct ImagePicker : UIViewControllerRepresentable {
let onImageSelected : ( UIImage ) -> Void
let shouldProcessImage : Bool
let targetSize : CGSize ?
@ Environment ( \ . dismiss ) private var dismiss
init ( onImageSelected : @ escaping ( UIImage ) -> Void , shouldProcessImage : Bool = false , targetSize : CGSize ? = nil ) {
@ State private var scale : CGFloat = 1.0
self . onImageSelected = onImageSelected
@ State private var lastScale : CGFloat = 1.0
self . shouldProcessImage = shouldProcessImage
@ State private var offset : CGSize = . zero
self . targetSize = targetSize
@State private var lastOffset : CGSize = . zero
}
var body : some View {
func makeUIViewController ( context : Context ) -> UIImagePickerController {
NavigationView {
let picker = UIImagePickerController ( )
GeometryReader { geometry in
picker . delegate = context . coordinator
ZStack {
picker . sourceType = . photoLibrary
Color . black
picker . allowsEditing = false
. ignoresSafeArea ( )
return picker
}
func updateUIViewController ( _ uiViewController : UIImagePickerController , context : Context ) { }
func makeCoordinator ( ) -> Coordinator {
Coordinator ( self )
}
class Coordinator : NSObject , UIImagePickerControllerDelegate , UINavigationControllerDelegate {
let parent : ImagePicker
init ( _ parent : ImagePicker ) {
self . parent = parent
}
func imagePickerController ( _ picker : UIImagePickerController , didFinishPickingMediaWithInfo info : [ UIImagePickerController . InfoKey : Any ] ) {
if let image = info [ . originalImage ] as ? UIImage {
let finalImage : UIImage
if parent . shouldProcessImage , let targetSize = parent . targetSize {
// 自 动 处 理 图 片 : 截 取 中 心 正 方 形 并 缩 放 到 指 定 大 小
finalImage = processImageToSquare ( image : image , targetSize : targetSize )
VStack {
// 打 印 最 终 图 片 的 内 存 大 小
// 裁 剪 区 域
let memorySize = calculateImageMemorySize ( image : finalImage )
ZStack {
print ( " 📊 最终自定义图片内存大小: \( memorySize ) " )
// 背 景 图 片
} else {
Image ( uiImage : image )
// 不 处 理 图 片 , 直 接 使 用 原 图
. resizable ( )
finalImage = image
. aspectRatio ( contentMode : . fit )
. scaleEffect ( scale )
. offset ( offset )
. gesture (
SimultaneousGesture (
MagnificationGesture ( )
. onChanged { value in
let delta = value / lastScale
lastScale = value
scale = min ( max ( scale * delta , 0.5 ) , 3.0 )
}
. onEnded { _ in
lastScale = 1.0
} ,
DragGesture ( )
. onChanged { value in
let delta = CGSize (
width : value . translation . width - lastOffset . width ,
height : value . translation . height - lastOffset . height
)
lastOffset = value . translation
offset = CGSize (
width : offset . width + delta . width ,
height : offset . height + delta . height
)
}
. onEnded { _ in
lastOffset = . zero
}
)
)
// 裁 剪 框
CropOverlay ( )
}
. frame ( height : geometry . size . width ) // 保 持 正 方 形
. clipped ( )
Spacer ( )
// 提 示 文 字
Text ( " 拖动和缩放来选择圆形Logo区域 " )
. foregroundColor ( . white )
. font ( . caption )
. padding ( . bottom , 20 )
}
}
}
. navigationTitle ( " 裁剪圆形Logo " )
. navigationBarTitleDisplayMode ( . inline )
. toolbar {
ToolbarItem ( placement : . navigationBarLeading ) {
Button ( " 取消 " ) {
dismiss ( )
}
. foregroundColor ( . white )
}
}
ToolbarItem ( placement : . navigationBarTrailing ) {
parent . onImageSelected ( finalImage )
Button ( " 完成 " ) {
let croppedImage = cropImage ( )
onCropComplete ( croppedImage )
dismiss ( )
}
. foregroundColor ( . white )
. font ( . system ( size : 16 , weight : . semibold ) )
}
}
}
picker . dismiss ( animated : true )
}
}
}
private func cropImage ( ) -> UIImage {
let renderer = UIGraphicsImageRenderer ( size : CGSize ( width : 80 , height : 80 ) )
return renderer . image { context in
func imagePickerControllerDidCancel ( _ picker : UIImagePickerController ) {
// 计 算 裁 剪 区 域
picker . dismiss ( animated : true )
let imageSize = image . size
}
let viewSize = CGSize ( width : 80 , height : 80 )
// 处 理 图 片 为 正 方 形 并 缩 放 到 指 定 大 小 , 同 时 压 缩 到 5 K B 以 下
private func processImageToSquare ( image : UIImage , targetSize : CGSize ) -> UIImage {
let originalSize = image . size
// 计 算 图 片 在 视 图 中 的 实 际 大 小 和 位 置
// 计 算 正 方 形 边 长 ( 取 较 小 的 边 )
let imageAspectRatio = imageSize . width / imageSize . height
let squareSize = min ( originalSize . width , originalSize . height )
let viewAspectRatio = viewSize . width / viewSize . height
let scaledImageSize : CGSize
// 计 算 裁 剪 区 域 ( 居 中 裁 剪 )
let scaledImageOffset : CGPoint
let cropX = ( originalSize . width - squareSize ) / 2
let cropY = ( originalSize . height - squareSize ) / 2
let cropRect = CGRect ( x : cropX , y : cropY , width : squareSize , height : squareSize )
if imageAspectRatio > viewAspectRatio {
// 裁 剪 图 片
// 图 片 更 宽 , 以 高 度 为 准
guard let cgImage = image . cgImage ? . cropping ( to : cropRect ) else {
scaledImageSize = CGSize ( width : viewSize . height * imageAspectRatio , height : viewSize . height )
return image
scaledImageOffset = CGPoint ( x : ( viewSize . width - scaledImageSize . width ) / 2 , y : 0 )
} else {
// 图 片 更 高 , 以 宽 度 为 准
scaledImageSize = CGSize ( width : viewSize . width , height : viewSize . width / imageAspectRatio )
scaledImageOffset = CGPoint ( x : 0 , y : ( viewSize . height - scaledImageSize . height ) / 2 )
}
}
// 应 用 缩 放 和 偏 移
// 创 建 新 的 U I I m a g e
let finalImageSize = CGSize (
let croppedImage = UIImage ( cgImage : cgImage )
width : scaledImageSize . width * scale ,
height : scaledImageSize . height * scale
)
let finalImageOffset = CGPoint (
// 缩 放 到 目 标 大 小
x : scaledImageOffset . x + offset . width ,
let renderer = UIGraphicsImageRenderer ( size : targetSize )
y : scaledImageOffset . y + offset . height
let scaledImage = renderer . image { context in
)
croppedImage . draw ( in : CGRect ( origin : . zero , size : targetSize ) )
}
// 计 算 裁 剪 区 域
// 压 缩 图 片 到 5 K B 以 下
let cropRect = CGRect (
return compressImageToTargetSize ( scaledImage , targetSizeInKB : 5.0 )
x : - finalImageOffset . x ,
}
y : - finalImageOffset . y ,
width : finalImageSize . width ,
// 压 缩 图 片 到 指 定 大 小 以 下
height : finalImageSize . height
private func compressImageToTargetSize ( _ image : UIImage , targetSizeInKB : Double ) -> UIImage {
)
let targetSizeInBytes = Int64 ( targetSizeInKB * 1024 )
// 尝 试 不 同 的 压 缩 质 量
let compressionQualities : [ CGFloat ] = [ 0.8 , 0.6 , 0.4 , 0.2 , 0.1 , 0.05 ]
// 创 建 圆 形 裁 剪 路 径 - 半 径 和 正 方 形 宽 度 一 样
for quality in compressionQualities {
let circlePath = UIBezierPath ( ovalIn : CGRect ( x : 0 , y : 0 , width : 80 , height : 80 ) )
if let imageData = image . jpegData ( compressionQuality : quality ) {
circlePath . addClip ( )
let dataSize = Int64 ( imageData . count )
if dataSize <= targetSizeInBytes {
// 如 果 压 缩 后 的 数 据 大 小 符 合 要 求 , 重 新 创 建 U I I m a g e
if let compressedImage = UIImage ( data : imageData ) {
print ( " ✅ 图片压缩成功: \( dataSize ) bytes (质量: \( quality ) ) " )
return compressedImage
}
}
}
}
// 绘 制 裁 剪 后 的 图 片
// 如 果 J P E G 压 缩 仍 然 太 大 , 尝 试 进 一 步 缩 小 尺 寸
image . draw ( in : cropRect )
print ( " ⚠️ JPEG压缩后仍超过目标大小, 尝试缩小尺寸 " )
return compressImageByReducingSize ( image , targetSizeInBytes : targetSizeInBytes )
}
}
}
}
// 通 过 缩 小 尺 寸 来 压 缩 图 片
private func compressImageByReducingSize ( _ image : UIImage , targetSizeInBytes : Int64 ) -> UIImage {
// MARK: - 裁 剪 覆 盖 层
let originalSize = image . size
struct CropOverlay : View {
let originalWidth = originalSize . width
var body : some View {
let originalHeight = originalSize . height
GeometryReader { geometry in
ZStack {
// 计 算 当 前 图 片 的 内 存 大 小
// 半 透 明 遮 罩
let currentMemorySize = Int64 ( originalWidth * originalHeight * 4 ) // 假 设 R G B A 格 式
Color . black . opacity ( 0.5 )
. mask (
// 计 算 需 要 的 缩 放 比 例
Rectangle ( )
let scaleFactor = sqrt ( Double ( targetSizeInBytes ) / Double ( currentMemorySize ) )
. overlay (
let newWidth = max ( originalWidth * CGFloat ( scaleFactor ) , 40 ) // 最 小 4 0 像 素
Circle ( )
let newHeight = max ( originalHeight * CGFloat ( scaleFactor ) , 40 )
. frame (
width : min ( geometry . size . width , geometry . size . height ) * 0.8 ,
let newSize = CGSize ( width : newWidth , height : newHeight )
height : min ( geometry . size . width , geometry . size . height ) * 0.8
)
// 重 新 渲 染 到 新 尺 寸
. blendMode ( . destinationOut )
let renderer = UIGraphicsImageRenderer ( size : newSize )
)
let resizedImage = renderer . image { context in
)
image . draw ( in : CGRect ( origin : . zero , size : newSize ) )
}
// 圆 形 裁 剪 框 边 框
Circle ( )
// 再 次 尝 试 J P E G 压 缩
. stroke ( Color . white , lineWidth : 2 )
if let imageData = resizedImage . jpegData ( compressionQuality : 0.3 ) {
. frame (
let finalSize = Int64 ( imageData . count )
width : min ( geometry . size . width , geometry . size . height ) * 0.8 ,
print ( " ✅ 通过缩小尺寸压缩成功: \( finalSize ) bytes (新尺寸: \( newWidth ) x \( newHeight ) ) " )
height : min ( geometry . size . width , geometry . size . height ) * 0.8
)
// 圆 形 裁 剪 框 四 角 标 记
if let finalImage = UIImage ( data : imageData ) {
ForEach ( 0. . < 4 ) { corner in
return finalImage
Circle ( )
. fill ( Color . white )
. frame ( width : 6 , height : 6 )
. offset (
x : corner % 2 = = 0 ? - min ( geometry . size . width , geometry . size . height ) * 0.4 : min ( geometry . size . width , geometry . size . height ) * 0.4 ,
y : corner < 2 ? - min ( geometry . size . width , geometry . size . height ) * 0.4 : min ( geometry . size . width , geometry . size . height ) * 0.4
)
}
}
}
}
// 如 果 还 是 太 大 , 返 回 最 小 尺 寸 的 图 片
print ( " ⚠️ 无法压缩到目标大小,返回最小尺寸图片 " )
let minSize = CGSize ( width : 40 , height : 40 )
let rendererMin = UIGraphicsImageRenderer ( size : minSize )
return rendererMin . image { context in
image . draw ( in : CGRect ( origin : . zero , size : minSize ) )
}
}
// 计 算 图 片 的 内 存 大 小
private func calculateImageMemorySize ( image : UIImage ) -> String {
guard let cgImage = image . cgImage else {
return " 无法计算 "
}
let width = cgImage . width
let height = cgImage . height
let bitsPerComponent = cgImage . bitsPerComponent
let bytesPerRow = cgImage . bytesPerRow
let colorSpace = cgImage . colorSpace
// 计 算 内 存 大 小 ( 字 节 )
let memorySizeInBytes = height * bytesPerRow
// 转 换 为 更 易 读 的 格 式
let formatter = ByteCountFormatter ( )
formatter . allowedUnits = [ . useKB , . useMB ]
formatter . countStyle = . memory
let memorySizeString = formatter . string ( fromByteCount : Int64 ( memorySizeInBytes ) )
// 返 回 详 细 信 息
return " \( memorySizeString ) ( \( width ) x \( height ) , \( bitsPerComponent ) bits/component, \( bytesPerRow ) bytes/row) "
}
}
}
}
}
}