iOS Custom UIImagePickerController Camera Crop to circle - in preview view - ios

I'm using this code to make a custom camera crop:
UIImagePickerController editing view circle overlay
This works perfectly in camera roll but not taking photos
If I change [navigationController.viewControllers count] == 3 --> [navigationController.viewControllers count] == 1 works for camera too, but not in next view (preview view where you accept to use the photo)
Someone help me??
-(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];
}
}
- (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];
}
}
}

Similar solution in SwiftUI, Swift 5
I banged my head on this for a long, long time, but finally have a solution that works.
I've not been coding in Swift or SwiftUI for long, and I absolutely welcome comments to improve this code. In some places, I've left in a bit of Debugging code you can uncomment. Wrapping my head around the math involved more trial and error than competent, well-thought out approaches!
Shortcomings in this code: first, it would be nice to open the Impage picker from ContentView() and then show my custom view. I don't know how to do that. Second, if there is already an image in the ContentView(), it would be nice to populate the image in the custom view. However, then the user may expect to be able to get the "original" image and move it and scale it. That would require more than is needed for this answer. Ie, do you want to save the original photo in some url / application folder as well as the cropped version? Or even save a dictionary with the original picture and the CGRect needed to recreate the cropped view? Third. there seems to be a bug if the selected photo is exactly the size of the screen (a screenshot); it can be scaled too low.
In a new SwiftUI lifecycle app, I have the following SwiftUI views:
ContentView with a UIImage binding to the subsequent
ImageMoveAndScaleSheet with a further UIBinding to
ImagePicker which is the UIViewControllerRepresentable from Hacking with Swift found here: https://www.hackingwithswift.com/books/ios-swiftui/importing-an-image-into-swiftui-using-uiimagepickercontroller
This is what you'll get:
I also use this crucial solution for cropping:
ImageManipulation.swift
Finally, some of my code accesses system UIcolors so I use the extension in
Colors.swift
ContentView
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()
}
}
Clicking / tapping on Change photo brings up the next view:
ImagemoveAndScaleSheet
This is a fullscreen modal which hides the statusbar while open.
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
}
Apart from the drag and scale gestures, the main things to look and (and probably clean up!) are the functions.
HoleShapeMask() (cannot remember where that code is, but I know I got it on SO.
repositionImage() (much headbanging here)
save() which uses the funciton in the ImageManipulation.swift file.
ImagePicker
Again, this is simply from Hacking With Swift. (Thanks Paul!) 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>) {
}
}
ImageManipulation.swift
This contains the following code:
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 SwiftUI:
import Foundation
import SwiftUI
extension Color {
static var label: Color {
return Color(UIColor.label)
}
static var secondaryLabel: Color {
return Color(UIColor.secondaryLabel)
}
static var tertiaryLabel: Color {
return Color(UIColor.tertiaryLabel)
}
static var quaternaryLabel: Color {
return Color(UIColor.quaternaryLabel)
}
static var systemFill: Color {
return Color(UIColor.systemFill)
}
static var secondarySystemFill: Color {
return Color(UIColor.secondarySystemFill)
}
static var tertiarySystemFill: Color {
return Color(UIColor.tertiarySystemFill)
}
static var quaternarySystemFill: Color {
return Color(UIColor.quaternarySystemFill)
}
static var systemBackground: Color {
return Color(UIColor.systemBackground)
}
static var secondarySystemBackground: Color {
return Color(UIColor.secondarySystemBackground)
}
static var tertiarySystemBackground: Color {
return Color(UIColor.tertiarySystemBackground)
}
static var systemGroupedBackground: Color {
return Color(UIColor.systemGroupedBackground)
}
static var secondarySystemGroupedBackground: Color {
return Color(UIColor.secondarySystemGroupedBackground)
}
static var tertiarySystemGroupedBackground: Color {
return Color(UIColor.tertiarySystemGroupedBackground)
}
static var systemRed: Color {
return Color(UIColor.systemRed)
}
static var systemBlue: Color {
return Color(UIColor.systemBlue)
}
static var systemPink: Color {
return Color(UIColor.systemPink)
}
static var systemTeal: Color {
return Color(UIColor.systemTeal)
}
static var systemGreen: Color {
return Color(UIColor.systemGreen)
}
static var systemIndigo: Color {
return Color(UIColor.systemIndigo)
}
static var systemOrange: Color {
return Color(UIColor.systemOrange)
}
static var systemPurple: Color {
return Color(UIColor.systemPurple)
}
static var systemYellow: Color {
return Color(UIColor.systemYellow)
}
static var systemGray: Color {
return Color(UIColor.systemGray)
}
static var systemGray2: Color {
return Color(UIColor.systemGray2)
}
static var systemGray3: Color {
return Color(UIColor.systemGray3)
}
static var systemGray4: Color {
return Color(UIColor.systemGray4)
}
static var systemGray5: Color {
return Color(UIColor.systemGray5)
}
static var systemGray6: Color {
return Color(UIColor.systemGray6)
}
}

extension ProfileViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard imagePickerController?.sourceType == .camera else {
return
}
guard let view = viewController.view.subviews(deep: true, where: {
String(describing: type(of:$0)) == "CAMPreviewView"
}).first else {
return
}
viewController.view.layoutIfNeeded()
let camPreviewBounds = view.bounds
let circleRect = CGRect(
x: camPreviewBounds.minX + (camPreviewBounds.width - 320) * 0.5,
y: camPreviewBounds.minY + (camPreviewBounds.height - 320) * 0.5,
width: 320,
height: 320
)
let path = UIBezierPath(roundedRect: camPreviewBounds, cornerRadius: 0)
path.append(UIBezierPath(ovalIn: circleRect))
let layer = CAShapeLayer()
layer.path = path.cgPath
layer.fillRule = CAShapeLayerFillRule.evenOdd;
layer.fillColor = UIColor.black.cgColor
layer.opacity = 0.8;
view.layer.addSublayer(layer)
}
}

