UISegmentedControl Set Selected Index Without Animation - ios

I have a UITabBarController with 3 views. Each view has a UISegmentedControl in its navigation bar, and all of these segmented controls need to remain in sync. Each time the selectedSegmentIndex on one of them changes, I fire an event that causes the others to change their selectedSegmentIndex as well. I also set the index in viewDidLoad to ensure it is correct the first time the view is displayed.
This all works as expected, except that when I switch to a different view I can see the segmented control animate its button to the correct position, even though the selectedSegmentIndex may have been set much earlier.
Can anyone tell me what is going on here? Is there a way to disable this animation (when setting the index programmatically)?

Here is the working solution from the comments:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
UIView.performWithoutAnimation {
segmentedControl.selectedSegmentIndex = 0
segmentedControl.layoutIfNeeded()
}
}

I know this answer is very late but in case anyone else needs same solution;
Layer of subviews of SegmentedControl has their own animation. Which are;
SelectionScale
SelectionPosition
SelectionBounds
here is a sample code from my project, I wrote whole new class for my UISegmentedControl
let foregroundIndex = numberOfSegments
if subviews.indices.contains(foregroundIndex),
let foregroundImageView = subviews[foregroundIndex] as? UIImageView {
foregroundImageView.layer.removeAllAnimations()
}
this is going to remove all animations over the selected UISegment and there will be no transition, it'll just snap.

Related

Set NavigationBar title on next ViewController without visual bugs

So I have a UICollectionView called WeatherVC that has a NavigationController.
WeatherVC segues to WeatherDetailVC which is UITableView upon UICollectionViewCell selection.
I want to set WeatherDetailVC NavigationBar title based on which UICollectionViewCell was selected.
This code below makes my whole NavigationBar just buggy.
Example: https://imgur.com/a/vOIbxK5
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? WeatherDetailVC {
let cities = ["City1", "City2", "City3", "City4"]
// cellPressed = 3 - for example this would work without bugs
vc.navigationItem.title = cities[cellPressed]
}
}
Variable cellPressed was set on cell highlight or tap. It behaves like this only in iOS 13 and only if I give it number based on cell tapped - if it is static, title changes with no visual bugs. I already tried creating functions inside WeatherDetailVC to change its title and calling them in prepare for:segue but it did not help whatsoever.
EDIT: My main problem is those bugs or glitches that comes with this solution. I have no problem with changing next VC title.
Since navigationBar title represents the title of the view controller, You can set it directly:
vc.title = cities[cellPressed]
Assuming cities[cellPressed] is tested and returning correct value
I figured it out. It happens because of the diacritic. If I assign title with diacritic character navigation bar just bugs everytime. Anyone know how to fix it?
EDIT: You can see this happening in IMGUR link that I provided. Character "á" is in the title.

How to fix highlighted state UIButton delay in UIPageViewController page?

I have a screen in which I have a UIPageViewController in each page I have a UIButton. The problem is that there is a delay of the pressed/highlighted state of the button for about a half second when the user presses the button. both state images are set to the button using the storyboard.
This happens in the Simulator as well as on a real device.
Now from my Google searches, I came across a few posts that describe this issue, for example:
UIButton delayed state change
and:
UIbutton only looks clicked (highlighted) on longPress?
In all posts, the solution is to use the delaysContentTouches setting and set it to false.
The problem is: I didn't found how would I apply this in my case of a UIPageViewController. most of the posts talk this issue in a UIScrollView or a UITableView.
So, the question is: how would I do that in case of a UIPageViewController? I didn't see that UIPageViewController has this setting and didn't find any other way to apply it.
Found a solution to this issue, This piece of code will fix the button highlighted click delay but will prevent the pager scroll on the button itself.
public override func viewDidAppear(_ animated: Bool) {
for view in self.view.subviews {
if view is UIScrollView {
(view as? UIScrollView)!.delaysContentTouches = false
}
}
}
The reason I didn't find this in UIPageViewController is that UIPageViewController is not a subclass of UIScrollView as I expected but it contains it as a subview.

Disable swipe back gesture in Swift

