UITapGestureRecognizer not working with custom view class - ios

UITapGestureRecognizer works perfectly fine in code 1. tapAction is called as expected.
However it's not working in code 2. Can somebody please tell me what's wrong in code 2?
(This and this are quite similar questions, but still couldn't figure it out) 🧐
Code 1:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myView : UIView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
myView.backgroundColor = .red
myView.addGestureRecognizer( UITapGestureRecognizer(target:self,action:#selector(self.tapAction)) )
self.view.addSubview(myView)
}
#objc func tapAction() {
print("tapped")
}
}
Code 2:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
self.view.addSubview(myView)
}
}
class MyView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
initView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initView(){
let myView : UIView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
myView.backgroundColor = .red
myView.addGestureRecognizer(UITapGestureRecognizer(target:self,action:#selector(self.doSomethingOnTap)))
addSubview(myView)
}
#objc func doSomethingOnTap() {
print("tapped")
}
}

You're creating a subview that in this particular case goes out of the parent bounds because the view main view has 100 height and 100 width and the subview is placed at x: 100 and y: 100, resulting to be positioned at the exact end of the parent.
The subview you create in initView should have (x: 0, y: 0) origin.

Related

When inheriting UIView and creating an instance for the new class, it does not create a view

I have created a new class inheriting UIView. When I create an instance of the new class, the view does not seem to be created.
This is my class.
class QuestionView: UIView {
var metrics : [String : CGFloat] = [:]
override init(frame : CGRect){
super.init(frame: frame)
self.backgroundColor = UIColor.blue
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here I have created an instance of this class to have the following values
x = 0, y = middle of the screen, width = as wide as the screen,
height = 400
class mainView : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var questionView = QuestionView(frame: CGRect(x: 0, y: self.view.frame.height/2, width: self.view.frame.width, height: 400))
self.view.addSubView(questionView)
}
}
I find no view being created.
Add subview(questionView) in mainView class in viewDidLoad method.
self.view.addSubview(questionView)
you can also test the view by setting the background color
questionView.backgroundColor = UIColor.red
You should add all views created programmatically in viewDidLayoutSubView like below
override func viewDidLayoutSubviews() {
var questionView = QuestionView(frame: CGRect(x: 0, y: self.view.frame.height/2, width: self.view.frame.width, height: 400))
self.view.addSubView(questionView)
}
Try following code to get the view in front, use bringSubviewToFront function.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let questionView = QuestionView(frame: CGRect(x: 0, y: self.view.frame.height/2, width: self.view.frame.width, height: 400))
self.view.addSubview(questionView)
self.view.bringSubviewToFront(questionView)
}

Error "EXC BAD ACCESS"with subclass in Swift

