Disabling swipe/gesture on SideMenu library for xcode - ios

I was using SideMenu library from this github repo.
https://github.com/jonkykong/SideMenu
And after going high and low, I can't pinned down how to disable swipe on a navigation bar of viewcontroller.
I will use example included in library as example. At MainViewController.swift file, SideMenu is initiated and as far as I know, there are only 2 methods/functions related to gesture. And none of them are related to disable reacting to gesture. And I added this line at setUp method in the file.
SideMenuManager.default.menuPushStyle = .preserveAndHideBackButton
I have tried using childrenController in these methods but as soon as I did that, I can't use swipe/gesture on the directed view after touching on items of left sidemenu/drawer.
Is there anyway I could disable gesture on navigation bar using this library? And kindly let me know if I need to edit question, as this is my first question in stackoverflow.

As checked with demo example from above github project,
SideMenuManager.default.menuAddPanGestureToPresent(toView: self.navigationController!.navigationBar)
This line is in class 'MainViewController' in method 'setupSideMenu' which is responsible for adding pan gesture to navigation bar, which causing swipe gesture work for navigation bar. If you don't want this feature, you can simply comment the line and it will work.
Hope it helps...

Not sure about your scenario, but in my case when user logs out and reach on login screen i wanted to disable gesture from left to right.
At th etime user clicks on logout i set below property to 0.0 and that's it.
SideMenuManager.default.menuWidth = 0.0
Now again reset it to 85% after user re-login.

I just figured out how to do this myself for a UITabBarController. The approach will be similar for a UINavigationController. I created the following function to enable/disable the swipe gestures:
/// Keeps references to the gestures created by the SideMenu manager.
private var sideMenuGestures : [UIGestureRecognizer] = []
/// Enables or disables the swipe gestures used to show a SideMenu on the given side (default: left).
func enableSideMenuGestures(_ enable: Bool, forMenu: UIRectEdge = .left) {
if enable {
if sideMenuGestures.count == 0 {
sideMenuGestures.append(SideMenuManager.default.menuAddPanGestureToPresent(toView: self.tabBarController!.view))
sideMenuGestures.append(contentsOf: SideMenuManager.default.menuAddScreenEdgePanGesturesToPresent(toView: self.tabBarController!.view, forMenu: forMenu))
}
} else {
self.tabBarController?.view.gestureRecognizers?.removeAll(where: { sideMenuGestures.contains($0) })
sideMenuGestures.removeAll()
}
}
In your case, simply replace self.tabBarController with self.navigationController and it should work the same. (The self. prefix is not required but I like to include it as a convention to show it's an inherited field rather than one I've declared.)
This works because SideMenuManager handily returns references to all the gestures it creates; otherwise you'd have to clear all gestures from the navigation controller, which would be a bad idea as explained in this answer.
Usage is then simply enableSideMenuGestures(true) in the action handler for a button which displays the menu (for example) and enableSideMenuGestures(false) when you want to disable the gestures again.

Related

Implement swipe to go back in Swift

Is there an easy solution to enable the function where the user swipes to get back? Couldn't find an easy solution that works for me.
UIKit disables the default back gesture if you use any custom transitions. You can unset the navigationControllerDelegate to get back the back gesture. or implement your own back gesture through interactive transition.
Inside viewDidLoad() of the destination-ViewController following line does the trick:
navigationController?.interactivePopGestureRecognizer?.delegate = nil
Link

Disabling Dock in iOS programmatically

Is it possible to disable the dock that pops up in iOS?
This is my View Controller. Notice that it has a draggable view controller in the footer.
But when I try to pull it up quickly, the dock shows up:
Is there any way to disable it?
I think the closest you can get is iOS 11's preferredScreenEdgesDeferringSystemGestures(), which will show an indicator at the bottom but not pull up the dock on the first swipe. For example, in your view controller:
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return [.bottom]
}
In my experience it still eats the swipe gesture, but it still gives the user a second chance to hit the right target.
On iOS <11 however, this behavior can only be obtained by hiding the status bar.
Edit:
Usually when faced with implementing a design choice like this, I try to offer a second, non-interfering gesture as a backup, such as a tap in that area, that has the same effect.
As in iOS 11, you cannot disable the dock in an application, nor in Settings. I'd suggest providing a larger area for swiping up from the bottom.
Normally such conflicts should be avoided, as they degrade user experience: how do you know that the user does not actually want to use the dock?
But if you really want, you can override the preferredScreenEdgesDeferringSystemGestures() method in the root controller to specify which edges should NOT (immediately) trigger system gestures.
e.g.
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return .bottom
}

