iOS 自定义 UIImagePickerController 相机裁剪为圆形 - 在预览视图中

rau*_*aul 3 camera uiimagepickercontroller ios

我正在使用此代码来制作自定义相机裁剪:

UIImagePickerController 编辑视图圆圈叠加

这在相机胶卷中效果很好,但不能拍照

如果我更改 [navigationController.viewControllers count] == 3 --> [navigationController.viewControllers count] == 1 也适用于相机,但不适用于下一个视图(您接受使用照片的预览视图)

谁来帮帮我??

-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 0) {
    NSLog(@"Camara");
    UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init];
    imagePicker.allowsEditing = YES;
    imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
    imagePicker.delegate = self;
    self.isCamera = YES;

    [self presentViewController:imagePicker animated:YES completion:nil];

}else{
    NSLog(@"Carrete");
    UIImagePickerController *imagePickerController = [[UIImagePickerController alloc]init];
    imagePickerController.allowsEditing = YES;
    imagePickerController.delegate = self;
    imagePickerController.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;
    self.isCamera = NO;
    [self presentViewController:imagePickerController animated:YES completion:nil];
}
Run Code Online (Sandbox Code Playgroud)

}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
if (self.isCamera) {
    if ([navigationController.viewControllers count] == 1)
    {
        CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;

        UIView *plCropOverlay = [[[viewController.view.subviews objectAtIndex:1]subviews] objectAtIndex:0];

        plCropOverlay.hidden = YES;

        int position = 0;

        if (screenHeight == 568)
        {
            position = 124;
        }
        else
        {
            position = 80;
        }

        CAShapeLayer *circleLayer = [CAShapeLayer layer];

        UIBezierPath *path2 = [UIBezierPath bezierPathWithOvalInRect:
                               CGRectMake(0.0f, position, 320.0f, 320.0f)];
        [path2 setUsesEvenOddFillRule:YES];

        [circleLayer setPath:[path2 CGPath]];

        [circleLayer setFillColor:[[UIColor clearColor] CGColor]];
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 320, screenHeight-72) cornerRadius:0];

        [path appendPath:path2];
        [path setUsesEvenOddFillRule:YES];

        CAShapeLayer *fillLayer = [CAShapeLayer layer];
        fillLayer.path = path.CGPath;
        fillLayer.fillRule = kCAFillRuleEvenOdd;
        fillLayer.fillColor = [UIColor blackColor].CGColor;
        fillLayer.opacity = 0.8;
        [viewController.view.layer addSublayer:fillLayer];

        UILabel *moveLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, 320, 50)];
        [moveLabel setText:@"Move and Scale"];
        [moveLabel setTextAlignment:NSTextAlignmentCenter];
        [moveLabel setTextColor:[UIColor whiteColor]];

        [viewController.view addSubview:moveLabel];
    }

}else{
    if ([navigationController.viewControllers count] == 3)
    {
        CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;

        UIView *plCropOverlay = [[[viewController.view.subviews objectAtIndex:1]subviews] objectAtIndex:0];

        plCropOverlay.hidden = YES;

        int position = 0;

        if (screenHeight == 568)
        {
            position = 124;
        }
        else
        {
            position = 80;
        }

        CAShapeLayer *circleLayer = [CAShapeLayer layer];

        UIBezierPath *path2 = [UIBezierPath bezierPathWithOvalInRect:
                               CGRectMake(0.0f, position, 320.0f, 320.0f)];
        [path2 setUsesEvenOddFillRule:YES];

        [circleLayer setPath:[path2 CGPath]];

        [circleLayer setFillColor:[[UIColor clearColor] CGColor]];
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 320, screenHeight-72) cornerRadius:0];

        [path appendPath:path2];
        [path setUsesEvenOddFillRule:YES];

        CAShapeLayer *fillLayer = [CAShapeLayer layer];
        fillLayer.path = path.CGPath;
        fillLayer.fillRule = kCAFillRuleEvenOdd;
        fillLayer.fillColor = [UIColor blackColor].CGColor;
        fillLayer.opacity = 0.8;
        [viewController.view.layer addSublayer:fillLayer];

        UILabel *moveLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, 320, 50)];
        [moveLabel setText:@"Move and Scale"];
        [moveLabel setTextAlignment:NSTextAlignmentCenter];
        [moveLabel setTextColor:[UIColor whiteColor]];

        [viewController.view addSubview:moveLabel];
    }

}
Run Code Online (Sandbox Code Playgroud)

}

Ril*_*eux 8

SwiftUI、Swift 5 中的类似解决方案

我在这个问题上苦苦思索了很长时间,但终于找到了一个可行的解决方案。

我使用 Swift 或 SwiftUI 编码的时间不长,我绝对欢迎提出意见来改进这段代码。在某些地方,我留下了一些调试代码,您可以取消注释。与有能力的、深思熟虑的方法相比,我对数学的思考涉及更多的尝试和错误!

此代码的缺点首先,最好从 ContentView() 打开图像选择器,然后显示我的自定义视图。我不知道该怎么做。其次,如果 ContentView() 中已有图像,则最好在自定义视图中填充该图像。然而,用户可能期望能够获得“原始”图像并移动它并缩放它。这将需要比这个答案所需的更多的东西。即,您想将原始照片以及裁剪后的版本保存在某个网址/应用程序文件夹中吗?或者甚至保存包含原始图片和重新创建裁剪视图所需的 CGRect 的字典?第三。如果所选照片与屏幕尺寸完全相同(屏幕截图),则似乎存在错误;它可能缩放得太低。

在新的 SwiftUI 生命周期应用程序中,我有以下 SwiftUI 视图:

这就是您将得到的:

照片移动和缩放应用程序的 SwiftUI 示例 用于照片选择的 SwiftUI 圆形遮罩屏幕 ImagePicker UIRepresentable 在 SwiftUI 中 SwiftUI 视图中选定的图像 自定义 SwiftUI 视图中的缩放图像 保存了联系人图像的裁剪和缩放图像

我还使用这个关键的解决方案进行裁剪:

  • 图像操作.swift

最后,我的一些代码访问系统 UIcolors,所以我在中使用扩展

  • 颜色.swift

