Search code examples
iosswiftuipangesturerecognizer

Custom view stops recognize gestures as soon as it moved into another custom view


I have the following two classes:

LongUpperView.swift

class LongUpperView: UIView, UIGestureRecognizerDelegate {

let TAG: String = "BannerView"

weak var mPredictionViewDelegate: PredictionViewDelegate!
weak var mUrlManagerDelegate: URLManagerdelegate!
weak var mLongUpperViewDelegate: LongUpperViewDelegate!
var panRecognizer: UIPanGestureRecognizer?

var mLogoView: LogoView!
var mPredictionView: PredictionView!
var mRunningNewsView: RunningNewsView!

var mCurrentCircleViewIndex: Int! = 0
var resUpdated: Array<String>!
var mWidth: CGFloat!
var mFeedsArray: Array<News>!

var mModelManager: ModelManager!
var mURLManager: UrlManager!
var mGetNewsTimer: NSTimer?
var mMovementBeginPoint: CGPoint!

let velocityFactor = 5
var mPreVelocity: CGPoint!

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

init(aFrame: CGRect, aWidth: CGFloat, activeIndex: Int, aIsAccessGranted: Bool, context: NSExtensionContext?, aPredictionViewDelegate: PredictionViewDelegate, aUrlManagerDelegate: URLManagerdelegate, aAllowFullAccessDelegate: AllowFullAccessViewDelegate, aLongUpperViewDelegate: LongUpperViewDelegate) {
    super.init(frame: aFrame)

    self.mWidth = aWidth
    self.mPredictionViewDelegate = aPredictionViewDelegate
    self.mUrlManagerDelegate = aUrlManagerDelegate
    self.mLongUpperViewDelegate = aLongUpperViewDelegate

    self.backgroundColor = UIColor.clearColor()
    self.mFeedsArray = Array<News>()

    if aIsAccessGranted == false {
        let AllowFullAccessButtonsView = AllowFullAccessView(frame: CGRectMake(0, 0, aFrame.size.width, 52), aWidth: self.mWidth, context: context, delegate: aAllowFullAccessDelegate)
        self.addSubview(AllowFullAccessButtonsView)
    } else {
        self.createLogoView(self.mWidth)
        self.createCompletionPredictionView(self.mWidth, aWidth: self.mWidth)
        self.createRunningFeedsView(self.mWidth * 2, aWidth: self.mWidth)
    }
}

func changeFrame(aParentWidth: CGFloat) {
    self.frame = CGRectMake(0, 0, aParentWidth, self.frame.size.height)
    //self.mLogoView.frame = CGRectMake(0, 0, aParentWidth, self.mLogoView.frame.size.height)
    //self.mPr

    self.cleanLongUpperViewViewsOnly()
    self.createLogoView(aParentWidth)
    self.createCompletionPredictionView(aParentWidth, aWidth: aParentWidth)
    self.createRunningFeedsView(aParentWidth * 2, aWidth: aParentWidth)
}

func setupGestures() {
    self.panRecognizer = UIPanGestureRecognizer(target: self, action: "movePanel:")
    self.panRecognizer!.minimumNumberOfTouches = 1
    self.panRecognizer!.maximumNumberOfTouches = 1
    self.addGestureRecognizer(self.panRecognizer!)
}

func createLogoView(aWidth: CGFloat) {
    let logoImage = UIImage(named: "Logo.png")
    self.mLogoView = LogoView(aFrame: CGRectMake(0, 0, aWidth, logoImage!.size.height), aWidth: aWidth, aFeedsArray: mFeedsArray)
    self.addSubview(self.mLogoView)
    self.mCurrentCircleViewIndex = 0
}

func createCompletionPredictionView(aOffset: CGFloat, aWidth: CGFloat) {
    self.mPredictionView = PredictionView(frame: CGRectMake(aOffset + 0, 8.5, aWidth, 30))
    self.mPredictionView.createButtons(Theme.sharedInstance.getKeyTheme(KiboConstants.ThemeKeys.THEME_KEY_MODIFIER), aCurrentScreenWidth: aWidth)
    self.mPredictionView.delegate = self.mPredictionViewDelegate
    self.addSubview(self.mPredictionView)
    self.mCurrentCircleViewIndex = 1
}

func createRunningFeedsView(aOffset: CGFloat, aWidth: CGFloat) {
    self.mRunningNewsView = RunningNewsView(aFrame: CGRectMake(aOffset, 0, aWidth, self.frame.height), aWidth: aWidth, aRunningFeedsArray: SystemUtils.KeyboardTasks.initMRunNewsArray(self.mFeedsArray))
    self.addSubview(self.mRunningNewsView)
    self.mCurrentCircleViewIndex = 2
}

func updateNewsDone(newsIdsArr: [News]) {
    let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true)
    let documentsDirectory: AnyObject = paths[0] // Get documents folder
    let newsImagesPath = documentsDirectory.stringByAppendingPathComponent(KiboConstants.CommonStrings.JSON_KEY_DATA_NEWS_IMAGES_CACHE)
    self.updateFeeds(newsIdsArr)
}

