jamesMcKey
jamesMcKey

Reputation: 491

How to generate a custom UIView that is above a UIImageView that has a circular hole punched in the middle that can see through to the UIImageView?

I am trying to punch a circular hole through a UIView that is above a UIImageView, whereby the hole can see through to the image below (I would like to interact with this image through the hole with a GestureRecognizer later). I have 2 problems, I cannot get the circular hole to centre to the middle of the UIImageView (it is currently centred to the top left of the screen), and that the effect that I am getting is the opposite to what i am trying to achieve (Everything outside of the circle is visible). Below is my code. Please can someone advise?

Result: enter image description here

class UploadProfileImageViewController: UIViewController {

var scrollView: ReadyToUseScrollView!
let container: UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.black
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

let imgView: UIImageView = {
    let imgView = UIImageView()
    imgView.backgroundColor = UIColor.black
    imgView.contentMode = .scaleAspectFit
    imgView.image = UIImage.init(named: "soldier")!
    imgView.translatesAutoresizingMaskIntoConstraints = false
    return imgView
}()

var overlay: UIView!

override func viewDidLoad() {
    super.viewDidLoad()
    setup()
}

private func setup(){
    view.backgroundColor = UIColor.white
    setupViews()
}

override func viewDidLayoutSubviews() {

    overlay.center = imgView.center
    print("imgView.center: \(imgView.center)")
    overlay.layer.layoutIfNeeded() // I have also tried view.layoutIfNeeded()
}


private func setupViews(){
    let s = view.safeAreaLayoutGuide

    view.addSubview(imgView)
    imgView.topAnchor.constraint(equalTo: s.topAnchor).isActive = true
    imgView.leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
    imgView.trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
    imgView.heightAnchor.constraint(equalTo: s.heightAnchor, multiplier: 0.7).isActive = true

    overlay = Overlay.init(frame: .zero, center: imgView.center)
    print("setup.imgView.center: \(imgView.center)")
    view.addSubview(overlay)
    overlay.translatesAutoresizingMaskIntoConstraints = false
    overlay.topAnchor.constraint(equalTo: s.topAnchor).isActive = true
    overlay.leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
    overlay.trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
    overlay.bottomAnchor.constraint(equalTo: imgView.bottomAnchor).isActive = true

}

private func deg2rad( number: Double) -> CGFloat{
       let rad = number * .pi / 180
       return CGFloat.init(rad)
   }
}



class Overlay: UIView{

var path: UIBezierPath!
var viewCenter: CGPoint?

init(frame: CGRect, center: CGPoint) {
    super.init(frame: frame)

    self.viewCenter = center
    setup()
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

private func setup(){

    backgroundColor = UIColor.black.withAlphaComponent(0.8)

    guard  let path = createCirclePath() else {return}
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path.cgPath
    shapeLayer.fillRule = CAShapeLayerFillRule.evenOdd
    self.layer.mask = shapeLayer
}


private func createCirclePath() -> UIBezierPath?{
    guard let center = self.viewCenter else{return nil}
    let circlePath = UIBezierPath()
    circlePath.addArc(withCenter: center, radius: 200, startAngle: 0, endAngle: deg2rad(number: 360), clockwise: true)
    return circlePath
}

private func deg2rad( number: Double) -> CGFloat{
    let rad = number * .pi / 180
    return CGFloat.init(rad)
}
}

CONSOLE:

setup.imgView.center: (0.0, 0.0)
imgView.center: (207.0, 359.0)

Upvotes: 0

Views: 40

Answers (1)

nicksarno
nicksarno

Reputation: 4245

Try getting rid of the overlay you have and instead add the below UIView. It's basically a circular UIView with a giant black border, but it takes up the whole screen so the user can't tell. FYI, you need to use .frame to position items on the screen. The below puts the circle in the center of the screen. If you want the center of the image, replace self.view.frame with self. imgView.frame... Play around with circleSize and borderSize until you get the circle size you want.

    let circle = UIView()
    let circleSize: CGFloat = self.view.frame.height * 2 //must be bigger than the screen
    let x = (self.view.frame.width / 2) - (circleSize / 2)
    let y = (self.view.frame.height / 2) - (circleSize / 2)
    circle.frame = CGRect(x: x, y: y, width: circleSize, height: circleSize)

    let borderSize = (circleSize / 2) * 0.9 //the size of the inner circle will be circleSize - borderSize
    circle.backgroundColor = .clear
    circle.layer.cornerRadius = circle.frame.height / 2
    circle.layer.borderColor = UIColor.black.cgColor
    circle.layer.borderWidth = borderSize

    view.addSubview(circle)

Upvotes: 1

Related Questions