内容视图

    import SwiftUI
    
    struct ContentView: View {
        
        @State private var isShowingPhotoSelectionSheet = false

        @State private var finalImage: UIImage?
        @State private var inputImage: UIImage?
        
        var body: some View {
            
            VStack {
                
                if finalImage != nil {
                    Image(uiImage: finalImage!)
                        .resizable()
                        .frame(width: 100, height: 100)
                        .scaledToFill()
                        .aspectRatio(contentMode: .fit)
                        .clipShape(Circle())
                        .shadow(radius: 4)
                } else {
                    Image(systemName: "person.crop.circle.fill")
                        .resizable()
                        .scaledToFill()
                        .frame(width: 100, height: 100)
                        .aspectRatio(contentMode: .fit)
                        .foregroundColor(.systemGray2)
                }
                Button (action: {
                    self.isShowingPhotoSelectionSheet = true
                }, label: {
                    Text("Change photo")
                        .foregroundColor(.systemRed)
                        .font(.footnote)
                })
            }
            .background(Color.systemBackground)
            .statusBar(hidden: isShowingPhotoSelectionSheet)
            .fullScreenCover(isPresented: $isShowingPhotoSelectionSheet, onDismiss: loadImage) {
                ImageMoveAndScaleSheet(croppedImage: $finalImage)
            }
        }
        
        func loadImage() {
            guard let inputImage = inputImage else { return }
            finalImage = inputImage
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }

Run Code Online (Sandbox Code Playgroud)

单击/点击Change photo将显示下一个视图:

图像移动和缩放表

这是一个全屏模式,在打开时隐藏状态栏。

    import SwiftUI
    
    struct ImageMoveAndScaleSheet: View {
        
        @Environment(\.presentationMode) var presentationMode
        
        @State private var isShowingImagePicker = false

        ///The croped image is what will will send back to the parent view.
        ///It should be the part of the image in the square defined by the
        ///cutout circle's diamter. See below, the cutout circle has an "inset" value
        ///which can be changed.
        @Binding var croppedImage: UIImage?

        ///The input image is received from the ImagePicker.
        ///We will need to calculate and refer to its aspectr ratio in the functions.
        @State private var inputImage: UIImage?
        @State private var inputW: CGFloat = 750.5556577
        @State private var inputH: CGFloat = 1336.5556577
        
        @State private var theAspectRatio: CGFloat = 0.0

        ///The profileImage is what wee see on this view. When added from the
        ///ImapgePicker, it will be sized to fit the screen,
        ///meaning either its width will match the width of the device's screen,
        ///or its height will match the height of the device screen.
        ///This is not suitable for landscape mode or for iPads.
        @State private var profileImage: Image?
        @State private var profileW: CGFloat = 0.0
        @State private var profileH: CGFloat = 0.0
        
        ///Zoom and Drag ...
        @State private var currentAmount: CGFloat = 0
        @State private var finalAmount: CGFloat = 1
        
        @State private var currentPosition: CGSize = .zero
        @State private var newPosition: CGSize = .zero
        
        ///We track of amount the image is moved for use in functions below.
        @State private var horizontalOffset: CGFloat = 0.0
        @State private var verticalOffset: CGFloat = 0.0
        
        var body: some View {
            
            ZStack {
                ZStack {
                    Color.black.opacity(0.8)
                    if profileImage != nil {
                        profileImage?
                            .resizable()
                            .scaleEffect(finalAmount + currentAmount)
                            .scaledToFill()
                            .aspectRatio(contentMode: .fit)
                            .offset(x: self.currentPosition.width, y: self.currentPosition.height)
                    } else {
                        Image(systemName: "person.crop.circle.fill")
                            .resizable()
                            .scaleEffect(finalAmount + currentAmount)
                            .scaledToFill()
                            .aspectRatio(contentMode: .fit)
                            .foregroundColor(.systemGray2)
                    }
                }
                Rectangle()
                    .fill(Color.black).opacity(0.55)
                    .mask(HoleShapeMask().fill(style: FillStyle(eoFill: true)))
                VStack {
                    Text((profileImage != nil) ? "Move and Scale" : "Select a Photo by tapping the icon below")
                        .foregroundColor(.white)
                        .padding(.top, 50)
                    Spacer()
                    HStack{
                        ZStack {
                            HStack {
                                Button(
                                    action: {presentationMode.wrappedValue.dismiss()},
                                    label: { Text("Cancel") })
                                Spacer()
                                Button(
                                    action: {
                                        self.save()
                                        presentationMode.wrappedValue.dismiss()
                                        
                                    })
                                    { Text("Save") }
                                    .opacity((profileImage != nil) ? 1.0 : 0.2)
                                    .disabled((profileImage != nil) ? false: true)
                                    
                            }
                            .padding(.horizontal)
                            .foregroundColor(.white)
                            Image(systemName: "circle.fill")
                                .font(.custom("system", size: 45))
                                .opacity(0.9)
                                .foregroundColor(.white)
                            Image(systemName: "photo.on.rectangle")
                                .imageScale(.medium)
                                .foregroundColor(.black)
                                .onTapGesture {
                                    isShowingImagePicker = true
                                }
                        }
                        .padding(.bottom, 5)
                    }
                }
                .padding()
            }
            .edgesIgnoringSafeArea(.all)
            
            //MARK: - Gestures
            
            .gesture(
                MagnificationGesture()
                    .onChanged { amount in
                        self.currentAmount = amount - 1
                        //                    repositionImage()
                    }
                    .onEnded { amount in
                        self.finalAmount += self.currentAmount
                        self.currentAmount = 0
                        repositionImage()
                    }
            )
            .simultaneousGesture(
                DragGesture()
                    .onChanged { value in
                        self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)
                    }
                    .onEnded { value in
                        self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)
                        self.newPosition = self.currentPosition
                        repositionImage()
                    }
            )
            .simultaneousGesture(
                TapGesture(count: 2)
                    .onEnded({
                        resetImageOriginAndScale()
                    })
            )
            .sheet(isPresented: $isShowingImagePicker, onDismiss: loadImage) {
                ImagePicker(image: self.$inputImage)
                    .accentColor(Color.systemRed)
            }
        }
        
        //MARK: - functions
        
        private func HoleShapeMask() -> Path {
            let rect = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
            let insetRect = CGRect(x: inset, y: inset, width: UIScreen.main.bounds.width - ( inset * 2 ), height: UIScreen.main.bounds.height - ( inset * 2 ))
            var shape = Rectangle().path(in: rect)
            shape.addPath(Circle().path(in: insetRect))
            return shape
        }
        
        ///Called when the ImagePicker is dismissed.
        ///We want to measure the image receoived and determine the aspect ratio.
        
        private func loadImage() {
            guard let inputImage = inputImage else { return }
            let w = inputImage.size.width
            let h = inputImage.size.height
            profileImage = Image(uiImage: inputImage)
            
            inputW = w
            inputH = h
            theAspectRatio = w / h
            
            resetImageOriginAndScale()
        }
        
        ///The profileImage will size to fit the screen.
        ///But we need to know the width and height
        ///to set the related @State variables.
        ///Douobke-tpping the image will also set it
        ///as it was sized originally upon loading.
        private func resetImageOriginAndScale() {
            withAnimation(.easeInOut){
                if theAspectRatio >= screenAspect {
                    profileW = UIScreen.main.bounds.width
                    profileH = profileW / theAspectRatio
                } else {
                    profileH = UIScreen.main.bounds.height
                    profileW = profileH * theAspectRatio
                }
                currentAmount = 0
                finalAmount = 1
                currentPosition = .zero
                newPosition = .zero
            }
        }
        
        
        private func repositionImage() {
            
            //Screen width
            let w = UIScreen.main.bounds.width
            
            if theAspectRatio > screenAspect {
                profileW = UIScreen.main.bounds.width * finalAmount
                profileH = profileW / theAspectRatio
            } else {
                profileH = UIScreen.main.bounds.height * finalAmount
                profileW = profileH * theAspectRatio
            }

            horizontalOffset = (profileW - w ) / 2
            verticalOffset = ( profileH - w ) / 2
            
            
            ///Keep the user from zooming too far in. Adjust as required by the individual project.
            if finalAmount > 4.0 {
                withAnimation{
                    finalAmount = 4.0
                }
            }
            
            ///The following if statements keep the image filling the circle cutout.
            if profileW >= UIScreen.main.bounds.width {
                
                if newPosition.width > horizontalOffset {
                    withAnimation(.easeInOut) {
                        newPosition = CGSize(width: horizontalOffset + inset, height: newPosition.height)
                        currentPosition = CGSize(width: horizontalOffset + inset, height: currentPosition.height)
                    }
                }
                
                if newPosition.width < ( horizontalOffset * -1) {
                    withAnimation(.easeInOut){
                        newPosition = CGSize(width: ( horizontalOffset * -1) - inset, height: newPosition.height)
                        currentPosition = CGSize(width: ( horizontalOffset * -1 - inset), height: currentPosition.height)
                    }
                }
            } else {
                
                withAnimation(.easeInOut) {
                    newPosition = CGSize(width: 0, height: newPosition.height)
                    currentPosition = CGSize(width: 0, height: newPosition.height)
                }
            }
            
            if profileH >= UIScreen.main.bounds.width {
                
                if newPosition.height > verticalOffset {
                    withAnimation(.easeInOut){
                        newPosition = CGSize(width: newPosition.width, height: verticalOffset + inset)
                        currentPosition = CGSize(width: newPosition.width, height: verticalOffset + inset)
                    }
                }
                
                if newPosition.height < ( verticalOffset * -1) {
                    withAnimation(.easeInOut){
                        newPosition = CGSize(width: newPosition.width, height: ( verticalOffset * -1) - inset)
                        currentPosition = CGSize(width: newPosition.width, height: ( verticalOffset * -1) - inset)
                    }
                }
            } else {
                
                withAnimation (.easeInOut){
                    newPosition = CGSize(width: newPosition.width, height: 0)
                    currentPosition = CGSize(width: newPosition.width, height: 0)
                }
            }
            
            if profileW <= UIScreen.main.bounds.width && theAspectRatio > screenAspect {
                resetImageOriginAndScale()
            }
            if profileH <= UIScreen.main.bounds.height && theAspectRatio < screenAspect {
                resetImageOriginAndScale()
            }
        }
        
        private func save() {
            
            let scale = (inputImage?.size.width)! / profileW
            
            let xPos = ( ( ( profileW - UIScreen.main.bounds.width ) / 2 ) + inset + ( currentPosition.width * -1 ) ) * scale
            let yPos = ( ( ( profileH - UIScreen.main.bounds.width ) / 2 ) + inset + ( currentPosition.height * -1 ) ) * scale
            let radius = ( UIScreen.main.bounds.width - inset * 2 ) * scale
            
            croppedImage = imageWithImage(image: inputImage!, croppedTo: CGRect(x: xPos, y: yPos, width: radius, height: radius))
            
            ///Debug maths
            print("Input: w \(inputW) h \(inputH)")
            print("Profile: w \(profileW) h \(profileH)")
            print("X Origin: \( ( ( profileW - UIScreen.main.bounds.width - inset ) / 2 ) + ( currentPosition.width  * -1 ) )")
            print("Y Origin: \( ( ( profileH - UIScreen.main.bounds.width - inset) / 2 ) + ( currentPosition.height  * -1 ) )")
            
            print("Scale: \(scale)")
            print("Profile:\(profileW) + \(profileH)" )
            print("Curent Pos: \(currentPosition.debugDescription)")
            print("Radius: \(radius)")
            print("x:\(xPos), y:\(yPos)")
        }
        
        let inset: CGFloat = 15
        let screenAspect = UIScreen.main.bounds.width / UIScreen.main.bounds.height
    }