func updateFeeds(mnewManagedObjectsArrary: Array<News>) {
    var managedObjectItemsCount = mnewManagedObjectsArrary.count

    for var i = managedObjectItemsCount - 1; i >= 0; i-- {

        mFeedsArray.insert(mnewManagedObjectsArrary[i], atIndex: 0)
    }
    self.mLogoView.updateNewsIndicator(self.mFeedsArray)
    self.mRunningNewsView.updateRunningNewsArray(SystemUtils.KeyboardTasks.initMRunNewsArray(self.mFeedsArray))
}

func updateSourcesDone() {
    if let val = mURLManager {

    } else {
        mURLManager = UrlManager()
        mURLManager.delegate = self.mUrlManagerDelegate
    }
    mURLManager.doGetNewsRequest()
}

//EMIL: method to handle the upper long view swipe gestures.
func movePanel(sender: UIPanGestureRecognizer) {
    sender.view!.layer.removeAllAnimations()

    let translatedPoint = sender.translationInView(self)
    let velocity = sender.velocityInView(sender.view)
    let upperViewxPos = self.frame.origin.x
    let compVelocity = Float(velocity.x) / Float(self.velocityFactor)
    let totalXPos = Float(compVelocity) + Float(upperViewxPos)

    if sender.state == UIGestureRecognizerState.Began {
        self.mMovementBeginPoint = translatedPoint
    }

    if sender.state == UIGestureRecognizerState.Ended {
        let upperViewxPos = self.frame.origin.x
        let animationOffsetFloat: Float = Float(totalXPos) / (-1.0 * Float(self.mWidth))

        var animationOffset: Int = Int(round(animationOffsetFloat)) * (-1 * Int(self.mWidth)) // (round(320/upperViewxPos)) * 320

        if (animationOffset > 0) {
            animationOffset = 0
        } else if (CGFloat(animationOffset) < (-1 * self.frame.size.width) + CGFloat(self.mWidth)) {
            animationOffset = -1 * Int(self.frame.size.width) + Int(self.mWidth)
        }

        self.mCurrentCircleViewIndex = Int(animationOffset / (-1 * Int(self.mWidth)))

        let animationOffsetCGFLoat: CGFloat = CGFloat(animationOffset)
        playMoveBackAnimation(CGFloat(animationOffsetCGFLoat), aduration_time: KiboConstants.UserDefaultsValues.DEFAULT_SWIPE_DURATION_VAL)
    }

    if sender.state == UIGestureRecognizerState.Changed {
        sender.view!.center = CGPointMake(sender.view!.center.x + translatedPoint.x, sender.view!.center.y);
        sender.setTranslation(CGPointMake(0, 0), inView: self)
        self.mPreVelocity = velocity;
    }
}

func playMoveBackAnimation(aOffset: CGFloat, aduration_time: Double) {
    UIView.animateWithDuration(aduration_time, delay: 0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: {
        () -> Void in
        self.frame = CGRectMake(aOffset, 0, self.frame.size.width, self.frame.size.height)

        /*/TODO: EMIL: This part updates the space button with the Chelsea logo image when logo is swipped.
        if ((self.mCurrentCircleViewIndex == 0) && (self.spaceButtonPointer.imageView.frame.origin.x < self.spaceOriginXVal)) {
            self.spaceButtonPointer.imageView.frame = CGRectMake(self.spaceOriginXVal, 0, self.spaceButtonPointer.imageView.frame.width, self.spaceButtonPointer.imageView.frame.height)
        } else if ((self.mCurrentCircleViewIndex > 0) && (self.spaceButtonPointer.imageView.frame.origin.x == self.spaceOriginXVal)) {
            self.spaceButtonPointer.imageView.frame = CGRectMake(-1 * self.spaceButtonPointer.imageView.frame.width / 2, 0, self.spaceButtonPointer.imageView.frame.width, self.spaceButtonPointer.imageView.frame.height)
        } */

    }, completion: {
        (Bool) -> Void in
        self.mLongUpperViewDelegate.updateActiveCircleIndex(self.mCurrentCircleViewIndex)
        if aOffset == self.mWidth * -2 {
            self.mRunningNewsView.startRunNewsTimer()
        } else {
            self.mRunningNewsView.stopRunNewsTimer()
        }
    })
}
}

This class is part of the following class:

BannerView.swift:

class BannerView: UIView, LongUpperViewDelegate {

weak var mController: KeyboardViewController?
var mWidth: CGFloat!
var mCircleView: CirclesView!
var mLongUpperView: LongUpperView!

init(aFrame: CGRect, aWidth: CGFloat, aIsAccessGranted: Bool, context: NSExtensionContext?, aPredictionViewDelegate: PredictionViewDelegate, aUrlManagerDelegate: URLManagerdelegate, aAllowFullAccessDelegate: AllowFullAccessViewDelegate, aGestureRecognizerDelegate: UIGestureRecognizerDelegate, aController: KeyboardViewController) {

    self.mController = aController

    super.init(frame: aFrame)

    self.mWidth = aWidth

    self.mCircleView = CirclesView(aYPos: 50, aCirclesNum: 3, aActiveCircleIndex: 0, aMywidth: self.mWidth)
    self.addSubview(self.mCircleView)

    self.mLongUpperView = LongUpperView(aFrame: CGRectMake(0, 0, self.mWidth * 3, 52), aWidth: self.mWidth, activeIndex: 0, aIsAccessGranted: aIsAccessGranted, context: context, aPredictionViewDelegate: aPredictionViewDelegate, aUrlManagerDelegate: aUrlManagerDelegate, aAllowFullAccessDelegate: aAllowFullAccessDelegate, aLongUpperViewDelegate: self)
    self.addSubview(self.mLongUpperView)
}

func changeFrame(aParentWidth: CGFloat) {
    self.frame = CGRectMake(0, 0, aParentWidth * 3, 52)

    let currentActiveCircleIndex = self.mCircleView.mActiveCircleIndex
    self.mCircleView.changeFrame(aParentWidth)
    self.mCircleView.updateActiveCircleIndex(currentActiveCircleIndex)

    self.mLongUpperView.changeFrame(aParentWidth)
}

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

func updateActiveCircleIndex(aIndex: Int) {
    self.mCircleView.updateActiveCircleIndex(aIndex)
}
}

The problem: As long as I use BannerView class directly in my UIInputViewController the the gestures are recognized and there is not problem.

As soon as I move this class to another custom view and use this custom view in my controller the gestures and the click stop responding.

This is the third class that is using BannerView:

class KeyboardView: UIView, KeyPressedDelegate, UIGestureRecognizerDelegate {

weak var mDelegate: KeyboardViewController!

var currentKeyboard: Keyboard!
var currentLayout: LayoutKeyboard!
//var panRecognizer: UIPanGestureRecognizer?

    .....

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

init(aFrame: CGRect, aWidth: CGFloat, aDelegate: KeyboardViewController, aPlatformSuffix: String) {
    self.mDelegate = aDelegate
    self.mLanguageCountryCode = aDelegate.mLanguageCountryCode
    self.mLayoutType = aDelegate.mLayoutType
    self.mPlatformSuffix = aPlatformSuffix
    self.mWidth = aWidth

    self.mJsonParser = JsonParser()
    self.mAbcKeyboard = Keyboard()
    self.m123Keyboard = Keyboard()
    self.mSignKeyboard = Keyboard()

    self.mBannerView = BannerView(aFrame: CGRectMake(0, 0, self.mWidth * 3, 52), aWidth: self.mWidth, aIsAccessGranted: mDelegate.mAccessIsGrantedFlag, context: self.mDelegate.extensionContext, aPredictionViewDelegate: self.mDelegate, aUrlManagerDelegate: self.mDelegate, aAllowFullAccessDelegate: self.mDelegate, aGestureRecognizerDelegate: self.mDelegate, aController: self.mDelegate)
    self.mShortView = ShortView(frame: CGRectMake(0, 0, 30, 45))
    super.init(frame: aFrame)
    self.setupDataAndView()
}

func setupDataAndView() {
    self.currentKeyboard = self.mAbcKeyboard
    var pathName: String!

    if self.mIsNumericKeyboardFlag == true {
        pathName = "number_logic"
    } else {
        pathName = self.mLanguageCountryCode + "_logic_abc" + "_" + self.mLayoutType
    }

    self.initKeyboardModel(self.mJsonParser.parseFile(pathName, aFileType: KiboConstants.FileType.FILE_TYPE_JSON))
    self.currentLayout = self.currentKeyboard.mKeyboardLayoutPortrait
    self.initViews()
}

func initViews() {

    if self.mDelegate.mNoBannerFlag == false {
        self.addSubview(self.mBannerView)
    }

    self.mKeysView = KeysView(aFrame: CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), aKeyboardView: self)
    self.addSubview(self.mKeysView)

    self.mShortView.hidden = true
    self.addSubview(self.mShortView)
    //self.setupGestures()
}
....

So the question is: How can I move a custom view into another custom view and still obtain the swipe event only on the custom view that has the gesture recognizer?


Solution

  • Eventually the problem was the fact that the second view that was added to the custom view (KeysView), was taking all the space of the parent custom view (KeyboardView) and because of that fact the second custom view (BannerView) was covered by the first one(KeysView) and it was taking all the click events to himself without passing them on to the next view.

    What I did to fix this issue was: 1. Change the initialisation order of the two custom views in the KeyboardView (parent view). So basically first I instatiate the bigger view (KeysView) only then I instatiate the smaller view (BannerView).

    1. In addition I have added the following method to the BannerView:

      override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
          let contains: Bool = CGRectContainsPoint(self.frame, point)
          if contains {
              return true
          } else {
              return false
          }
      }
      

    So the BannerView (the most top view) handles only events for himself (check of the event point is contained in the view) and passes the events not for himself down the view hierarchy.