Although I believe that my reply might be too late, I ended it up mixing my solution with this one: https://gist.github.com/hamin/e8c6dfe00d9c81375f3e, where:
In order to get overlay working properly on Camera, I was listening to notifications (taken & rejecting picture) due to add or remove circle overlay
Kept the solution mentioned above where I need to loop through UINavigationController and draw circle overlay when it was requested to.
To sum up, please find below my solution written in Swift:
public class CustomPicture: NSObject {
//MARK: - Properties
private var myPickerController: UIImagePickerController?
private var plCropOverlayBottomBar: UIView?
private var customLayer: CAShapeLayer?
//MARK: - Constants
private let screenHeight = UIScreen.mainScreen().bounds.size.height
private let screenWidth = UIScreen.mainScreen().bounds.size.width
private let kCameraNotificationIrisAnimationEnd = "_UIImagePickerControllerUserDidCaptureItem"
private let kCameraNotificationUserRejection = "_UIImagePickerControllerUserDidRejectItem"
private let kPUUIImageViewController = "PUUIImageViewController"
private let kPLUIImageViewController = "PLUIImageViewController"
private let kPLCropOverlayCropView = "PLCropOverlayCropView"
private let kPLCropOverlayBottomBar = "PLCropOverlayBottomBar"
//MARK: - Overrides
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
//MARK: - Privates
private func camera() {
listenToCameraNotifications()
let myPickerController = UIImagePickerController()
myPickerController.delegate = self
myPickerController.sourceType = .Camera
myPickerController.allowsEditing = true
self.myPickerController = myPickerController
self.navigationController?.presentViewController(myPickerController, animated: true, completion: nil)
}
private func listenToCameraNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(cameraNotificationIrisEnd), name: kCameraNotificationIrisAnimationEnd, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(cameraNotificationRejected), name: kCameraNotificationUserRejection, object: nil)
}
private func photoLibrary() {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self
myPickerController.allowsEditing = true
myPickerController.sourceType = .PhotoLibrary
self.myPickerController = myPickerController
self.navigationController?.presentViewController(myPickerController, animated: true, completion: nil)
}
//MARK: - Selector
/**
Listen to notification sent after reject button has been touched
*/
func cameraNotificationRejected() {
customLayer!.removeFromSuperlayer()
plCropOverlayBottomBar!.removeFromSuperview()
}
/**
Listen to notification sent after picture has been taken
*/
func cameraNotificationIrisEnd() {
addCircleOverlay(viewController: self.myPickerController!)
}
}
extension CustomPicture: UINavigationControllerDelegate {
//MARK: - Override
public func navigationController(navigationController: UINavigationController, willShowViewController: UIViewController, animated: Bool) {
if isImageViewer(navigationController: navigationController) {
addCircleOverlay(viewController: willShowViewController)
}
}
//MARK: - Private
private func addCircleOverlay(viewController viewController: UIViewController) {
hidePLCropOverlay(view: viewController.view)
setPLCropOverlayBottomBar(view: viewController.view)
setCustomLayer(viewController: viewController)
}
private func getCirclePath() -> UIBezierPath {
let circlePath = UIBezierPath(ovalInRect: CGRectMake(0, screenHeight / 2 - screenWidth / 2, screenWidth, screenWidth))
circlePath.usesEvenOddFillRule = true
let circleLayer = CAShapeLayer()
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = UIColor.clearColor().CGColor
return circlePath
}
private func getMaskPath(screenWidth screenWidth: CGFloat, screenHeight: CGFloat, circlePath: UIBezierPath) -> UIBezierPath {
let maskPath = UIBezierPath(roundedRect: CGRectMake(0, 0, screenWidth, screenHeight), cornerRadius: 0)
maskPath.appendPath(circlePath)
maskPath.usesEvenOddFillRule = true
return maskPath
}
private func hidePLCropOverlay(view view: UIView) {
for myView in view.subviews {
if myView.isKindOfClass(NSClassFromString(kPLCropOverlayCropView)!) {
myView.hidden = true
break
} else {
hidePLCropOverlay(view: myView as UIView)
}
}
}
private func isImageViewer(navigationController navigationController: UINavigationController) -> Bool {
if (navigationController.viewControllers.count == 3 &&
(navigationController.viewControllers[2].dynamicType.description() == kPUUIImageViewController ||
navigationController.viewControllers[2].dynamicType.description() == kPLUIImageViewController)) {
return true
}
return false
}
private func setPLCropOverlayBottomBar(view view: UIView) {
for myView in view.subviews {
if myView.isKindOfClass(NSClassFromString(kPLCropOverlayBottomBar)!) {
plCropOverlayBottomBar = myView
break
}
else {
savePLCropOverlayBottomBar(view: myView as UIView)
}
}
}
private func setCustomLayer(viewController viewController: UIViewController) {
let circlePath = getCirclePath()
let maskPath = getMaskPath(screenWidth: screenWidth, screenHeight: screenHeight, circlePath: circlePath)
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.CGPath
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.fillColor = UIColor.blackColor().colorWithAlphaComponent(0.8).CGColor
customLayer = maskLayer
viewController.view.layer.addSublayer(customLayer!)
viewController.view.addSubview(plCropOverlayBottomBar!) // put back overlayBottomBar once we set its parent to hidden (subview of PLCropOverlay)
}
}

Here is the solution which might help you to create crop overlay:-
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
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];
}
}

Related

Custom thumb image of UISlider doesn't show full of image icon size

