2016-11-23 53 views
1

作爲一個初出茅廬的程序員,有人告訴我,我會知道,當我用舒適的圖書館往往當我已經達到中級水平。我面臨的問題是允許我的用戶選擇個人資料圖片,如果圖片不是正方形,則允許我的用戶在圖片周圍移動一個正方形進行裁剪。是否沒有選擇圖像並從該圖像中選擇方形裁剪的庫?

這是這樣一個共同的特點(打火,GitHub上,臉譜等),我想會有一個迅速的3兼容的解決方案,我可以在網上找到,但我沒有任何運氣;我只用WDImagePicker進行攻擊,發現它並不完全符合用戶習慣看到的標準。

如果沒有解,我將代碼我自己,如果成功分享上網,但我覺得很難相信這是事實。任何人都知道任何解決方案?

爲了澄清我的確切意圖:用戶應該能夠選擇要上傳的圖片。如果該圖像不是正方形,則應該出現一個正方形,其大小等於min(imageWidth,imageHeight)。該方塊應該以滾動視圖的方式重疊圖片,以便用戶可以移動圖片以適合他或她想要上傳的方形圖像的方塊。然後應該上傳圖像的這一部分。實質上,我想要一個方面填充,用戶決定圖像的哪一部分被切斷。是否真的沒有API來處理這個問題?

+1

知道什麼?我們甚至不知道你正在開發哪個開發平臺,很愚蠢。 –

+0

我最終寫了我自己的實現,因爲UIImagePickerController顯然不符合任務。我現在還沒有與我的代碼,但如果我回家的時候你仍然在尋找我會在這裏添加它。 –

+0

謝謝雅各布,這將是非常有幫助 – n00bie42

回答

1

權,讓我道歉不是越早得到這個開始,但這裏是我的圖片剪裁工具,實際工作完全自定義的實現(與蘋果的)。這非常簡單,但不幸的是我(愚蠢)沒有將它構建爲易於重用,因此您將不得不嘗試重新創建故事板中的內容。我會盡量用截圖清楚。

首先,請注意,這是所有Swift 2.3代碼,因爲它是一箇舊項目,如果您在Swift 2.3中工作,很好,如果不是,那麼您需要更新它。

這裏,你將需要添加到您的項目,注意哪些解釋的各個部分是如何工作的註釋視圖控制器文件:

import Foundation 
import UIKit 
import AVFoundation 

class EditProfilePictureViewController : UIViewController { 

    var imageView: UIImageView? 
    var image : UIImage! 
    var center: CGPoint! 

    @IBOutlet var indicatorView: UIView! 
    @IBOutlet var spaceView: UIView! 

    // Set these to the desired width and height of your output image. 
    let desiredWidth:CGFloat = 75 
    let desiredHeight: CGFloat = 100 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     // Set up UI and gesture recognisers. 
     indicatorView.layer.borderColor = UIColor.whiteColor().CGColor 
     indicatorView.layer.borderWidth = 8 

