I am first drawing a rectangle on the screen with "UITouch". Afterwards i am taking a screenshot of the whole screen and attempting to crop the image with the positioning of the drawn rectangle.
All my attempts crop the image in an incorrect position. What am i missing? How can this be achieved?
1: Drawing a rectangle and storing the drawn position as global variables.
func drawSelectionArea(fromPoint: CGPoint, toPoint: CGPoint) {
let rect = CGRect(x: min(fromPoint.x, toPoint.x),
y: min(fromPoint.y, toPoint.y),
width: abs(fromPoint.x - toPoint.x),
height: abs(fromPoint.y - toPoint.y));
overlay.frame = rect
xCurrent = min(fromPoint.x, toPoint.x)
yCurrent = min(fromPoint.y, toPoint.y)
widthCurrent = abs(fromPoint.x - toPoint.x)
heightCurrent = abs(fromPoint.y - toPoint.y)
}
2: Taking a screenshot of the whole screen.
@IBAction func createScreenshot(_ sender: Any) {
let imageSize = UIScreen.main.bounds.size as CGSize;
UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
let context = UIGraphicsGetCurrentContext()
for obj : AnyObject in UIApplication.shared.windows {
if let window = obj as? UIWindow {
if window.responds(to: #selector(getter: UIWindow.screen)) || window.screen == UIScreen.main {
context!.saveGState();
context!.translateBy(x: window.center.x, y: window.center.y);
context!.concatenate(window.transform);
context!.translateBy(x: -window.bounds.size.width * window.layer.anchorPoint.x,
y: -window.bounds.size.height * window.layer.anchorPoint.y);
window.layer.render(in: context!)
context!.restoreGState();
}
}
}
let imageContext = UIGraphicsGetImageFromCurrentImageContext();
let image = self.cropImage(screenshot: imageContext!)
}
3: Attempting to crop the screenshot with the same positioning as the drawn rectangle.
func cropImage(screenshot: UIImage) -> UIImage {
let crop = CGRectMake(self.xCurrent, self.yCurrent,
self.widthCurrent,
self.heightCurrent)
let cgImage = screenshot.cgImage!.cropping(to: crop)
let image: UIImage = UIImage(cgImage: cgImage!)
return image
}
You should be able to get rid of almost all of that code...
Try replacing your func with this:
@IBAction func createScreenshot(_ sender: Any) {
// hide the overlay while we capture the view
overlay.isHidden = true
let renderRect: CGRect = overlay.frame
let rndr = UIGraphicsImageRenderer(bounds: renderRect)
let croppedImage = rndr.image { ctx in
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}
// show the overlay again
overlay.isHidden = false
// do something with croppedImage
}
Edit - based on comments...
OK, as I understand your goal now, you want to present
a view controller (over current context) with a clear background. That controller will have the "drawable" overlay view to define the area to capture ... along with, I'm assuming, a "Capture" button.
You can still use the above code - but you'll want to get a reference to the view
from the controller that presented
the overlay-view controller:
@IBAction func createScreenshot(_ sender: Any) {
// make sure we have been presented and
// get a reference to the *presenting* controller's view
guard let pc = self.presentingViewController, let v = pc.view else { return }
let renderRect: CGRect = self.overlay.frame
let rndr = UIGraphicsImageRenderer(bounds: renderRect)
let croppedImage = rndr.image { ctx in
// draw the presenting controller's view
v.drawHierarchy(in: v.bounds, afterScreenUpdates: true)
}
// do something with the croppedImage
}
Here's a quick example...
We'll start with a view controller containing a button and a "grid" of labels:
class CropShotVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// let's fill the view with a grid of labels
var colors: [[UIColor]] = [
[.gray, .white], [.systemGreen, .white], [.systemBlue, .white],
[.cyan, .black], [.yellow, .black], [.magenta, .black],
[.green, .black], [.blue, .white], [.systemYellow, .black],
]
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fillEqually
for r in 1...6 {
let rowStack = UIStackView()
rowStack.distribution = .fillEqually
for c in 1...3 {
let v = UILabel()
v.numberOfLines = 0
v.textAlignment = .center
v.text = "Row: \(r)\nCol: \(c)"
let colorPair = colors.removeFirst()
v.backgroundColor = colorPair[0]
v.textColor = colorPair[1]
colors.append(colorPair)
rowStack.addArrangedSubview(v)
}
stack.addArrangedSubview(rowStack)
}
// add a white "panel" view at the top
// to hold the "Present Capture VC" button
let panel = UIView()
panel.backgroundColor = .white
var cfg = UIButton.Configuration.filled()
cfg.title = "Present Capture VC"
let btnA = UIButton(configuration: cfg)
btnA.addAction (
UIAction { _ in
if self.presentedViewController != nil {
return
}
let vc = RectOverVC()
vc.modalPresentationStyle = .overCurrentContext
self.present(vc, animated: true)
}, for: .touchUpInside
)
btnA.setContentHuggingPriority(.required, for: .vertical)
panel.translatesAutoresizingMaskIntoConstraints = false
btnA.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(panel)
view.addSubview(btnA)
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
panel.topAnchor.constraint(equalTo: g.topAnchor),
panel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
panel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
btnA.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
btnA.centerXAnchor.constraint(equalTo: g.centerXAnchor),
btnA.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -8.0),
stack.topAnchor.constraint(equalTo: panel.bottomAnchor, constant: 0.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
])
view.backgroundColor = .systemBackground
}
}
This is the controller we will present, where the user can define the capture area (for this example, not "sizable" ... just a draggable view):
class RectOverVC: UIViewController {
let overlay: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
// add a translucent white "panel" view across the top
let panel = UIView()
panel.backgroundColor = .white.withAlphaComponent(0.8)
// we'll add "Cancel" and "Capture" buttons
var cfg = UIButton.Configuration.filled()
cfg.title = "Cancel"
let btnA = UIButton(configuration: cfg)
btnA.addAction (
UIAction { _ in
self.dismiss(animated: true)
}, for: .touchUpInside
)
cfg.title = "Capture"
let btnB = UIButton(configuration: cfg)
btnB.addAction (
UIAction { _ in
self.createScreenshot(self)
}, for: .touchUpInside
)
panel.translatesAutoresizingMaskIntoConstraints = false
btnA.translatesAutoresizingMaskIntoConstraints = false
btnB.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(panel)
panel.addSubview(btnA)
panel.addSubview(btnB)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
panel.topAnchor.constraint(equalTo: g.topAnchor),
panel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
panel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
btnA.topAnchor.constraint(equalTo: panel.topAnchor, constant: 8.0),
btnA.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 8.0),
btnB.topAnchor.constraint(equalTo: panel.topAnchor, constant: 8.0),
btnB.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -8.0),
btnB.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -8.0),
])
// add a red-bordered draggable view
// to select the capture area
// we'll give it a translucent-white background to make it easy to see
overlay.backgroundColor = .white.withAlphaComponent(0.60)
overlay.layer.borderColor = UIColor.red.cgColor
overlay.layer.borderWidth = 2
view.addSubview(overlay)
// pan gesture to make the overlay view draggable
let pg = UIPanGestureRecognizer(target: self, action: #selector(panView(_:)))
overlay.addGestureRecognizer(pg)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if overlay.frame == .zero {
// start the overlayView centered
overlay.frame = .init(origin: .zero, size: .init(width: 160.0, height: 240.0))
overlay.center = view.center
}
}
@objc func panView(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view else { return }
let translation = sender.translation(in: self.view)
v.frame = v.frame.offsetBy(dx: translation.x, dy: translation.y)
sender.setTranslation(CGPoint(x: 0, y: 0), in: v)
}
@IBAction func createScreenshot(_ sender: Any) {
// make sure we have been presented and
// get a reference to the *presenting* controller's view
guard let pc = self.presentingViewController, let v = pc.view else { return }
let renderRect: CGRect = self.overlay.frame
let rndr = UIGraphicsImageRenderer(bounds: renderRect)
let croppedImage = rndr.image { ctx in
// draw the presenting controller's view
v.drawHierarchy(in: v.bounds, afterScreenUpdates: true)
}
// do something with the croppedImage
// for this example, we'll show it in a presented controller
// present another controller to show the captured UIImage
let vc = ShowCroppedImageVC()
vc.theImage = croppedImage
self.present(vc, animated: true)
}
}
and a view controller to display the resulting UIImage
:
class ShowCroppedImageVC: UIViewController {
var theImage: UIImage?
let imgView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
guard let img = theImage else { return }
// dark-gray view to use as a "frame" for the image view
let frameView = UIView()
frameView.backgroundColor = .darkGray
let imgView = UIImageView(image: img)
[frameView, imgView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
frameView.addSubview(imgView)
view.addSubview(frameView)
NSLayoutConstraint.activate([
imgView.widthAnchor.constraint(equalToConstant: 160.0),
imgView.heightAnchor.constraint(equalToConstant: 240.0),
frameView.widthAnchor.constraint(equalTo: imgView.widthAnchor, constant: 40.0),
frameView.heightAnchor.constraint(equalTo: imgView.heightAnchor, constant: 40.0),
imgView.centerXAnchor.constraint(equalTo: frameView.centerXAnchor),
imgView.centerYAnchor.constraint(equalTo: frameView.centerYAnchor),
frameView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
frameView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
It will look like this on launch:
Tapping "Present Capture VC" will look like this (capture area starts centered):
Here I've dragged the rect to the lower-right:
and tapping "Capture" will display the captured UIImage
: