I have a custom tab bar with a horizontal scroll view underneath that.
Tab bar contains 5 buttons each of them calls one of the five custom keyboards (they are placed on a scroll view). You can switch between these keyboards scrolling the scroll view or pressing the buttons.
Question: how do I change the states of the buttons (default <-> selected) when I switch between keyboards using scroll view?
Visual:
I downloaded your project and you have some issues there. You're implementing the UIScrollViewDelegate but for some reason, the method you have in your app is wrong for the scrollViewDidScroll(_:).
The method needs to be declared like in this way:
func scrollViewDidScroll(_ scrollView: UIScrollView) {}
You cannot change anything in that method and in your case you changed the name of the argument as the name of your UIScrollView, just a minor change but it implies that you will not be notified every time the UIScrollView did scroll.
So let's go back to your problem. To achieve what do you want you to need to calculate the current page where you are inside the UIScrollView or in your case the number of the button, we can calculate that using the following formula:
let pageWidth = scrollView.frame.size.width
let page = Int(round((scrollView.contentOffset.x ) / (pageWidth)))
Afterwards what you need is update your selected button when the scroll finish of scrolling. The problem with the scrollViewDidScroll(_:) method is that this method is called several times during the scrolling process, so we need a method that is called only when the scrolling is finished, and it's the scrollViewDidEndDecelerating(_:).
So you need to add the following code in your code:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = scrollView.frame.size.width
let page = Int(round((scrollView.contentOffset.x ) / (pageWidth)))
let previousIndex = selectedIndex
tabBarButtons[previousIndex].isSelected = false
selectedIndex = page
tabBarButtons[page].isSelected = true
}
I tested the behaviour in your app and works fine. If you have any trouble I have your project modified, but the only thing you need is to add the above method in your UIViewController and it should work fine
I hope this help you
UIScroll has a delegate method called scrollViewDidScroll. This gets called every time the user scrolls. The scrollView has a property called contentOffset. That property tells you exactly how much as been scrolled. Use this method and property to branch your logic.
Related
I have a UITablaView in my Objective C application. I have the custom cells with a label and a UIImageView. I want to disable a part of the rows, to disable didSelectRowAtIndexPath when users click on this row's part.
I want this:
Is it possible?
Here is the simple and the most elegant solution that I can think off.
I believe you must be having a CustomCell, which holds the IBOutlet to imageView on the left side :) You can make use of hitTest method to solve your problem :)
In your CustomCell lets assume it to be MyTestCell class write this,
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.myImageView.frame.contains(point) {
return nil
}
return super.hitTest(point, with: event)
}
Where myImageView is the IBOulet of imageView on the left hand side of your cell :)
All am doing is checking where did user tap, if the touch point is within the frame of cells imageView you return nil as the view. When you return nil touch event stops propagating to its parent views and finally will never reach cell hence didSelectRowAtIndexPath never called,
On the other hand you should handover touch to the next view and you do it simply by calling same method on its super iOS will eventually trace it back to cell and triggers didSelectRowAtIndexPath
Hope it helps :)
You can do a simple trick (without the need of writing code) to solve this issue, by adding a button that covers the part that you want to disable the selection of it. Obviously, the button should not have any text or background color (it should be clear color), also make sure to add suitable constraints for making sure that is covered the wanted part of the cell.
So when tapping on the button, nothing should happen and didselectRow should not get called because the actual touching event should be referred to the button, not to the row.
Hope this helped.
This is very simple trick,
Add a button on profile image part, means on red part as shown in picture (provided by you). And don't do on click on button click.
Happy Coding!!!!
Simply put the button over Green area and set tag for each button. Onclick you can perform your functionality using tag. Like this
inside
func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let obj: AnyObject = self.dataList.objectAtIndex(indexPath.row);
cell?.populateCellWithData(obj, indexPath: indexPath)
cell?.destinationLabel.userInteractionEnabled = true
let destRecognizer : UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "DestLabelTapped:")
destRecognizer.numberOfTapsRequired = 1
destRecognizer.delegate = self
cell?.destinationLabel.addGestureRecognizer(destRecognizer)
}
and in DestLabelTapped you can peform your functionlity
Another way
Just set two Tableview and scroll them side by side and one table is selectable and another is not. (don't do this)
In my understanding the tricks with buttons and other UIElements that are covering the content are not the right way to solve the target. As you will need extra manipulations with them in Storyboard, if you will need to make dynamically content, if you will work with constraints and many more situations where you will need to control your content and + the artificial cover. There are few things to do:
Set UITableView selection to No Selection
Put the second part that you want to be active in UIView. This UIView will be the content container.
Add UITapGestureRecognizer to UIView
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(selectCellAction:)];
Add IBAction where you could do all you need.
- (IBAction)selectCellAction:(id)sender
{
// do what you need
}
And thats all.
You may simply do change the image view width constraints to 0. Where you want to action is performed. As for example:
1) If you use a button that cover the image.
2) If you take a navigation bar then you there take a button and perform the action.
hope this will help.
I've got this block of code in my ViewWillAppear method which simply moves a set of labels down off the screen:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//Hide 1,3,5 RM labels
oneRepMax.center.y += view.bounds.height
threeRepMax.center.y += view.bounds.height
fiveRepMax.center.y += view.bounds.height
}
All 3 labels have been properly linked to my storyboard file. When I run the project, nothing happens.
If I copy the exact same code into a method linked to a button, it works as expected.
There isn't anything else in the project so i'm puzzled as to why this doesn't work.
I'm using Xcode 7 and following a tutorial where this is working in the ViewWillAppearMethod.
as follows:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
heading.center.x -= view.bounds.width
username.center.x -= view.bounds.width
password.center.x -= view.bounds.width
loginButton.center.y += 30
loginButton.alpha = 0
}
You never know the bounds untill the all the labels appear on the screen.
Try to do the same in viewDidAppear method.
From Documentation:
This method is called after the completion of any drawing and animations involved in the initial appearance of the view. You can override this method to perform tasks appropriate for that time, such as work that should not interfere with the presentation animation, or starting an animation that you want to begin after the view appears.
This chunk of code is from Ray Wenderlich book iOS Animation by Tutorials, right?
I had the same problem. I found the cause of this problem - the view from the tutorial don't use the Auto Layout and Size Classes, that's why a label don't respond on setting its coordinates. Try to change the color of the label in viewWillAppear method like labelText.textColor = UIColor.greenColor() and everything goes ok, but if you try to set coordinates like labelText.center.x -= view.bounds.width nothing happens. So click your view and go to File Inspector, scroll a bit and you'll see the checkmarks Use Auto Layout and Use Size Classes uncheck it and you'll get what you need. But you'll get pain in the ass either because this is a different and long story to code without Auto Layout. good luck then.
There are several methods called when you load a View controller. Their order is something like this (on UIViewController):
loadView
viewDidLoad
viewWillAppear:
viewWillLayoutSubviews
layoutSubviews (on UIViews inside the view controller)
viewDidLayoutSubviews (here the view controller knows everything is set)
viewDidAppear:
From viewDidLayoutSubviews you're completely sure your changes will take effect, because elements are set. Before it, you can't be sure (unless you cached the view controller and it is appearing for a second time).
Try to change your code to viewDidAppear.
You can find more information in this old post.
In the app I am working on I have two view controllers inside a scroll view. The second view controller (VC2) contains a text view. You can see the setup on the image below:
!
When I scroll from VC2 to VC1, the keyboard persists and covers the content of VC1. I managed to solve the problem by making the scroll view the first responder on scrollViewDidScroll event. This works, but it results in the keyboard disappearing even on a partial scroll, which can be annoying to the users. I can solve this problem by also checking the content offset but it strikes me as overcomplicated and not elegant at all. Is there a better way to do this?
Edit:
As Chonch and latenitecoder suggested, I detected the page change. I adapted the code from: Detecting UIScrollView page change to swift. Here it is:
var previousPage = 0
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageWidth = scrollView.frame.size.width
let fractionalPage = scrollView.contentOffset.x / pageWidth
let page = Int(round(fractionalPage))
if (previousPage != page) {
// Page has changed, do your thing!
self.becomeFirstResponder()
// Finally, update previous page
previousPage = page
}
}
You can set the UIScrollView's pagingEnabled property to YES and only call resignFirstResponder for the UITextView when the paging ended and the resulting page is VC1. As long as the current page remains VC2, you don't call resignFirstResponder, and the keyboard remains shown.
However, notice that it may actually be a good idea to hide the keyboard as soon as the user starts scrolling (as you're describing is your current state). Maybe you should leave it like this and when the paging ends, check if the current page is VC2, and if it is, call becomeFirstResponder on the UITextView in order to display the keyboard again.
I'd suggested to use UIPageViewController to horizontal scrolling controllers. Then you can do the same action in it's delegate method
- (void)pageViewController: didFinishAnimating: previousViewControllers: transitionCompleted: but it will save you memory a lot.
I am using UIPageViewController to create the introduction pages of my app.
As the gif(I swiped to the 2nd view and swiped back to the 1st view):
My second page's view was not at the correct position initially. It's upper than it should be and dropped down later. However, if I swiped back, the first page's view was correct.
I guess it's because the UIPageViewController didn't load my 2nd view early enough, so the auto layout system was still calculating the position when the 2nd view already appeared. When I swiped back to the 1st view, since the view was already loaded, there was no such issue.(it's just my guess, I am not sure.)
I found I can use 2ndViewController.loadView() as a workaround, but Apple's document discourages programmers to call this method directly. I also found calling this method directly is buggy.
How do I prevent this correctly?
If you are setting constraints, fix the view's top space from Superview.
In func create view at index, let try my code
func pageTutorialAtIndex(index: Int) ->TestNodeController
{
let pageContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("TestNodeController") as! TestNodeController
pageContentViewController.automaticallyAdjustsScrollViewInsets = false
pageContentViewController.pageIndex = index
return pageContentViewController
}
I have a Custom Scroll View, subclassing UIScrollView. I have added a scroll view in my viewcontroller nib file and changed its class to CustomScrollView. Now, this custom scroll view (made from xib) is added as a subview on self.view.
In this scroll view, I have 3 text fields and 1 UIImageView(named signImageView) added from xib. On clicking UIImageView (added a TapGestureRecogniser), a UIView named signView is added on the custom scroll view. I want to allow User to sign on this view, So I have created a class Signature.m and .h, subclassing UIView and implemented the touches methods (touchesBegan, touchesMoved and touchesEnded) and initialised the signView as follows:
signView = [[Signature alloc]initWithFrame:signImageView.frame];
[customScrollView addSubview:signView];
But when I start signing on the signView, the view gets scrolled and hence the touches methods don't get called.
I have tried adding signView on self.view instead of custom scroll view, but in that case the view remains glued to a fixed position when I start scrolling. (Its frame remains fixed in this case)
Try setting canCancelContentTouches of the scrollView to NO and delaysContentTouches to YES.
EDIT:
I see that similiar question was answered here Drag & sweep with Cocoa on iPhone (the answer is exactly the same).
If the user tap-n-holds the signView (for about 0.3-0.5 seconds) then view's touchesBegan: method gets fired and all events from that moment on go to the signView until touchesEnded: is called.
If user quickly swipes trough the signView then UIScrollView takes over.
Since you already have UIView subclassed with touchesBegan: method implemented maybe you could somehow indicate to user that your app is prepared for him to sign ('green light' equivalent).
You could also use touchesEnded: to turn off this green light.
It might be better if you add signImageView as as subView of signView (instead of to customScrollView) and hide it when touchesBegan: is fired). You would add signView to customScrollview at the same place where you add signImageView in existing code instead.
With this you achieve that there is effectively only one subView on that place (for better touch-passing efficiency. And you could achieve that green light effect by un-hiding signImageView in touchesBegan:/touchesEnded:
If this app-behaviour (0.3-0.5s delay) is unacceptable then you'd also need to subclass UIScrollView. There Vignesh's method of overriding UIScrollView's touchesShouldBegin: could come to the rescue. There you could possibly detect if the touch accoured in signView and pass it to that view immediately.
When ever you add a scrollview in your view hierarchy it swallows all touches.Hence you are not getting the touches began. So to get the touches in your signon view you will have to pass the touches to signon view. This is how you do it.
We achieved this with a UIScrollView subclass that disables the pan gesture recogniser for a list of views that you provide.
class PanGestureSelectiveScrollView: UIScrollView {
var disablePanOnViews: [UIView]?
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let disablePanOnViews = disablePanOnViews else {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
let touchPoint = gestureRecognizer.location(in: self)
let isTouchingAnyDisablingView = disablePanOnViews.first { $0.frame.contains(touchPoint) } != nil
if gestureRecognizer === panGestureRecognizer && isTouchingAnyDisablingView {
return false
}
return true
}
}