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?
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).
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.