Why can't I draw on an image when I subclass UIView? - ios

I am trying to draw on an image from the camera roll using the code from this tutorial. After selecting a photo from the camera roll, imageView is set to the photo. When the user presses the draw button, tempImageView is added over the photo, so the user can draw onto it. However, when I attempt to draw on the image, nothing happens. Why?
Code:
class EditViewController: UIViewController{
var tempImageView: DrawableView!
var imageView: UIImageView = UIImageView(image: UIImage(named: "ClearImage"))
func draw(sender: UIButton!) {
tempImageView = DrawableView(frame: imageView.bounds)
tempImageView.backgroundColor = UIColor.clearColor()
self.view.addSubview(tempImageView)
}
}
class DrawableView: UIView {
let path=UIBezierPath()
var previousPoint:CGPoint
var lineWidth:CGFloat=10.0
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override init(frame: CGRect) {
previousPoint=CGPoint.zero
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
previousPoint=CGPoint.zero
super.init(coder: aDecoder)
let panGestureRecognizer=UIPanGestureRecognizer(target: self, action: "pan:")
panGestureRecognizer.maximumNumberOfTouches=1
self.addGestureRecognizer(panGestureRecognizer)
}
override func drawRect(rect: CGRect) {
// Drawing code
UIColor.greenColor().setStroke()
path.stroke()
path.lineWidth=lineWidth
}
func pan(panGestureRecognizer:UIPanGestureRecognizer)->Void
{
let currentPoint=panGestureRecognizer.locationInView(self)
let midPoint=self.midPoint(previousPoint, p1: currentPoint)
if panGestureRecognizer.state == .Began
{
path.moveToPoint(currentPoint)
}
else if panGestureRecognizer.state == .Changed
{
path.addQuadCurveToPoint(midPoint,controlPoint: previousPoint)
}
previousPoint=currentPoint
self.setNeedsDisplay()
}
func midPoint(p0:CGPoint,p1:CGPoint)->CGPoint
{
let x=(p0.x+p1.x)/2
let y=(p0.y+p1.y)/2
return CGPoint(x: x, y: y)
}
}

make sure that tempImageView.userIteractionEnabled = YES. by default this property for UIImageView is NO.
after put breakpoint to you gestureRecogniser method, see if it's being called.

Related

