Search code examples
iosswiftimessageimessage-extension

iOS UIImageView tap only registering on top part of view


I am working on a project in which a Tic Tac Toe grid is being drawn on the screen.

The issue is that, for the bottom row of "tiles", the UITapGestureRecognizer is not working properly. For the top two rows of the grid, when any part of the UIImageView that the recognizer is assigned to is tapped, the proper function gets called. However, for the bottom row, it only works when the very top of the UIImageView is tapped.

Here is the code:

func drawTiles() {
    for view in subviews {
        if (view as? UIImageView != nil) {
            view.removeFromSuperview()
        }
    }

    // Upper Left Tile [0][0]
    var view = UIImageView(image: battleGround.tiles[0][0].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(upperLeftTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX, y: frame.minY, width: frame.width / 3, height: frame.height / 3)
    addSubview(view)

    // Upper Middle Tile [0][1]
    view = UIImageView(image: battleGround.tiles[0][1].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(upperCenterTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX + frame.width / 3, y: frame.minY, width: frame.width / 3, height: frame.height / 3)
    addSubview(view)

    // Upper Right Tile [0][2]
    view = UIImageView(image: battleGround.tiles[0][2].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(upperRightTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX + ((frame.width / 3) * 2), y: frame.minY, width: frame.width / 3, height: frame.height / 3)
    addSubview(view)



    // Middle Left Tile [1][0]
    view = UIImageView(image: battleGround.tiles[1][0].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(middleLeftTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX, y: frame.minY + ((frame.height / 3)), width: frame.width / 3, height: frame.height / 3)
    addSubview(view)

    // Middle Center Tile [1][1]
    view = UIImageView(image: battleGround.tiles[1][1].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(middleCenterTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX + ((frame.width / 3)), y: frame.minY + ((frame.height / 3)), width: frame.width / 3, height: frame.height / 3)
    addSubview(view)

    // Middle Center Tile [1][2]
    view = UIImageView(image: battleGround.tiles[1][2].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(middleRightTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX + ((frame.width / 3) * 2), y: frame.minY + ((frame.height / 3)), width: frame.width / 3, height: frame.height / 3)
    addSubview(view)

    // FIXME: Only clicking the top of the next 3 views works

    // Lower Left Tile [2][0]
    view = UIImageView(image: battleGround.tiles[2][0].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerLeftTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX, y: frame.minY + ((frame.height / 3) * 2), width: frame.width / 3, height: frame.height / 3)
    addSubview(view)

    // Lower Center Tile [2][1]
    view = UIImageView(image: battleGround.tiles[2][1].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerCenterTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX + ((frame.width / 3)), y: frame.minY + ((frame.height / 3) * 2), width: frame.width / 3, height: frame.height / 3)
    addSubview(view)

    // Lower Right Tile [2][2]
    view = UIImageView(image: battleGround.tiles[2][2].image)
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerRightTapped)))
    view.isUserInteractionEnabled = true
    view.frame = CGRect(x: frame.minX + ((frame.width / 3) * 2), y: frame.minY + ((frame.height / 3) * 2), width: frame.width / 3, height:frame.height / 3)
    addSubview(view)
}

func upperLeftTapped() {
    tapped(row: 0, column: 0)
}

func upperCenterTapped(){
    tapped(row: 0, column: 1)
}

func upperRightTapped() {
    tapped(row: 0, column: 2)
}

func middleLeftTapped() {
    tapped(row: 1, column: 0)
}

func middleCenterTapped() {
    tapped(row: 1, column: 1)
}
func middleRightTapped() {
    tapped(row: 1, column: 2)
}

func lowerLeftTapped() {
    tapped(row: 2, column: 0)
}

func lowerCenterTapped() {
    tapped(row: 2, column: 1)
}

func lowerRightTapped() {
    tapped(row: 2, column: 2)
}

func tapped(row: Int, column: Int) {
    if (battleGround.tiles[row][column] == .empty) {
        battleGround.tiles[row][column] = currentPlayer

        drawTiles()
    }
}

Here is the code to pay attention to (what draws the lower row of the grid)

// Lower Left Tile [2][0]
view = UIImageView(image: battleGround.tiles[2][0].image)
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerLeftTapped)))
view.isUserInteractionEnabled = true
view.frame = CGRect(x: frame.minX, y: frame.minY + ((frame.height / 3) * 2), width: frame.width / 3, height: frame.height / 3)
addSubview(view)