Been looking around on here for a while but can't seem to find a working solution.
I'm trying to disable the swipe to go back to previous view gesture, in Swift.
I've tried a variety of solutions including:
self.navigationController?.interactivePopGestureRecognizer.enabled = false
and
self.navigationController.interactivePopGestureRecognizer.delegate = self
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer!) -> Bool {
return false
}
Is there a new method of doing this or some other method that works?
The following is an easy approach to disabling & re-enabling the swipe back.
Swift 3.x & up
In a viewDidLoad/willAppear/didAppear method add:
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
Just keep in mind that if you do it with viewDidLoad, then the next time you open the view, it may not be set depending upon whether or not it remains in your stack.
Unless you want it to remain off, you will need to turn it back on when the view is closed via either willMove(toParentViewController:) or willDisappear. Your navigationController will be nil at viewDidDisappear, so that is too late.
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
A special note on SplitViewControllers:
As pointed out by CompC in the comments, you will need to call the second navigation controller to apply it to a detail view as such:
navigationController?.navigationController?.interactivePopGe‌​stureRecognizer?.isE‌​nabled = false
Swift 2.2 & Objective-C
Swift versions 2.x & below:
navigationController?.interactivePopGestureRecognizer?.enabled
Objective-C:
self.navigationController.interactivePopGestureRecognizer.enabled
You could disable it but that would not be to recommended as most iOS users go back by swiping and less by pressing the back button.
If you want to disable it it would be more reasonable to use a modal segue instead of a push segue which is not that big of a transfer.
If you really want to get rid of the swipe to go back function I would just disable the back button and have a done button on the top right of the screen.
self.navigationController?.navigationItem.backBarButtonItem?.isEnabled = false;
I was able to do this by returning false in gestureRecognizerShouldBegin
class ViewController2: UIViewController, UIGestureRecognizerDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationController?.interactivePopGestureRecognizer.delegate = self
}
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
Add this line before pushing view controller to navigation controller
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
Nothing wrong with either answer from Hari or Stefan but this is more succinct. Just put it in viewDidLoad and you're done.
if navigationController!.respondsToSelector(Selector("interactivePopGestureRecognizer")) {
navigationController!.view.removeGestureRecognizer(navigationController!.interactivePopGestureRecognizer)
}
EDIT:
One small caveat is that if the Navigation Controller was opened by another view and the Navigation Controller is closed then you'll get an EXC_BAD_ACCESS error. To fix it you have to save the original UIGestureRecognizer and put it back when you exit the view.
Declare:
private var popGesture: UIGestureRecognizer?
Immediately before removing the gesture:
popGesture = navigationController!.interactivePopGestureRecognizer
Then when closing the view:
If popGesture != nil {
navigationController!.view.addGestureRecognizer(popGesture!)
}
RowanPD's logic for Swift 4
private var popGesture: UIGestureRecognizer?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if navigationController!.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
self.popGesture = navigationController!.interactivePopGestureRecognizer
self.navigationController!.view.removeGestureRecognizer(navigationController!.interactivePopGestureRecognizer!)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let gesture = self.popGesture {
self.navigationController!.view.addGestureRecognizer(gesture)
}
}
Instead of
self.navigationController.pushViewController(VC, animated: Bool)
call
self.navigationController.setViewContollers([VC], animated: Bool)
setViewControllers replaces the all the VCs on the stack, instead of adding a new controller on top. This means that the new set VC is the root VC, and the user cannot go back.
This is most effective when you only want to disable the swipe on a single VC, and keep the swipe-to-back for the other VC.
If you want users to be able to go back, just not through swiping, do not use this method as it will disable all backs (as there is no VC to go back to).
for objective -c
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:true];
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
This is something you missed if it doesn't work after you tried all.
Add navigationController?.interactivePopGestureRecognizer?.isEnabled = false
to your viewWillAppear(animated:) method.
if it doesn't work, remove navigation delegate from the view controller. Check again if your view controller is confirming UINavigationControllerDelegate, UIGestureRecognizerDelegate protocols. if so, just remove it.
I generally make sure that swipe back is enabled in as many places as possible, even adding a custom gesture recognizer to add it to modal screens. However for an authentication and download process in my app I start the process with a modal navigation controller and then push the view for each next step. However, once it's completed I want to prevent them from backing up into the authentication screens.
For this scenario I've been using:
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
navigationItem.hidesBackButton = true
in viewWillAppear() on the final screen. You can undo these in viewWillDisappear() if you're pushing another view and need them there.
Come here a little bit late. In my case self.navigationController?.navigationItem.backBarButtonItem?.isEnabled = false; not working. So I do this: you can present view controller instead of push view controller. This way the swipe back gesture will not apply to the view controller.
navigationController?.present(vc, animated: true)
You could use dismiss for your custom back button
self.dismiss(animated: true)
Note: You could set VC modal presentation style before present it to make sure it's full screen.
vc.modalPresentationStyle = .fullScreen
Hope this help.
If requirement is to show side menu on some of the screens then add AddScreenEdgePanGesture on this specific view instead of navigationController view
replace it
SideMenuManager.default.menuAddScreenEdgePanGesturesToPresent(toView: self.navigationController?.view)
with this
SideMenuManager.default.menuAddScreenEdgePanGesturesToPresent(toView: self.view)
Only complete removal of the gesture recognizer worked for me (from the presenting view controller).
if let navigationController = parent.navigationController,
let interactivePopGestureRecognizer = navigationController.interactivePopGestureRecognizer {
navigationController.view.removeGestureRecognizer(interactivePopGestureRecognizer)
}
Don't Use this if you don't want to come back, or you set the new rootViewController.
self.navigationController.pushViewController(VC, animated: Bool)
Use this
self.navigationController.setViewContollers([VC], animated: Bool)
setViewControllers Remove all the View Controllers on the stack then the user cannot go back. it will disable all backs
this worked for me
gesture(DragGesture(minimumDistance: 10, coordinateSpace: .global))
so minimum distance is the distance to which drag gesture start listening, setting to 0 removes any listening, but it will remove all interactions be aware, i have changed 0 to 10 to listen to tap gestures, but in your screen if you have any other interaction it will not work after adding this,
If you don't care about system back button appearance (for example, if you're using custom back button or navigation bar is hidden at all), it might help you:
navigationItem.hidesBackButton = true
It hides back button and disables swipe back gesture.

Swift: The best way to show and hide different views based off of my UISegmentedControl?

I want to show one view when the segmented control highlights the first option. When the user highlights the other option, I want the first view to disappear (or hide) and the other view to become visible. Then, if the user presses the first option again, the second view hides and the first becomes visible.
What is the best way to do this?
I do not want to switch ViewControllers, but simply Views who are both using the same ViewController.
A way to do that would be to give a tag to each view (as in your segment's indices) and call a method when the its value is changed that would hide all view's accept the one with the right tag number.
You could hook the UISegmentedControl from the view controller to the code using an #IBAction and use the following method:
#IBAction func switchView(sender: UISegmentedControl) {
// Change your view controller's view property
// to reference whatever custom views you have.
if(sender.selectedSegmentIndex == 0) {
self.view = viewOne
} else {
self.view = viewTwo
}
}
viewOne and viewTwo being UIViews

UIPageViewController - view was not at the correct position initially

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
}

Resources