How to detect 2 fingers tap and which views are tap in Swift [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I'm making falling tiles game. I already implemented the normal tile when you tap (single) it will be deleted. Next I want to make the double tile when you tap the 2 tiles at the same time these will be deleted. But I have no idea to detect "whether you tap 2 double tiles at the same time".
The double tiles fall from top of the screen from the bottom of screen. If tiles achieved to the bottom, new tiles will appear from the top of screen.
And now I could detect single tap and tap at the same time by 2 fingers.
How can I make the double tiles?
UPDATE:
So far I can detect 2 fingers touches by GestureRecognizer. But can't detect "which views you touched". The log of "gesture.view" is only one view even you touched 2 tiles. What I want to do is like the below code:
if gesture.view.count == 2 && gesture.view is GameTileDouble {
print("touched 2 doubleTiles at the same time")
}
Tiles Class
class GameTile: UIImageView {
init(named: String, frame: CGRect) {
super.init(frame: frame)
super.image = (UIImage(named: named))
super.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Normal Tile Class
class GameTileNormal: GameTile {
let namedDefault: String
var frameDefault: CGRect
let isHiddenDefault: Bool
var isUserInteractionEnabledDefault: Bool
let colorName: UIColor
var tileLane: Int
init(
named: String,
frame: CGRect,
isHidden: Bool = false,
isUserInteractionEnabled: Bool = true,
color: UIColor = UIColor.blue,
lane: Int) {
namedDefault = named
isHiddenDefault = isHidden
frameDefault = frame
isUserInteractionEnabledDefault = isUserInteractionEnabled
colorName = color
tileLane = lane
super.init(named: named, frame: frame)
super.isHidden = isHiddenDefault
super.isUserInteractionEnabled = isUserInteractionEnabledDefault
super.backgroundColor = colorName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Double Tile Class
class GameTileDouble: GameTile {
let namedDefault: String
var frameDefault: CGRect
let isHiddenDefault: Bool
let isUserInteractionEnabledDefault: Bool
let colorName: UIColor
var tileLane: Int
init(
named: String,
frame: CGRect,
isHidden: Bool = false,
isUserInteractionEnabled: Bool = true,
color: UIColor = UIColor.yellow,
lane: Int) {
namedDefault = named
frameDefault = frame
isHiddenDefault = isHidden
isUserInteractionEnabledDefault = isUserInteractionEnabled
colorName = color
tileLane = lane
super.init(named: named, frame: frame)
super.isHidden = isHiddenDefault
super.isUserInteractionEnabled = isUserInteractionEnabledDefault
super.backgroundColor = colorName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let tileNormal = GameTileNormal.init(named: "normal.png",frame: CGRect(x: 0, y:0, width: 50, height: 50),isUserInteractionEnabled: true, lane: 1)
self.view.addSubview(tileNormal)
let tileDouble1 = GameTileDouble.init(named: "black.png",frame: CGRect(x:150, y: 0, width: 50, height: 50),isUserInteractionEnabled: true, lane: 2)
self.view.addSubview(tileDouble1)
let tileDouble2 = GameTileDouble.init(named: "black.png",frame: CGRect(x: 200, y: 0, width: 50, height: 50),isUserInteractionEnabled: true, lane: 3)
self.view.addSubview(tileDouble2)
//detect single tap
let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(StandardGameViewController.handleSingleTapGesture(_:)))
singleTapGesture.numberOfTapsRequired = 1
singleTapGesture.numberOfTouchesRequired = 1
view.addGestureRecognizer(singleTapGesture)
//detect tap at the same time by 2 fingers
let doubleTapGesture = UITapGestureRecognizer(target: self , action: #selector(StandardGameViewController.handleDoubleTapGesture(_:)))
doubleTapGesture.numberOfTouchesRequired = 2
view.addGestureRecognizer(doubleTapGesture)
}
//something function to move tiles...
#objc func handleSingleTapGesture(_ gesture: UITapGestureRecognizer) {
print("single tap")
//if tapped tileNormal, the normalTile is deleted (removeFromSuperview)
}
#objc func handleDoubleTapGesture(_ gesture: UITapGestureRecognizer) {
print("tap at the same time by 2 fingers")
//if tapped doubleTile1 and tileDouble1 and tileDouble2, these tiles will be deleted (this is the point I don't have any idea
}
You just need to enable multi touch on the view on which your tiles are added
//Assuming it's on self.view
self.view.isMultipleTouchEnabled = true
Note: Doing this this will support N number of touches.
Then you can make do with only handleSingleTapGesture:.
No need for handleDoubleTapGesture:.
Just ensure you add a tap gesture on every tile and assign the tap action to handleSingleTapGesture: so it will be called for every tile you tap on.
Then simply delete the tile like so:
gesture.view?.removeFromSuperview()
Example:
#IBAction func handleSingleTapGesture(_ gesture: UITapGestureRecognizer) {
guard let view = gesture.view else { return }
print("Current view tapped: ", terminator: "")
if view.isKind(of: GameTileNormal.self) {
print("Normal Tile")
}
else if view.isKind(of: GameTileDouble.self) {
print("Double Tile")
}
view.removeFromSuperview()
}

Why touchesBegan listener not called(Swift 4)?

I created simple button - class Btn: UIControl and then add to parent View. Inside my Btn class I have override func touchesBegan.
Why touchesBegan not callend when I tapped to the button?
As I think, my Btn extends UIControl and Btn have to call touchesBegan.
My code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add my button
let button = Btn()
view.addSubview(button)
}
}
class Btn: UIControl {
fileprivate let button: UIButton = {
let swapButton = UIButton()
let size = CGFloat(50)
swapButton.frame.size = CGSize(width: size, height: size)
swapButton.backgroundColor = UIColor.green
return swapButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addSubview(button)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Why it not called?
print("test")
}
}
Play around with the following code for playgrounds based on your code, and you will understand what's going on. While your control wants to process the touchesBegan callback, it has an UIButton inside of it, which consumes the event itself. In the example below, the inner button is taking one quarter of the Btn size (red part), if you click on it, the "test" text won't get printed. The rest of the Btn space (green part) does not contain any other view that would consume the touchesBegan, thus clicking there will print "test" text.
I recommend you to take a look at the responder chain.
import PlaygroundSupport
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add my button
let button = Btn(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.addSubview(button)
}
}
class Btn: UIControl {
fileprivate let button: UIButton = {
let swapButton = UIButton()
let size = CGFloat(50)
swapButton.frame.size = CGSize(width: size, height: size)
swapButton.backgroundColor = UIColor.green
return swapButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button)
self.backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addSubview(button)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Why it not called?
print("test")
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ViewController()

iOS Swift 3 - Clearing the UIView

I'm building an iOS app where I have a class named "DrawableCanvas" that extends UIView. The idea is to enable users to draw on UIViews in the Storyboard that extend the class I made.
The drawing works fine and smooth at this point. Here's the code I used:
class DrawableView: UIView {
let path=UIBezierPath()
var previousPoint:CGPoint
var lineWidth:CGFloat=1.0
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override init(frame: CGRect) {
previousPoint=CGPoint.zero
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
previousPoint=CGPoint.zero
super.init(coder: aDecoder)
let panGestureRecognizer=UIPanGestureRecognizer(target: self, action: #selector(pan))
panGestureRecognizer.maximumNumberOfTouches=1
self.addGestureRecognizer(panGestureRecognizer)
}
override func draw(_ rect: CGRect) {
// Drawing code
UIColor.black.setStroke()
path.stroke()
path.lineWidth=lineWidth
}
func pan(panGestureRecognizer:UIPanGestureRecognizer)->Void{
let currentPoint=panGestureRecognizer.location(in: self)
let midPoint=self.midPoint(p0: previousPoint, p1: currentPoint)
if panGestureRecognizer.state == .began{
path.move(to: currentPoint)
}
else if panGestureRecognizer.state == .changed{
path.addQuadCurve(to: midPoint,controlPoint: previousPoint)
}
previousPoint=currentPoint
self.setNeedsDisplay()
}
func midPoint(p0:CGPoint, p1:CGPoint)->CGPoint{
let x=(p0.x+p1.x)/2
let y=(p0.y+p1.y)/2
return CGPoint(x: x, y: y)
}
}
Of course, I have to clear the view on a button press, and here's my button press action:
#IBAction func reset1(_ sender: AnyObject) {
print("reset hit!")
sig1View.setNeedsLayout()
sig1View.setNeedsDisplay()
}
where sig1View is the view I'm trying to reset.
This didn't work out, so what I tried instead was to write a reloadData() in the DrawableView class:
func reloadData() {
print("reload data hit")
self.setNeedsDisplay()
}
The function got called, but that too, didn't work. How do I clear or reset a UIView in iOS Swift 3?
Call your DrawableView's (instance's) path.removeAllPoints function to remove all the drawings/path.
So following your code, reset1 should look like this now:
#IBAction func reset1(_ sender: AnyObject) {
print("reset hit!")
sig1View.path.removeAllPoints()
sig1View.setNeedsDisplay()
}
The problem is that, although your question talks about "resetting", your code does not in fact reset anything. You are saying:
override func draw(_ rect: CGRect) {
// Drawing code
UIColor.black.setStroke()
path.stroke()
path.lineWidth=lineWidth
}
So that's what's happening. As long as path contains a drawable path, that is what is drawn in your view whenever the view redraws itself. If that's not what you want, change the path, or change the implementation of draw.
Add this method to DrawableView class and call it with sig1View instance:
func clear()
{
path.removeAllPoints()
setNeedsDisplay()
}
and call it from the reset method.
Usage:
sig1View.clear()