// Lower Center Tile [2][1]
view = UIImageView(image: battleGround.tiles[2][1].image)
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerCenterTapped)))
view.isUserInteractionEnabled = true
view.frame = CGRect(x: frame.minX + ((frame.width / 3)), y: frame.minY + ((frame.height / 3) * 2), width: frame.width / 3, height: frame.height / 3)
addSubview(view)

// Lower Right Tile [2][2]
view = UIImageView(image: battleGround.tiles[2][2].image)
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerRightTapped)))
view.isUserInteractionEnabled = true
view.frame = CGRect(x: frame.minX + ((frame.width / 3) * 2), y: frame.minY + ((frame.height / 3) * 2), width: frame.width / 3, height:frame.height / 3)
addSubview(view)

EDIT: Here are some screenshots of the app running to give a better idea of what the issue is.

image1

This screenshot shows what an empty grid looks like.

image2

This screenshot shows the grid after I clicked on the top middle and middle left tiles of the grid. As you can see, the image is properly added.

However, if I tap on the bottom row of the grid, nothing happens. Look at this GIF:

gif1

I tapped on the bottom middle multiple times, and nothing happened. Yet tapping in the center worked properly.

Here is the interesting part: tapping on the top of that same bottom middle tile causes the image to appear. Take a look:

enter image description here

Any help would be greatly appreciated!


Solution

  • @Nailer's clipToBounds method helped me find the solution, but did not reveal it outright. After a couple of weeks of deliberation, it turns out that replacing most of the frame. with bounds. fixes the issue. Here is the new code.

    func drawTiles() {
        for view in subviews {
            if (view as? UIImageView != nil) {
                view.removeFromSuperview()
            }
        }
    
        // Upper Left Tile [0][0]
        var view = UIImageView(image: battleGround.tiles[0][0].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(upperLeftTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX, y: bounds.minY, width: bounds.width / 3, height: bounds.height / 3)
        addSubview(view)
    
        // Upper Middle Tile [0][1]
        view = UIImageView(image: battleGround.tiles[0][1].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(upperCenterTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX + bounds.width / 3, y: bounds.minY, width: bounds.width / 3, height: bounds.height / 3)
        addSubview(view)
    
        // Upper Right Tile [0][2]
        view = UIImageView(image: battleGround.tiles[0][2].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(upperRightTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX + ((bounds.width / 3) * 2), y: bounds.minY, width: bounds.width / 3, height: bounds.height / 3)
        addSubview(view)
    
    
    
        // Middle Left Tile [1][0]
        view = UIImageView(image: battleGround.tiles[1][0].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(middleLeftTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX, y: bounds.minY + ((bounds.height / 3)), width: bounds.width / 3, height: bounds.height / 3)
        addSubview(view)
    
        // Middle Center Tile [1][1]
        view = UIImageView(image: battleGround.tiles[1][1].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(middleCenterTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX + ((bounds.width / 3)), y: bounds.minY + ((bounds.height / 3)), width: bounds.width / 3, height: bounds.height / 3)
        addSubview(view)
    
        // Middle Center Tile [1][2]
        view = UIImageView(image: battleGround.tiles[1][2].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(middleRightTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX + ((bounds.width / 3) * 2), y: bounds.minY + ((bounds.height / 3)), width: bounds.width / 3, height: bounds.height / 3)
        addSubview(view)
    
        // FIXME: Only clicking the top of the next 3 views works
    
        // Lower Left Tile [2][0]
        view = UIImageView(image: battleGround.tiles[2][0].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerLeftTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX, y: bounds.minY + ((bounds.height / 3) * 2), width: bounds.width / 3, height: bounds.height / 3)
        addSubview(view)
    
        // Lower Center Tile [2][1]
        view = UIImageView(image: battleGround.tiles[2][1].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerCenterTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX + ((bounds.width / 3)), y: bounds.minY + ((bounds.height / 3) * 2), width: bounds.width / 3, height: bounds.height / 3)
        addSubview(view)
    
        // Lower Right Tile [2][2]
        view = UIImageView(image: battleGround.tiles[2][2].image)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(lowerRightTapped)))
        view.isUserInteractionEnabled = true
        view.frame = CGRect(x: bounds.minX + ((bounds.width / 3) * 2), y: bounds.minY + ((bounds.height / 3) * 2), width: bounds.width / 3, height:bounds.height / 3)
        addSubview(view)
    }