Embedding Share Icons Inline Within Another View

I am attempting to implement a "Share" feature in my iOS app with a similar look and feel to that of the Google Photos iOS app:
The bottom two rows of icons are what I care about. They look nearly identical to those displayed when using the UIActivityViewController.
In the Google Photos app, these icons appear inline with the "select photos" portion of the screen (e.g. you can still interact with the upper portion of the screen). However, the documentation for the UIActivityViewController states that "On iPhone and iPod touch, you must present [the view controller] modally."
This is the difference that is really important to me -- I'd like for the "share" icons to display inline with the rest of my content, rather than having a modal that is displayed on top of my content.
Is it possible to use the UIActivityViewController to achieve a similar effect shown in the screenshot above? If not, is there a recommended approach that I might use to implement this sort of functionality?
As discussed in another answer, reverse engineering UIActivityViewController is the only option in order to be able to achieve a similar effect. I tried this using iPhone 6s - 10.3 Simulator. The following findings may not be accurate for iOS 9.x or 11.x or above.
A. Find out all internal variables for UIActivityViewController
var variablesCount: UInt32 = 0
let variables = class_copyIvarList(UIActivityViewController.self, &variablesCount)
for i in 0..<variablesCount {
if let variable = variables?[Int(i)] {
let name = String(cString: ivar_getName(variable))
let typeEncoding = String(cString: ivar_getTypeEncoding(variable))
print("\(name)\n\(typeEncoding)\n\n")
}
}
free(variables)
The ones those got my attention at first sight are (in order) -
_activityViewController
#"UIViewController"
_contentController
#"_UIActivityViewControllerContentController"
_activityAlertController
#"UIAlertController"
On inspecting them further, I found out that _contentController is the one we should be looking for. We need to look one level deeper in hierarchy for UICollectionViewController to get to where we want to be.
if let activityContentController = activityVC.value(forKeyPath: "_contentController") as? UIViewController {
print("Found _contentController!")
for child in activityContentController.childViewControllers {
print(String(describing: child))
if child is UICollectionViewController {
print("Found UICollectionViewController!")
break
}
}
}
Why did I look for UICollectionViewController?
Debug View Hierarchy has the answer for this.
I tried adding this as a childViewController to my UIViewController -
self.addChildViewController(child)
child.didMove(toParentViewController: self)
self.view.addSubview(child.view)
child.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
child.view.topAnchor.constraint(equalTo: self.view.topAnchor),
child.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
child.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
child.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
IT SHOWS UP CORRECTLY ONLY IF YOU HAVE LOADED/PRESENTED UIActivityViewController FIRST.
I was able to achieve this using a silent present/dismiss call -
self.present(activityVC, animated: false, completion: {
self.dismiss(animated: false, completion: nil)
})
IS THIS APP STORE SAFE? - Most likely not.
As soon as you start stealing the view(s) or viewController(s) from UIKit's standard components, the behavior is not stable and it will break with upcoming updates for sure.
What Google Photos has is the result of way more advanced reverse engineering. In above implementation, you can't see More option screen. The hierarchy UIActivityViewController expects is broken.
Hope this helps.
Okay, I thought about this and I did some intensive research on the web but nobody ever seemed to needed to modify it like you want. So here are my guesses how Google engineers solved this:
They reverse engineered the UIActivityViewController and call some private APIs to get the same icons to show up and the reordering controllers
They use the UIViewController transitioning API and hack the view hierarchy of a modally presented UIActivityViewController, removing the cancel button and adding some custom views on top of its view
An indicator for the second option could be that the top of the presented "sheet" has a white background while the bottom has the greyish color.
Unfortunately I'm not very fit with the transitioning API as I'm just about to learn it but my understanding is that you can provide a custom object as the transitioning delegate.
This object then gets called when you present/dismiss or push/pop a UIViewController. It will get a reference to both, the presenting and the presented view controller and you can have fun with both of their views.
This should make it "quiet" easy to remove and add some subviews, change frames and colors etc while still having all the default behavior.
I hope this answer helps you to achieve what you want. However, be aware that the structure of the controller could change at any time so always make sure to also test agains betas so that you don't get caught by surprise when apple releases an update which breaks your UI. :)