Why Is My IBDesignable UIButton Subclass Not Rendering In Interface Builder?

In the screen shot below please note that the DropDownButton (the selected view) is not being live rendered. Also, please note the "Designables Up To Date" in the Identity Inspector. Finally, please note the two break points in the assistant editor: if I execute Editor -> Debug Selected Views then both of these break points are hit.
Here's what It looks like when I run it:
Here's the code:
#IBDesignable
class DropDownButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
if image(for: .normal) == nil {
//setImage(#imageLiteral(resourceName: "DropDown"), for: .normal)
let bundle = Bundle(for: DropDownButton.self)
if let image = UIImage(named: "DropDown", in: bundle, compatibleWith: nil) {
setImage(image, for: .normal) // Editor -> Debug Selected Views reaches this statement
}
}
if title(for: .normal) == nil {
setTitle("DropDown", for: .normal) // Editor -> Debug Selected Views reaches this statement
}
addTarget(self, action: #selector(toggle), for: .touchUpInside)
}
override func layoutSubviews() {
super.layoutSubviews()
if var imageFrame = imageView?.frame, var labelFrame = titleLabel?.frame {
labelFrame.origin.x = contentEdgeInsets.left
imageFrame.origin.x = labelFrame.origin.x + labelFrame.width + 2
imageView?.frame = imageFrame
titleLabel?.frame = labelFrame
}
}
override func setTitle(_ title: String?, for state: UIControlState) {
super.setTitle(title, for: state)
sizeToFit()
}
public var collapsed: Bool {
return imageView?.transform == CGAffineTransform.identity
}
public var expanded: Bool {
return !collapsed
}
private func collapse() {
imageView?.transform = CGAffineTransform.identity
}
private func expand() {
imageView?.transform = CGAffineTransform(rotationAngle: .pi)
}
#objc private func toggle(_: UIButton) {
if collapsed { expand() } else { collapse() }
}
}
First Edit:
I added the prepareForInterfaceBuilder method as per #DonMag's answer. Doing so made an improvement but there is still something wrong: Interface builder seems confused about the frame. When I select the button only the title is selected, not the image (i.e. triangle). I added a border; it goes around both the title and the image. Here is a picture:
If I drag the button to a new position then everything moves, title and image.
Also, it surprised me that prepareForInterfaceBuilder made a difference. My understanding of this method it that it allows me to do interface builder only setup such as providing dummy data.
Add this:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
initialize()
}