     let pan = UIPanGestureRecognizer(target: self, action: "didPan:") 
     let pinch = UIPinchGestureRecognizer(target: self, action: "didPinch:") 
     indicatorView.addGestureRecognizer(pan) 
     indicatorView.addGestureRecognizer(pinch) 
    } 

    override func viewDidAppear(animated: Bool) { 
     super.viewDidAppear(animated) 

     // Set up the image view in relation to the storyboard views. 
     imageView = UIImageView() 
     imageView?.image = image 
     imageView!.frame = AVMakeRectWithAspectRatioInsideRect(image.size, indicatorView.frame) 
     center = imageView!.center 
     spaceView.insertSubview(imageView!, belowSubview: indicatorView) 
    } 


    // Invoked when the user pans accross the screen. The logic happening here basically just checks if the pan would move the image outside the cropping square and if so, don't allow the pan to happen. 
    func didPan(recognizer: UIPanGestureRecognizer) { 

     let translation = recognizer.translationInView(spaceView) 

     if (imageView!.frame.minX + translation.x >= indicatorView.frame.minX && imageView!.frame.maxX + translation.x <= indicatorView.frame.maxX) || ((imageView!.frame.size.width >= indicatorView.frame.size.width) && (imageView!.frame.minX + translation.x <= indicatorView.frame.minX && imageView!.frame.maxX + translation.x >= indicatorView.frame.maxX)) { 
      imageView!.center.x += translation.x 
     } 

     if (imageView!.frame.minY + translation.y >= indicatorView.frame.minY && imageView!.frame.maxY + translation.y <= indicatorView.frame.maxY) || ((imageView!.frame.size.height >= indicatorView.frame.size.height) && (imageView!.frame.minY + translation.y <= indicatorView.frame.minY && imageView!.frame.maxY + translation.y >= indicatorView.frame.maxY)) { 
      imageView!.center.y += translation.y 
     } 

     recognizer.setTranslation(CGPointZero, inView: spaceView) 
    } 

    // Invoked when the user pinches the screen. Again the logic here just ensures that zooming the image would not make it exceed the bounds of the cropping square and cancels the zoom if it does. 
    func didPinch(recognizer: UIPinchGestureRecognizer) { 

     let view = UIView(frame: imageView!.frame) 

     view.transform = CGAffineTransformScale(imageView!.transform, recognizer.scale, recognizer.scale) 

     if view.frame.size.width >= indicatorView.frame.size.width || view.frame.size.height >= indicatorView.frame.size.height { 

      imageView!.transform = CGAffineTransformScale(imageView!.transform, recognizer.scale, recognizer.scale) 
      recognizer.scale = 1 
     } 

     if recognizer.state == UIGestureRecognizerState.Ended { 

      if imageView!.frame.minX > indicatorView.frame.minX || imageView!.frame.maxX < indicatorView.frame.maxX { 

       UIView.animateWithDuration(0.3, animations: {() -> Void in 
        self.imageView!.center = self.indicatorView.center 
       }) 
      } 

      if imageView!.frame.size.height < indicatorView.frame.size.height && imageView!.frame.size.width < indicatorView.frame.size.width { 

       UIView.animateWithDuration(0.3, animations: {() -> Void in 
        self.imageView!.frame = AVMakeRectWithAspectRatioInsideRect(self.image.size, self.indicatorView.frame) 
       }) 
      } 
     } 
    } 

    // Outlet for the cancel button. 
    @IBAction func cancelButtonPressed(sender: AnyObject) { 
     dismissViewControllerAnimated(true, completion: nil) 
    } 

    // Outlet for the save button. The logic here scales the outputed image down to the desired size. 
    @IBAction func saveButtonPressed(sender: AnyObject) { 

     let croppedImage = grabIndicatedImage() 
     UIGraphicsBeginImageContext(CGSizeMake(desiredWidth, desiredHeight)) 
     CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), UIColor.blackColor().CGColor) 

     if desiredWidth/desiredHeight == croppedImage.size.width/croppedImage.size.height { 
      croppedImage.drawInRect(CGRect(x: 0, y: 0, width: desiredWidth, height: desiredHeight)) 
     } else { 
      let croppedImageSize : CGRect = AVMakeRectWithAspectRatioInsideRect(croppedImage.size, CGRectMake(0, 0, desiredWidth, desiredHeight)) 
      croppedImage.drawInRect(croppedImageSize) 
     } 

     let resizedCroppedImage = UIGraphicsGetImageFromCurrentImageContext() 
     UIGraphicsEndImageContext() 
     let data = UIImagePNGRepresentation(resizedCroppedImage) 
     // At this point you now have an image cropped to your desired size, as well as data representation of it should you want to send to an API. 
    } 

    // When you call this method it basically takes a screenshot of the crop area and gets the UIImage object from it. 
    func grabIndicatedImage() -> UIImage { 

     UIGraphicsBeginImageContext(self.view.layer.frame.size) 
     let context : CGContextRef = UIGraphicsGetCurrentContext(); 
     self.view.layer.renderInContext(context) 
     let screenshot : UIImage = UIGraphicsGetImageFromCurrentImageContext(); 

     let rectToCrop = CGRectMake(indicatorView.frame.minX + 8, indicatorView.frame.minY + 72, indicatorView.frame.width - 16, indicatorView.frame.height - 16) 

     let imageRef : CGImageRef = CGImageCreateWithImageInRect(screenshot.CGImage, rectToCrop) 
     let croppedImage = UIImage(CGImage: imageRef)! 


     UIGraphicsEndImageContext(); 
     return croppedImage 
    } 

    // MARK: The following methods relate to re-laying out the view if the user changes the device orientation. 

    override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) { 
     if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) 
     { 
      UIView.animateWithDuration(0.3, animations: {() -> Void in 
       self.imageView!.center = self.indicatorView.center 
       self.imageView!.frame = AVMakeRectWithAspectRatioInsideRect(self.image.size, self.indicatorView.frame) 
      }) 
     } 
    } 

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { 

     coordinator.animateAlongsideTransition({ (context) -> Void in 

      if UIDevice.currentDevice().userInterfaceIdiom == .Pad 
      { 
       UIView.animateWithDuration(0.3, animations: {() -> Void in 
        self.imageView!.center = self.indicatorView.center 
        self.imageView!.frame = AVMakeRectWithAspectRatioInsideRect(self.image.size, self.indicatorView.frame) 
       }) 
      } 
     }, completion: { (context) -> Void in 
     }) 
    } 
} 