I'm making a custom UISlider with thumb like this:
But here is what I get:
I want my thumb show with full height of the grey container, my image has size 160x160 for #2x. But when set into UISlider it doesn't act like that. Here is the view debugging:
I'm use a custom class for this UISlider, where's wrong in the code?:
class CustomSlider: UISlider {
#IBInspectable var trackHeight: CGFloat = 0.0001
// #IBInspectable var thumbRadius: CGFloat = 20
// Custom thumb view which will be converted to UIImage
// and set as thumb. You can customize it's colors, border, etc.
private lazy var thumbView: UIView = {
let thumb = UIImageView()
// thumb.backgroundColor = .white
// thumb.layer.borderWidth = 0.4
// thumb.layer.borderColor = UIColor.white.cgColor
thumb.image = UIImage(named: "icon_slider")
return thumb
}()
override func awakeFromNib() {
super.awakeFromNib()
let thumb = thumbImage()
setThumbImage(thumb, for: .normal)
}
private func thumbImage() -> UIImage {
thumbView.frame = CGRect(x: 0, y: 50/2, width: 50, height: 50)
// thumbView.dropShadow(color: .red, opacity: 1, offSet: CGSize(width: -1, height: 1), radius: 3, scale: true)
thumbView.layer.cornerRadius = 4
thumbView.layer.masksToBounds = true
// Convert thumbView to UIImage
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: thumbView.bounds)
return renderer.image { rendererContext in
thumbView.layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(thumbView.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
// Set custom track height
// As seen here: https://stackoverflow.com/a/49428606/7235585
var newRect = super.trackRect(forBounds: bounds)
newRect.size.height = trackHeight
return newRect
}
}

Correct remote video size on iPhoneX during video call using webrtc iOS swift

I am using webRTC for video calling. Everything is running smooth but
I am struggling with aspect ratio of Remote video on iPhoneX, XSMax. I am seeing lot of zoom in video. Can you please help me out how I can manage remote video on devices that have notch. Below is the code where I am handling remote size.
func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
print(size)
let defaultAspectRatio: CGSize = CGSize(width: 4, height: 3)
let aspectRatio: CGSize = size.equalTo(CGSize.zero) ? defaultAspectRatio : size
let videoRect: CGRect = self.view.bounds
let maxFloat = CGFloat.maximum(self.view.frame.width, self.view.frame.height)
let newAspectRatio = aspectRatio.width / aspectRatio.height
var frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
if (aspectRatio.width < aspectRatio.height) {
frame.size.width = maxFloat;
frame.size.height = frame.size.width / newAspectRatio;
} else {
frame.size.height = maxFloat;
frame.size.width = frame.size.height * newAspectRatio;
}
frame.origin.x = (self.view.frame.width - frame.size.width) / 2
frame.origin.y = (self.view.frame.height - frame.size.height) / 2
self.remoteView.frame = frame
}
According to #Eysner's answer, what is work for me, the final code (written using swift 5):
import UIKit
import WebRTC
final class WebRTCView: UIView, RTCVideoViewDelegate {
let videoView = RTCEAGLVideoView(frame: .zero)
var videoSize = CGSize.zero
override init(frame: CGRect) {
super.init(frame: frame)
videoView.delegate = self
addSubview(videoView)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
videoView.delegate = self
addSubview(videoView)
}
func videoView(_ videoView: RTCVideoRenderer, didChangeVideoSize size: CGSize) {
self.videoSize = size
setNeedsLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
guard videoSize.width > 0 && videoSize.height > 0 else {
videoView.frame = bounds
return
}
var videoFrame = AVMakeRect(aspectRatio: videoSize, insideRect: bounds)
let scale = videoFrame.size.aspectFitScale(in: bounds.size)
videoFrame.size.width = videoFrame.size.width * CGFloat(scale)
videoFrame.size.height = videoFrame.size.height * CGFloat(scale)
videoView.frame = videoFrame
videoView.center = CGPoint(x: bounds.midX, y: bounds.midY)
}
}
extension CGSize {
func aspectFitScale(in container: CGSize) -> CGFloat {
if height <= container.height && width > container.width {
return container.width / width
}
if height > container.height && width > container.width {
return min(container.width / width, container.height / height)
}
if height > container.height && width <= container.width {
return container.height / height
}
if height <= container.height && width <= container.width {
return min(container.width / width, container.height / height)
}
return 1.0
}
}
There is aspectFitScale function, it is simply to describe logic, you can refactor it if you want to.
I'm using this approach in the demo:
https://github.com/Maxatma/Walkie-Talkie/
You can make some class with show video -
class SomeVideoView: UIView, RTCVideoViewDelegate {
let videoView = RTCEAGLVideoView(frame: .zero)
var videoSize = CGSize.zero
With init method
override init(frame: CGRect) {
super.init(frame: frame)
videoView.delegate = self
self.addSubview(videoView)
...
Handle delegate method videoView:didChangeVideoSize like this (we don't need change frame in UI, only mark needsLayout)
func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
if (self.videoView == videoView) {
self.videoSize = size
}
self.setNeedsLayout()
}
And overwrite layoutSubviews method
override func layoutSubviews() {
if (self.videoSize.width > 0 && self.videoSize.height > 0) {
var videoFrame = AVMakeRect(aspectRatio: self.videoSize, insideRect: bounds)
var scale = 1.0
if (videoFrame.size.width > videoFrame.size.height) {
scale = bounds.size.height / videoFrame.size.height
} else {
scale = bounds.size.width / videoFrame.size.width
}
videoFrame.size.width = videoFrame.size.width * scale
videoFrame.size.height = videoFrame.size.height * scale
self.videoView.frame = videoFrame
self.videoView.center = CGPointMake(bounds.midX, bounds.midY)
} else {
self.videoView.frame = bounds
}
...
}
And set up SomeVideoView is full screen (this link can be help with it)
Safe Area of Xcode 9
just simple using AVMakeRect
//TODO: Default mobile
if size.width < size.height{
newSize = aspectFill(aspectRatio: size, minimumSize: UIScreen.main.bounds.size)
}else{
//Default computer
newSize = AVMakeRect(aspectRatio: size, insideRect: UIScreen.main.bounds).size
}
for sometimes user may have call from website, so i didnt make fill from it
func aspectFill(aspectRatio : CGSize, minimumSize: CGSize) -> CGSize {
let mW = minimumSize.width / aspectRatio.width;
let mH = minimumSize.height / aspectRatio.height;
var temp = minimumSize
if( mH > mW ) {
temp.width = minimumSize.height / aspectRatio.height * aspectRatio.width;
}
else if( mW > mH ) {
temp.height = minimumSize.width / aspectRatio.width * aspectRatio.height;
}
return temp;
}

UIScrollView scrolls out of image after image is zoomed in?

I'm trying to implement a image zooming functionality using UIScrollview. where as I kept image as aspect fit.
Image is inside a UIScrollView, and image frame has been given similar to UIScrollView.
Here is my code.
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
scroller.minimumZoomScale = 1.0
scroller.maximumZoomScale = 7.0
}
// MARK: - User Defined Methods
#IBAction func doubleTapGestureAction(_ sender: UITapGestureRecognizer)
{
if scroller.zoomScale == 1
{
scroller.zoom(to: zoomForScale(scale: scroller.maximumZoomScale, center: sender.location(in: sender.view)), animated: true)
}
else
{
scroller.setZoomScale(1, animated: true)
}
print(isZoomedIn)
}
func zoomForScale(scale: CGFloat, center: CGPoint) -> CGRect
{
var zoomRect = CGRect.zero
zoomRect.size.height = image.frame.size.height / scale
zoomRect.size.width = image.frame.size.width / scale
let newCenter = image.convert(center, from: scroller)
zoomRect.origin.x = newCenter.x - (zoomRect.size.width / 2.0)
zoomRect.origin.y = newCenter.y - (zoomRect.size.height / 2.0)
return zoomRect
}
func viewForZooming(in scrollView: UIScrollView) -> UIView?
{
return image
}
Here is sample code:
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
var imgDemo: UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill
img.isUserInteractionEnabled = true
return img
}()
var scrollView:UIScrollView = {
let scroll = UIScrollView()
scroll.maximumZoomScale = 4.0
scroll.minimumZoomScale = 0.25
scroll.clipsToBounds = true
return scroll
}()
override func viewDidLoad() {
super.viewDidLoad()
imgDemo.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height)
imgDemo.image = UIImage(named: "5.jpg")
scrollView.delegate = self
scrollView.frame = imgDemo.frame
scrollView.addSubview(imgDemo)
view.addSubview(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgDemo
}
}
Take a look at these methods. May be it will help. I have scroll view stretched to controller's view size. customizeScrollView() will calculate min and max scale options. centerImageView() will put UIImageView in the center of your UIScrollView
Call the customizeScrollView function in viewDidload.
fileprivate func customizeScrollView() {
guard let image = imageView?.image else { return }
var minZoom = fmin(self.view.frame.width / image.size.width, self.view.frame.height / image.size.height)
minZoom = fmin(1.0, minZoom)
scrollView?.contentSize = image.size
scrollView?.minimumZoomScale = minZoom
scrollView?.addSubview(self.imageView!)
scrollView?.setZoomScale(minZoom, animated: false)
centerImageView()
}
fileprivate func centerImageView() {
guard let imageView = imageView else { return }
guard let scrollView = scrollView else { return }
let boundsSize = scrollView.bounds.size
var frameToCenter = imageView.frame
// Center horizontally
if frameToCenter.size.width < boundsSize.width {
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2
} else {
frameToCenter.origin.x = 0
}
// Center vertically
if frameToCenter.size.height < boundsSize.height {
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2
} else {
frameToCenter.origin.y = 0
}
imageView.frame = frameToCenter
}
public func scrollViewDidZoom(scrollView: UIScrollView) {
print(imageView.frame)
centerImageView()
}