How does initializing UIView subclass properties affect superview's center?

I'm having a problem with the graphOrigin property in my UIView subclass. When I defined graphOrigin as a computed variable, it convert's the superview's center point to this view's center point and displays the graph in the center of the screen. This does not happen when the variable isn't computed. See code and screenshot for the working case:
class GraphX_YCoordinateView: UIView {
var graphOrigin: CGPoint {
return convertPoint(center, fromView: superview)
}
#IBInspectable var scale: CGFloat = 50 {
didSet {
setNeedsDisplay()
}
}
override func drawRect(rect: CGRect) {
// Draw X-Y axes in the view
let axesDrawer = AxesDrawer(contentScaleFactor: contentScaleFactor)
axesDrawer.drawAxesInRect(bounds, origin: graphOrigin, pointsPerUnit: scale)
}
}
AxesDrawer is a class that draws axes in the current view, here is the method signature for drawAxesInRect:
drawAxesInRect(bounds: CGRect, origin: CGPoint, pointsPerUnit: CGFloat)
And here is the code and screenshot for the case that doesn't work:
class GraphX_YCoordinateView: UIView {
var graphOrigin: CGPoint! {
didSet {
setNeedsDisplay()
}
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
graphOrigin = convertPoint(center, fromView: superview)
}
#IBInspectable var scale: CGFloat = 50 {
didSet {
setNeedsDisplay()
}
}
override func drawRect(rect: CGRect) {
// Draw X-Y axes
let axesDrawer = AxesDrawer(contentScaleFactor: contentScaleFactor)
axesDrawer.drawAxesInRect(bounds, origin: graphOrigin, pointsPerUnit: scale)
}
}
So literally all I changed was initializing the graphOrigin property in place and computing it in the initializer. I didn't touch the StoryBoard at all while editing this code.
I tried initializing the variable inline:
var graphOrigin = convertPoint(center, fromView: superview)
But this wasn't allowed because the implicit self is not initialized when properties are computed.
Can anyone explain why the superview's center seems to change location depending on how the variable is initialized?
This function
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
graphOrigin = convertPoint(center, fromView: superview)
}
means that you are loading view from xib, and at this time the view dimension is 600:600 (look at your xib). So that your graphOrigin = 300:300. This is why you see the second picture.
To fix that problem, you should compute the graphOrigin after the view finish layout in viewDidLayout.

Resources