Have Slide menu cover view controller, not move it aside (iOS)

In my iOS app I am using SWRevealViewController to add menu functionality. It works great, however my client wants to make the menu cover the current view controller, not move it to the right.
I mean something like this:
before:
after:
The author of this library told me to implement the following, where I can get current offset of the menu and move the current view controller by that offset by doing something like that:
let rvrw = self.revealViewController().rearViewRevealWidth
var bounds = navigationController!.view.bounds
bounds.origin.x = rvrw
navigationController!.view.bounds = bounds
The problem is that the menu is behind it. I would like to know how I can do that in this library or if there is another library with similar implementation and that functionality as well.
I have found tons of libraries, but all of them are pushing the controller not covering it. I have found one or two which do it way I need, but they missed some features (like being under navigation bar), were outdated or buggy.
SWRevealViewController offers some customization options
documented in the SWRevealViewController.h header file.
It seems as though the frontView is the part with "WatchKit" in your example and the rearView is the menu.
In that case, you might be able to use setFrontViewPosition with the value FrontViewPositionLeftSide, which gives the frontView
// Left position, front view is presented left-offseted by rightViewRevealWidth
If that does not work, you could also try FrontViewPositionLeftSideMost, which gives the position
// Left most position, front view is presented left-offseted by rightViewRevealWidth+rigthViewRevealOverdraw
I did not have the setup at hand to try this.

Replace views in detail part of the screen after user's action

I am new to Swift and app development. I have a design question. I am trying to make a view that contains a slider, but that as soon as the "touch up inside" action is performed, is replaced by a progress bar + button. If the button is pressed, then we go back to showing only the slider. This view will be not take the whole screen, only part of it.
What would be the best way of doing this? I have already investigated several options:
1. using a navigation controller with a segue triggered by the slider that goes into a new scene with a progress bar & button.
2. creating a custom view with two properties: a slider and a custom view (progress bar & button). The slider can be laid out using interface builder, and the custom view can be loaded from a nib file when needed.
3. creating a custom view with two properties: a slider and a custom view (progress bar & button). The new progress bar and button are created programmatically whenever the action is triggered on the slider.
I have already tried options 1 and 2 to some extent with no success. Since I am a beginner, I am trying to use the IB as much as possible. What is the best option (if any) from the list?
You can do this directly on the Storyboard without needing to create a custom view class, but you'll need a few lines of code in any case. Just drag a Slider into your View, and then drag a button and a progress view directly on top of that. Now select the button, and in the Attributes inspector, tick the box next to "Hidden". Do the same with the progress bar. Then just open the assistant editor and connect references to all 3 of those. You'll also need to create an action for the button (I've called it change), and make sure you leave the type field as AnyObject. Add the following line inside ViewDidLoad:
slider.addTarget(self, action: Selector("change:"), forControlEvents: .TouchUpInside)
This line just makes it so that change gets called anytime the user uses the slider. Obviously change slider to whatever you name your UISlider. You can implement the change function like this:
#IBAction func change(sender: AnyObject) {
slider.hidden = !slider.hidden
button.hidden = !button.hidden
progressBar.hidden = !progressBar.hidden
}
This is a simple implementation that just toggles between true and false for each of the items, but you'll probably want to do it differently depending on what this project does.
Now, if you want to put this functionality in multiple places in your app it might be easiest to create a custom view using the same concept as above, in which case check out this tutorial on how to create an IBDesignable UIView.

Resources