How to forward the pan gesture from UIScrollView to UIImageView?

I have a UIScrollView and inside a UIImageView so that I can pinche zoom the image view using:
extension CropperViewController : UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView;
}
}
I now also want to be able to freely move the UIImageView so I tried adding a UIPanGestureRecognizer to myUIImageView`:
self.imageView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))));
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.view);
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y);
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view);
}
}
I now had the problem that no pan touch event was fired at all so I thought maybe the UIScrollView is catching all those events. So some research on Stackoverflow told me to add the following to my UIScrollView:
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)));
tapGestureRecognizer.numberOfTapsRequired = 1;
tapGestureRecognizer.cancelsTouchesInView = false;
self.scrollView.addGestureRecognizer(tapGestureRecognizer);
but actually that changed nothing. I can zoom and move the image after zooming but I can not move the image using UIPanGestureRecognizer. I want to use the UIScrollView to be able to zoom but I want to use the UIPanGestureRecognizer to move the UIImageView.
How can I do that?
EDIT
Maybe it is possible to disable or change the pan gesture recognizer of the UIScrollView and forward those events to the UIImageView?
You need to give the pan recognizer a delegate, then return true for shouldRecogniseSimultaneouslyWith....
You may also need to do the same with the scroll view's pan recognizer, which is available as a property.
Alternatively, add another target/action to the scroll view's pan recognizer (using addTarget(_, action:) instead of creating your own.
Try this:
scrollView.panGestureRecognizer.require(toFail: imagePanRecognizer)
If you still get problems (like scrolling feeling laggy) set scrollView's delaysContentTouches to false
Since it seems that there isnt a solution for that I came up with not using a UIScrollView and impelementing UIPinchGestureRecognizer and UIPanGestureRecognizer for my UIImageView by myself:
import Foundation
import UIKit
/**
*
*/
protocol CropperCallback {
/**
*
*/
func croppingDone(image: UIImage);
/**
*
*/
func croppingCancelled();
}
/**
*
*/
class CropperViewController : UIViewController {
/**
*
*/
#IBOutlet var imageView: UIImageView!;
/**
*
*/
var imageViewScaleCurrent: CGFloat! = 1.0;
var imageViewScaleMin: CGFloat! = 0.5;
var imageViewScaleMax: CGFloat! = 5.0;
/**
*
*/
#IBOutlet var cropAreaView: CropAreaView!;
/**
*
*/
#IBOutlet weak var cropAreaViewConstraintWidth: NSLayoutConstraint!
#IBOutlet weak var cropAreaViewConstraintHeight: NSLayoutConstraint!
/**
*
*/
#IBOutlet var btnCrop: UIButton!;
/**
*
*/
#IBOutlet var btnCancel: UIButton!;
/**
*
*/
var callback: CropperCallback! = nil;
/**
*
*/
var image: UIImage! = nil;
/**
*
*/
var imageOriginalWidth: CGFloat!;
var imageOriginalHeight: CGFloat!;
/**
*
*/
var cropWidth: CGFloat! = 287;
/**
*
*/
var cropHeight: CGFloat! = 292;
/**
*
*/
var cropHeightFix: CGFloat! = 1.0;
/**
*
*/
var cropArea: CGRect {
/**
*
*/
get {
let factor = self.imageView.image!.size.width / self.view.frame.width;
let scale = 1 / self.imageViewScaleCurrent;
let x = (self.cropAreaView.frame.origin.x - self.imageView.frame.origin.x) * scale * factor;
let y = (self.cropAreaView.frame.origin.y - self.imageView.frame.origin.y) * scale * factor;
let width = self.cropAreaView.frame.size.width * scale * factor;
let height = self.cropAreaView.frame.size.height * scale * factor;
return CGRect(x: x, y: y, width: width, height: height);
}
}
/**
*
*/
static func storyboardInstance() -> CropperViewController? {
let storyboard = UIStoryboard(name: String(describing: NSStringFromClass(CropperViewController.classForCoder()).components(separatedBy: ".").last!), bundle: nil);
return storyboard.instantiateInitialViewController() as? CropperViewController;
}
/**
*
*/
override func viewDidLoad() {
super.viewDidLoad();
self.imageView.image = self.image;
self.imageView.isUserInteractionEnabled = true;
self.imageView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))));
self.imageView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(self.handlePinch(_:))));
self.cropAreaViewConstraintWidth.constant = self.cropWidth;
self.cropAreaViewConstraintHeight.constant = self.cropHeight;
self.cropAreaView.layer.borderWidth = 1;
self.cropAreaView.layer.borderColor = UIColor(red: 173/255, green: 192/255, blue: 4/255, alpha: 1.0).cgColor;
self.btnCrop.addTarget(self, action: #selector(self.didTapCropButton), for: UIControlEvents.touchUpInside);
self.btnCancel.addTarget(self, action: #selector(self.didTapCancelButton), for: UIControlEvents.touchUpInside);
}
/**
*
*/
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews();
let imageOriginalRect = self.getRectOfImageInImageView(imageView: self.imageView);
self.imageOriginalWidth = imageOriginalRect.size.width;
self.imageOriginalHeight = imageOriginalRect.size.height;
}
/**
*
*/
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let rect = self.getRectOfImageInImageView(imageView: self.imageView);
let xImage = rect.origin.x;
let yImage = rect.origin.y;
let widthImage = rect.size.width;
let heightImage = rect.size.height;
let xCropView = self.cropAreaView.frame.origin.x;
let yCropView = self.cropAreaView.frame.origin.y;
let widthCropView = self.cropAreaView.frame.size.width;
let heightCropView = self.cropAreaView.frame.size.height;
let translation = gestureRecognizer.translation(in: self.view);
var x: CGFloat;
var y: CGFloat;
if (translation.x > 0) {
if (!(xImage >= xCropView)) {
x = gestureRecognizer.view!.center.x + translation.x;
} else {
x = gestureRecognizer.view!.center.x;
}
} else if (translation.x < 0) {
if (!((xImage + widthImage) <= (xCropView + widthCropView))) {
x = gestureRecognizer.view!.center.x + translation.x;
} else {
x = gestureRecognizer.view!.center.x;
}
} else {
x = gestureRecognizer.view!.center.x;
}
if (translation.y > 0) {
if (!(yImage >= (yCropView - self.cropHeightFix))) {
y = gestureRecognizer.view!.center.y + translation.y;
} else {
y = gestureRecognizer.view!.center.y;
}
} else if (translation.y < 0) {
if (!((yImage + heightImage) <= (yCropView + heightCropView + self.cropHeightFix))) {
y = gestureRecognizer.view!.center.y + translation.y;
} else {
y = gestureRecognizer.view!.center.y;
}
} else {
y = gestureRecognizer.view!.center.y;
}
gestureRecognizer.view!.center = CGPoint(x: x, y: y);
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view);
self.fixImageViewPosition();
}
}
/**
*
*/
func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) {
if let view = gestureRecognizer.view {
let widthCropView = self.cropAreaView.frame.size.width;
let heightCropView = self.cropAreaView.frame.size.height;
if (((self.imageViewScaleCurrent * gestureRecognizer.scale * self.imageOriginalWidth) > widthCropView)
&& ((self.imageViewScaleCurrent * gestureRecognizer.scale * self.imageOriginalHeight) > (heightCropView + (2 * self.cropHeightFix)))
&& ((self.imageViewScaleCurrent * gestureRecognizer.scale) < self.imageViewScaleMax)) {
self.imageViewScaleCurrent = self.imageViewScaleCurrent * gestureRecognizer.scale;
view.transform = CGAffineTransform(scaleX: self.imageViewScaleCurrent, y: self.imageViewScaleCurrent);
}
gestureRecognizer.scale = 1.0;
self.fixImageViewPosition();
}
}
/**
*
*/
func fixImageViewPosition() {
let rect = self.getRectOfImageInImageView(imageView: self.imageView);
let xImage = rect.origin.x;
let yImage = rect.origin.y;
let widthImage = rect.size.width;
let heightImage = rect.size.height;
let xCropView = self.cropAreaView.frame.origin.x;
let yCropView = self.cropAreaView.frame.origin.y;
let widthCropView = self.cropAreaView.frame.size.width;
let heightCropView = self.cropAreaView.frame.size.height;
if (xImage > xCropView) {
self.imageView.frame = CGRect(x: xCropView, y: self.imageView.frame.origin.y, width: widthImage, height: heightImage);
}
if ((xImage + widthImage) < (xCropView + widthCropView)) {
self.imageView.frame = CGRect(x: ((xCropView + widthCropView) - widthImage), y: self.imageView.frame.origin.y, width: widthImage, height: heightImage);
}
if (yImage > yCropView) {
self.imageView.frame = CGRect(x: self.imageView.frame.origin.x, y: (yCropView - self.cropHeightFix), width: widthImage, height: heightImage);
}
if ((yImage + heightImage) < (yCropView + heightCropView + self.cropHeightFix)) {
self.imageView.frame = CGRect(x: self.imageView.frame.origin.x, y: ((yCropView + heightCropView + self.cropHeightFix) - heightImage), width: widthImage, height: heightImage);
}
}
/**
*
*/
func getRectOfImageInImageView(imageView: UIImageView) -> CGRect {
let imageViewSize = imageView.frame.size;
let imageSize = imageView.image!.size;
let scaleW = imageViewSize.width / imageSize.width;
let scaleH = imageViewSize.height / imageSize.height;
let aspect = min(scaleW, scaleH);
var imageRect = CGRect(x: 0, y: 0, width: (imageSize.width * aspect), height: (imageSize.height * aspect));
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2;
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2;
imageRect.origin.x += imageView.frame.origin.x;
imageRect.origin.y += imageView.frame.origin.y;
return imageRect;
}
/**
*
*/
func didTapCropButton(sender: AnyObject) {
let croppedCGImage = self.imageView.image?.cgImage?.cropping(to: self.cropArea);
let croppedImage = UIImage(cgImage: croppedCGImage!);
if (self.callback != nil) {
self.callback.croppingDone(image: croppedImage);
}
self.dismiss(animated: true, completion: nil);
}
/**
*
*/
func didTapCancelButton(sender: AnyObject) {
if (self.callback != nil) {
self.callback.croppingCancelled();
}
self.dismiss(animated: true, completion: nil);
}
}
/**
*
*/
extension UIImageView {
/**
*
*/
func imageFrame() -> CGRect {
let imageViewSize = self.frame.size;
guard let imageSize = self.image?.size else {
return CGRect.zero;
}
let imageRatio = imageSize.width / imageSize.height;
let imageViewRatio = imageViewSize.width / imageViewSize.height;
if (imageRatio < imageViewRatio) {
let scaleFactor = imageViewSize.height / imageSize.height;
let width = imageSize.width * scaleFactor;
let topLeftX = (imageViewSize.width - width) * 0.5;
return CGRect(x: topLeftX, y: 0, width: width, height: imageViewSize.height);
} else {
let scaleFactor = imageViewSize.width / imageSize.width;
let height = imageSize.height * scaleFactor;
let topLeftY = (imageViewSize.height - height) * 0.5;
return CGRect(x: 0, y: topLeftY, width: imageViewSize.width, height: height);
}
}
}

SwiftPages updateUI Does Not Work with Swift 3

I'm using Swiftpages. When app is opened it looks like first picture.
But app goes to background and opened different app on device, after open again my app it looks like second picture.
I updated to Swift 3, but I can't figure out the issue, I write about it on Github but no reply from them.
public class SwiftPages: UIView {
private lazy var token = 0
var containerVieww: UIView!
private var scrollView: UIScrollView!
private var topBar: UIView!
var animatedBar: UIView!
var viewControllerIDs = [String]()
private var buttonTitles = [String]()
private var buttonImages = [UIImage]()
private var pageViews = [UIViewController?]()
private var currentPage: Int = 0
// Container view position variables
private var xOrigin: CGFloat = 0
private var yOrigin: CGFloat = 64
private var distanceToBottom: CGFloat = 0
// Color variables
private var animatedBarColor = UIColor(red: 28/255, green: 95/255, blue: 185/255, alpha: 1)
private var topBarBackground = UIColor.white
private var buttonsTextColor = UIColor.gray
private var containerViewBackground = UIColor.white
// Item size variables
private var topBarHeight: CGFloat = 52
private var animatedBarHeight: CGFloat = 3
// Bar item variables
private var aeroEffectInTopBar = false //This gives the top bap a blurred effect, also overlayes the it over the VC's
private var buttonsWithImages = false
var barShadow = true
private var shadowView : UIView!
private var shadowViewGradient = CAGradientLayer()
private var buttonsTextFontAndSize = UIFont(name: "HelveticaNeue-Light", size: 20)!
private var blurView : UIVisualEffectView!
private var barButtons = [UIButton?]()
// MARK: - Positions Of The Container View API -
public func setOriginX (origin : CGFloat) { xOrigin = origin }
public func setOriginY (origin : CGFloat) { yOrigin = origin }
public func setDistanceToBottom (distance : CGFloat) { distanceToBottom = distance }
// MARK: - API's -
public func setAnimatedBarColor (color : UIColor) { animatedBarColor = color }
public func setTopBarBackground (color : UIColor) { topBarBackground = color }
public func setButtonsTextColor (color : UIColor) { buttonsTextColor = color }
public func setContainerViewBackground (color : UIColor) { containerViewBackground = color }
public func setTopBarHeight (pointSize : CGFloat) { topBarHeight = pointSize}
public func setAnimatedBarHeight (pointSize : CGFloat) { animatedBarHeight = pointSize}
public func setButtonsTextFontAndSize (fontAndSize : UIFont) { buttonsTextFontAndSize = fontAndSize}
public func enableAeroEffectInTopBar (boolValue : Bool) { aeroEffectInTopBar = boolValue}
public func enableButtonsWithImages (boolValue : Bool) { buttonsWithImages = boolValue}
public func enableBarShadow (boolValue : Bool) { barShadow = boolValue}
override public func draw(_ rect: CGRect) {
DispatchQueue.main.async {
let pagesContainerHeight = self.frame.height - self.yOrigin - self.distanceToBottom
let pagesContainerWidth = self.frame.width
// Set the notifications for an orientation change & BG mode
let defaultNotificationCenter = NotificationCenter.default
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.applicationWillEnterBackground), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.orientationWillChange), name: NSNotification.Name.UIApplicationWillChangeStatusBarOrientation, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.orientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.applicationWillEnterForeground), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
// Set the containerView, every item is constructed relative to this view
self.containerVieww = UIView(frame: CGRect(x: self.xOrigin, y: self.yOrigin, width: pagesContainerWidth, height: pagesContainerHeight))
self.containerVieww.backgroundColor = self.containerViewBackground
self.containerVieww.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.containerVieww)
//Add the constraints to the containerView.
if #available(iOS 9.0, *) {
let horizontalConstraint = self.containerVieww.centerXAnchor.constraint(equalTo: self.centerXAnchor)
let verticalConstraint = self.containerVieww.centerYAnchor.constraint(equalTo: self.centerYAnchor)
let widthConstraint = self.containerVieww.widthAnchor.constraint(equalTo: self.widthAnchor)
let heightConstraint = self.containerVieww.heightAnchor.constraint(equalTo: self.heightAnchor)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
// Set the scrollview
if self.aeroEffectInTopBar {
self.scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.containerVieww.frame.size.width, height: self.containerVieww.frame.size.height))
} else {
self.scrollView = UIScrollView(frame: CGRect(x: 0, y: self.topBarHeight, width: self.containerVieww.frame.size.width, height: self.containerVieww.frame.size.height - self.topBarHeight))
}
self.scrollView.isPagingEnabled = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.delegate = self
self.scrollView.backgroundColor = UIColor.clear
self.scrollView.contentOffset = CGPoint(x: 0, y: 0)
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.isScrollEnabled = false
self.containerVieww.addSubview(self.scrollView)
// Add the constraints to the scrollview.
if #available(iOS 9.0, *) {
let leadingConstraint = self.scrollView.leadingAnchor.constraint(equalTo: self.containerVieww.leadingAnchor)
let trailingConstraint = self.scrollView.trailingAnchor.constraint(equalTo: self.containerVieww.trailingAnchor)
let topConstraint = self.scrollView.topAnchor.constraint(equalTo: self.containerVieww.topAnchor)
let bottomConstraint = self.scrollView.bottomAnchor.constraint(equalTo: self.containerVieww.bottomAnchor)
NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
// Set the top bar
self.topBar = UIView(frame: CGRect(x: 0, y: 0, width: self.containerVieww.frame.size.width, height: self.topBarHeight))
self.topBar.backgroundColor = self.topBarBackground
if self.aeroEffectInTopBar {
// Create the blurred visual effect
// You can choose between ExtraLight, Light and Dark
self.topBar.backgroundColor = UIColor.clear
let blurEffect: UIBlurEffect = UIBlurEffect(style: .light)
self.blurView = UIVisualEffectView(effect: blurEffect)
self.blurView.frame = self.topBar.bounds
self.blurView.translatesAutoresizingMaskIntoConstraints = false
self.topBar.addSubview(self.blurView)
}
self.topBar.translatesAutoresizingMaskIntoConstraints = false
self.containerVieww.addSubview(self.topBar)
// Set the top bar buttons
// Check to see if the top bar will be created with images ot text
if self.buttonsWithImages {
var buttonsXPosition: CGFloat = 0
for (index, image) in self.buttonImages.enumerated() {
let frame = CGRect(x: buttonsXPosition, y: 0, width: self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count), height: self.topBarHeight)
let barButton = UIButton(frame: frame)
barButton.backgroundColor = UIColor.clear
barButton.imageView?.contentMode = .scaleAspectFit
barButton.setImage(image, for: .normal)
barButton.tag = index
barButton.addTarget(self, action: #selector(SwiftPages.barButtonAction), for: .touchUpInside)
self.topBar.addSubview(barButton)
self.barButtons.append(barButton)
buttonsXPosition += self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)
}
} else {
var buttonsXPosition: CGFloat = 0
for (index, title) in self.buttonTitles.enumerated() {
let frame = CGRect(x: buttonsXPosition, y: 0, width: self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count), height: self.topBarHeight)
let barButton = UIButton(frame: frame)
barButton.backgroundColor = UIColor.clear
barButton.titleLabel!.font = self.buttonsTextFontAndSize
barButton.setTitle(title, for: .normal)
barButton.setTitleColor(self.buttonsTextColor, for: .normal)
barButton.tag = index
barButton.addTarget(self, action: #selector(SwiftPages.barButtonAction), for: .touchUpInside)
self.topBar.addSubview(barButton)
self.barButtons.append(barButton)
buttonsXPosition += self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)
}
}
// Set up the animated UIView
self.animatedBar = UIView(frame: CGRect(x: 0, y: self.topBarHeight - self.animatedBarHeight + 1, width: (self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)) * 0.8, height: self.animatedBarHeight))
self.animatedBar.center.x = self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count << 1)
self.animatedBar.backgroundColor = self.animatedBarColor
self.containerVieww.addSubview(self.animatedBar)
// Add the bar shadow (set to true or false with the barShadow var)
if self.barShadow {
self.shadowView = UIView(frame: CGRect(x: 0, y: self.topBarHeight, width: self.containerVieww.frame.size.width, height: 4))
self.shadowViewGradient.frame = self.shadowView.bounds
self.shadowViewGradient.colors = [UIColor(red: 150/255, green: 150/255, blue: 150/255, alpha: 0.28).cgColor, UIColor.clear.cgColor]
self.shadowView.layer.insertSublayer(self.shadowViewGradient, at: 0)
self.containerVieww.addSubview(self.shadowView)
}
let pageCount = self.viewControllerIDs.count
// Fill the array containing the VC instances with nil objects as placeholders
for _ in 0..<pageCount {
self.pageViews.append(nil)
}
// Defining the content size of the scrollview
let pagesScrollViewSize = self.scrollView.frame.size
self.scrollView.contentSize = CGSize(width: pagesScrollViewSize.width * CGFloat(pageCount), height: pagesScrollViewSize.height)
// Load the pages to show initially
self.loadVisiblePages()
// Do the initial alignment of the subViews
self.alignSubviews()
}
}
// MARK: - Initialization Functions -
public func initializeWithVCIDsArrayAndButtonTitlesArray (VCIDsArray: [String], buttonTitlesArray: [String]) {
// Important - Titles Array must Have The Same Number Of Items As The viewControllerIDs Array
if VCIDsArray.count == buttonTitlesArray.count {
viewControllerIDs = VCIDsArray
buttonTitles = buttonTitlesArray
buttonsWithImages = false
} else {
print("Initilization failed, the VC ID array count does not match the button titles array count.")
}
}
public func initializeWithVCIDsArrayAndButtonImagesArray (VCIDsArray: [String], buttonImagesArray: [UIImage]) {
// Important - Images Array must Have The Same Number Of Items As The viewControllerIDs Array
if VCIDsArray.count == buttonImagesArray.count {
viewControllerIDs = VCIDsArray
buttonImages = buttonImagesArray
buttonsWithImages = true
} else {
print("Initilization failed, the VC ID array count does not match the button images array count.")
}
}
public func loadPage(page: Int) {
// If it's outside the range of what you have to display, then do nothing
guard page >= 0 && page < viewControllerIDs.count else { return }
// Do nothing if the view is already loaded.
guard pageViews[page] == nil else { return }
print("Loading Page \(page)")
// The pageView instance is nil, create the page
var frame = scrollView.bounds
frame.origin.x = frame.size.width * CGFloat(page)
frame.origin.y = 0.0
// Look for the VC by its identifier in the storyboard and add it to the scrollview
let newPageView = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewControllerIDs[page])
newPageView.view.frame = frame
scrollView.addSubview(newPageView.view)
// Replace the nil in the pageViews array with the VC just created
pageViews[page] = newPageView
}
public func loadVisiblePages() {
// First, determine which page is currently visible
let pageWidth = scrollView.frame.size.width
let page = Int(floor((scrollView.contentOffset.x * 2.0 + pageWidth) / (pageWidth * 2.0)))
// Work out which pages you want to load
let firstPage = page - 1
let lastPage = page + 1
// Load pages in our range
for index in firstPage...lastPage {
loadPage(page: index)
}
}
public func barButtonAction(sender: UIButton?) {
let index = sender!.tag
let pagesScrollViewSize = scrollView.frame.size
scrollView.setContentOffset(CGPoint(x: pagesScrollViewSize.width * CGFloat(index), y: 0), animated: true)
currentPage = index
}
// MARK: - Orientation Handling Functions -
public func alignSubviews() {
let pageCount = viewControllerIDs.count
// Setup the new frames
scrollView.contentSize = CGSize(width: CGFloat(pageCount) * scrollView.bounds.size.width, height: scrollView.bounds.size.height)
topBar.frame = CGRect(x: 0, y: 0, width: containerVieww.frame.size.width, height: topBarHeight)
blurView?.frame = topBar.bounds
animatedBar.frame.size = CGSize(width: (containerVieww.frame.size.width / (CGFloat)(viewControllerIDs.count)) * 0.8, height: animatedBarHeight)
if barShadow {
shadowView.frame.size = CGSize(width: containerVieww.frame.size.width, height: 4)
shadowViewGradient.frame = shadowView.bounds
}
// Set the new frame of the scrollview contents
for (index, controller) in pageViews.enumerated() {
controller?.view.frame = CGRect(x: CGFloat(index) * scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
}
// Set the new frame for the top bar buttons
var buttonsXPosition: CGFloat = 0
for button in barButtons {
button?.frame = CGRect(x: buttonsXPosition, y: 0, width: containerVieww.frame.size.width / CGFloat(viewControllerIDs.count), height: topBarHeight)
buttonsXPosition += containerVieww.frame.size.width / CGFloat(viewControllerIDs.count)
}
}
func applicationWillEnterBackground() {
//Save the current page
currentPage = Int(scrollView.contentOffset.x / scrollView.bounds.size.width)
print("Haydar")
}
func orientationWillChange() {
//Save the current page
currentPage = Int(scrollView.contentOffset.x / scrollView.bounds.size.width)
}
func orientationDidChange() {
//Update the view
alignSubviews()
scrollView.contentOffset = CGPoint(x: CGFloat(currentPage) * scrollView.frame.size.width, y: 0)
}
func applicationWillEnterForeground() {
alignSubviews()
scrollView.contentOffset = CGPoint(x: CGFloat(currentPage) * scrollView.frame.size.width, y: 0)
initializeWithVCIDsArrayAndButtonTitlesArray(VCIDsArray: buttonTitles, buttonTitlesArray: buttonTitles)
print("ForegroundHound")
}
public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let previousPage : NSInteger = currentPage
let pageWidth : CGFloat = scrollView.frame.size.width
let fractionalPage = scrollView.contentOffset.x / pageWidth
let page : NSInteger = Int(round(fractionalPage))
if (previousPage != page) {
currentPage = page;
}
}
deinit {
NotificationCenter.default.removeObserver(self)
print("deinittta")
}
}
extension SwiftPages: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Load the pages that are now on screen
loadVisiblePages()
// The calculations for the animated bar's movements
// The offset addition is based on the width of the animated bar (button width times 0.8)
let offsetAddition = (containerVieww.frame.size.width / CGFloat(viewControllerIDs.count)) * 0.1
animatedBar.frame = CGRect(x: (offsetAddition + (scrollView.contentOffset.x / CGFloat(viewControllerIDs.count))), y: animatedBar.frame.origin.y, width: animatedBar.frame.size.width, height: animatedBar.frame.size.height)
}
}

Resources