I made 3 files GameViewController, GameView, GameItemView but if GameItemView inherits GameView than
EXC BAD ACCESS error appeared
like below code.
class GameViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(gameView)
gameView.gameViewController = self
}
//....
}
class GameView: UIView {
weak var gameViewController: GameViewController! //when GameViewController will appear, BAD ACCESS error appear and stop here
weak var gameItemView: GameItemView!
override init(frame: CGRect) {
super.init(frame: frame)
gameItemView = GameItemView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.addSubview(gameItemView)
}
//....
}
class GameItemView: GameView {
override init(frame: CGRect) {
super.init(frame: frame)
//add some views
}
//...
}
I wrote some code of GameItemView on GameView before. But GameView's code became so long, so I moved a part of them to GameItemView. But then the error appeared. How can I solve it?
UPDATE:
I'll add the code where I got the error. I'm sorry I forgot to write it.
var gameGameViewController: GameViewController!
Why are you inheriting GamveViewItem from GameView? If you wanted to do so then why are coding like above which causes recursive calls to the init of GamveViewItem & GameView. Please either change the parent class of the GamveViewItem to UIView or some other or break the recursive pattern caused by the init.
GameView's init
override init(frame: CGRect) {
super.init(frame: frame)
// Here you're calling GameItemView's init, now go to GameItemView's init
gameItemView = GameItemView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.addSubview(gameItemView)
}
GameItemView's init
override init(frame: CGRect) {
// You're calling GameView's init here and it goes back to
// there and then this keep on happening
super.init(frame: frame)
//add some views
}
try this:
class GameViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(gameView)
gameView.gameViewController = self
}
//....
}
class GameView: UIView {
var gameViewController = GameViewController()
var gameItemView = GameItemView()
override init(frame: CGRect) {
super.init(frame:frame)
gameItemView = GameItemView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.addSubview(gameItemView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameItemView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
//add some views
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Swift add Custom View to screen with UIApplication.shared.keyWindow?.addSubview

hope this is an easy one...
I am in a empty new project.
I have added a custom view called MyCustomView:
import UIKit
public class MyCustomView: UIView{
private var littleView: UIView!
open class func show() -> UIView{
let bigView = MyCustomView()
bigView.configureView()
UIApplication.shared.keyWindow?.addSubview(bigView)
return bigView
}
private func configureView(){
let screenSize = UIScreen.main.bounds.size
self.frame = CGRect(x: 0,
y: 0,
width: screenSize.width,
height: screenSize.height)
littleView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
littleView.backgroundColor = .black
addSubview(littleView)
}
}
In the ViewController doing this:
override func viewDidLoad() {
let test = MyFirstView.show()
}
I hoped this will present the view, but I still have to use self.view.addSubview(test) to see it....
I thought with UIApplication.shared.keyWindow?.addSubview(bigView) and adding a subView to it, it should present the View.
What am I missing?
Add the subview in viewDidAppear
override func viewDidAppear() {
let test = MyFirstView.show()
}

Can a UIView subclass create other custom view instances?

I have a UIView subclass called LifeCounter, of which I want to create one for each player. I was able to add two views to Main.storyboard, set their class to LifeCounter through the Attributes panel and create multiple instances that way, connect to the view controller, change properties, etc.
What I was thinking about now was creating a larger view, GameHeader, that will hold the LifeCounters and some other supplementary information such as time, game reset button, etc. GameHeader is a UIView subclass, but I can't get it to draw my LifeCounter views in the simulator and I have no idea why.
GameHeader is currently a view dragged into the storyboard, and given it's class with the Attributes panel.
GameHeader.swift
import UIKit
class GameHeader: UIView {
// MARK: Properties
// The Frame
let topFrame = CGRect(x: 0, y: 0, width: 320, height: 200)
// MARK: Initlization
required init() {
super.init(frame: topFrame)
// Adds the player one counter
let playerOneCounter = LifeCounter()
addSubview(playerOneCounter)
}
required init(coder aDecoder: NSCoder) {
// Calls the super class (UIView) initializer
super.init(coder: aDecoder)
}
}
LifeCounter.swift
import UIKit
class LifeCounter: UIView {
// MARK: Propterties
// Starting life total
var lifeTotal = 20 {
didSet {
// Updates the layout whenever the lifeTotal is updated
setNeedsLayout()
}
}
// Creates the UI Labels
// All created views need a defined frame for where they sit
// Is this neccesary outside of the init? Is there a better way?
var counter = UILabel(frame: CGRect(x: 0, y: 20, width: 100, height: 90))
var playerName = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
var winner = ""
// MARK: Initlization
// First init. LifeCounter takes a frame parameter, the adds the labels etc.
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
self.addLifeCounter()
}
required init(coder aDecoder: NSCoder) {
// Calls the super class (UIView) initializer
super.init(coder: aDecoder)
}
func addLifeCounter() {
print("addLifeCounter is running")
// Styles life counter label
counter.textColor = UIColor.blackColor()
counter.textAlignment = .Center
counter.font = UIFont.boldSystemFontOfSize(72)
counter.text = String(lifeTotal)
// Styles playerName label
playerName.text = "Player Name"
playerName.textAlignment = .Center
// Button
let minusButton = UIButton(frame: CGRect(x: 5, y: 110, width: 40, height: 40))
let plusButton = UIButton(frame: CGRect(x: 55, y: 110, width: 40, height: 40))
minusButton.backgroundColor = UIColor.redColor()
plusButton.backgroundColor = UIColor.blueColor()
// Button action
minusButton.addTarget(self, action: "minusLife:", forControlEvents: .TouchDown)
plusButton.addTarget(self, action: "plusLife:", forControlEvents: .TouchDown)
addSubview(playerName)
addSubview(counter)
addSubview(minusButton)
addSubview(plusButton)
}
// MARK: Button actions
func minusLife(minusButton: UIButton) {
lifeTotal -= 1
counter.text = String(lifeTotal)
}
func plusLife(plusButton: UIButton) {
lifeTotal += 1
counter.text = String(lifeTotal)
}
}
init(coder:) is the initializer that is called when a view is created from a storyboard or nib. If you move the code that creates and adds LifeCounter instances there, it should work.
A good strategy to make it more reusable is to create a setup method that is called from both initializers so that it will run whether it comes from a storyboard/nib or it is instantiated programmatically.
class GameHeader: UIView {
let topFrame = CGRect(x: 0, y: 0, width: 320, height: 200)
required init() {
super.init(frame: topFrame)
self.setupSubviews()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupSubviews()
}
func setupSubviews() {
let playerOneCounter = LifeCounter()
addSubview(playerOneCounter)
}
}
I discovered the error was in putting the LifeController drawing code inside the init(frame:) block instead of with the Coder block. When a view is added through Interface Builder, it uses init(coder:) instead of init(frame:)

Strange behaviour - instantiating a UITextField in willMoveToSuperview as opposed to didMoveToSuperview

I'm working with XCode 6.3/iOS/Swift 1.2. I've just stumbled upon some strange behaviour that I don't understand (coming from a strong OO background).
Put simply, if I subclass a UIView and use it to instantiate some UITextFields; in the constructor, overriding didMoveToSuperview, on dynamically (ie, with a click event)... the UITextFields work as expected. However, when creating the UITextField object during willMoveToSuperview, it seems fine... but it doesn't respond to touch events.
This is demonstrated in the code below (a UIViewController and the subclassed UIView). I've added a gesture recognizer to the entire view containing the UITextFields. Clicking any of them other than the one creating during willMoveToSuperview, will move the focus to that textfield, and will ignore the touch event (as expected). However, clicking on the one added during willMoveToSuperview fires a touch event. Event without the touch event... this textfield remains unresponsive. Can anyone explain this behaviour?
import UIKit
class RootViewController:UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
self.view.frame = CGRectMake(0,0,500,500)
}
override func viewDidAppear(animated: Bool)
{
var uiview = ExtendedUIView()
view.addSubview(uiview)
}
}
class ExtendedUIView:UIView
{
internal var hasMovedToSuperView:Bool = false
override init (frame : CGRect)
{
super.init(frame : frame)
}
convenience init()
{
self.init(frame:CGRectMake(0,0,500,500))
var tf: UITextField = UITextField(frame: CGRect(x: 0, y: 50, width: 300, height: 40))
tf.backgroundColor = UIColor.greenColor()
tf.text = "init // ok"
addSubview(tf)
}
required init(coder aDecoder: NSCoder)
{
fatalError("This class does not support NSCoding")
}
override func willMoveToSuperview(newSuperview: UIView?)
{
if (!hasMovedToSuperView) {
var tf: UITextField = UITextField(frame: CGRect(x: 0, y: 100, width: 300, height: 40))
tf.backgroundColor = UIColor.redColor()
tf.text = "willMoveToSuperview // not ok"
addSubview(tf)
}
}
override func didMoveToSuperview()
{
if (!hasMovedToSuperView) {
var tf: UITextField = UITextField(frame: CGRect(x: 0, y: 150, width: 300, height: 40))
tf.backgroundColor = UIColor.greenColor()
tf.text = "didMoveToSuperview // ok"
addSubview(tf)
hasMovedToSuperView = true
addGestureRecognizer(UITapGestureRecognizer(target:self, action:Selector("createTFDynamically:")))
}
}
func createTFDynamically(value:AnyObject)
{
var tf: UITextField = UITextField(frame: CGRect(x: 0, y: 200, width: 300, height: 40))
tf.backgroundColor = UIColor.greenColor()
tf.text = "createTFDynamically // ok"
addSubview(tf)
}
}

Resources