下一步,你需要設置你的情節提要/筆尖並連接插座。故事板中的UI看起來是這樣的:

enter image description here

不是特別有幫助的,我知道。視圖層次結構是更深入,看起來像這樣:

enter image description here

正如你所看到的,是不是有很多在故事板成立。 Space View實際上只是主視圖的子視圖。它與所有四面的約束固定在一起,因此它與根視圖的大小相匹配。很容易複製。它有一個黑色的背景,但這可以是你選擇的任何顏色。

Indicator View是一個比較複雜的。從視圖heirarchy截圖可以看出,它是Space View的子視圖。就約束而言,最重要的是寬高比。這需要您想要的作物的縱橫比。在我的情況下,它是4:3,但對於你來說,如果你想要一個方形的作物,它可能是1:1。您可以輕鬆更改,但請注意,desiredHeightdesiredWidth必須設置爲反映寬高比。

enter image description here

這些限制可能看起來複雜,但它們實際上是相當簡單的,讓我把它們分解:

enter image description here

正如我所說,設定縱橫比。接下來在空間視圖中水平和垂直居中。然後爲空間視圖創建一組等寬,等高度約束。使這兩個'小於或等於'。之後,創建另一組等寬,等高限制。將它們的優先級都設置爲750.

對,這是用於佈置UI的;現在你只需要連接插座。如何連接spaceViewindicatorView網點是非常明顯的,所以請繼續操作。另外不要忘記將取消和保存按鈕連接到他們的操作。

最後,我會解釋如何使用它。創建一個segue到這個新的視圖控制器並覆蓋視圖控制器上的prepareForSegue方法。通過您選擇的任何方式獲取對視圖控制器的引用,並將圖像屬性設置爲要裁剪的圖像。請注意,這不是UIImagePickerController的替代品,而是更多的來補充它。您仍然需要使用UIImagePickerController才能從相機膠捲或相機中獲取圖像,但是這用於處理編輯。例如:

override func prepareForSegue(segue: UIStoryboardSegue) { 
    if let editVC = segue.destinationViewController as? EditProfilePictureViewController { 
     editVC.image = self.pickedImage // Set image to the image you want to crop. 
    } 
} 

然後編輯器會彈出,您可以在保存之前按照自己的喜好定位圖像。你可以選擇實現一個委託來從編輯器中獲取裁剪後的圖像,但我會把它留給你。

再次抱歉,因爲推遲收到你,但我希望你會發現它是值得的!祝您的應用程序順暢,請在發佈時發佈鏈接,以便我可以查看!

+0

我一直在考慮對此進行重構,使其易於重用,並將其封裝到框架中。讓我知道你是否喜歡它,並認爲它會讓人受益! –