Run Code Online (Sandbox Code Playgroud)

除了拖动和缩放手势之外,主要的查看和(可能还需要清理!)是功能。

  • HoleShapeMask() (不记得该代码在哪里,但我知道我得到了它。
  • repositionImage() (这里很让人头疼)
  • save() 使用文件中的函数ImageManipulation.swift

图像选择器

再说一次,这只是来自 Hacking With Swift。(谢谢保罗!)https://twitter.com/twostraws/

    import SwiftUI

    struct ImagePicker: UIViewControllerRepresentable {
        
        @Environment(\.presentationMode) var presentationMode
        @Binding var image: UIImage?
        
        class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
            let parent: ImagePicker
            
            init(_ parent: ImagePicker) {
                self.parent = parent
            }
            
            func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
                if let uiImage = info[.originalImage] as? UIImage {
                    parent.image = uiImage
                }
                parent.presentationMode.wrappedValue.dismiss()
            }
        }

        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
        
        func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
            let picker = UIImagePickerController()
            picker.delegate = context.coordinator
            return picker
        }

        func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {

        }
    }
Run Code Online (Sandbox Code Playgroud)

图像操作.swift

其中包含以下代码:

    import UIKit

    func imageWithImage(image: UIImage, croppedTo rect: CGRect) -> UIImage {

        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()

        let drawRect = CGRect(x: -rect.origin.x, y: -rect.origin.y,
                              width: image.size.width, height: image.size.height)

        context?.clip(to: CGRect(x: 0, y: 0,
                                 width: rect.size.width, height: rect.size.height))

        image.draw(in: drawRect)

        let subImage = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()
        return subImage!
    }

## Colors.swift ##

A handy extension to access system UIColors in