I'm using the Slide menu controller pod in my project and have a little problem. When there are controllers in navigation stack and I want to swipe left, the burger menu appears instead of popping the contorller. How could that be changed? Could someone please guide me?
So I know it's a pretty basic functionality that you get for free, but it's overridden here.
I tried switching off the pan gesture for the slide menu, but it powered off the whole mechanism.
SlideMenuOptions.panGesturesEnabled = false
I also found the handleLeftPanGesture(_ panGesture: UIPanGestureRecognizer) method, which looks as follows:
open func isTargetViewController() -> Bool {
// Function to determine the target ViewController
// Please to override it if necessary
guard let navController = navigationController else { return true }
return navController.viewControllers.isEmpty
}
#objc func handleLeftPanGesture(_ panGesture: UIPanGestureRecognizer) {
if !isTargetViewController() {
navigationController?.popViewController(animated: true)
}
if isRightOpen() {
return
}
switch panGesture.state {
case UIGestureRecognizerState.began:
if LeftPanState.lastState != .ended && LeftPanState.lastState != .cancelled && LeftPanState.lastState != .failed {
return
}
if isLeftHidden() {
self.delegate?.leftWillOpen?()
} else {
self.delegate?.leftWillClose?()
}
LeftPanState.frameAtStartOfPan = leftContainerView.frame
LeftPanState.startPointOfPan = panGesture.location(in: view)
LeftPanState.wasOpenAtStartOfPan = isLeftOpen()
LeftPanState.wasHiddenAtStartOfPan = isLeftHidden()
leftViewController?.beginAppearanceTransition(LeftPanState.wasHiddenAtStartOfPan, animated: true)
addShadowToView(leftContainerView)
setOpenWindowLevel()
case UIGestureRecognizerState.changed:
if LeftPanState.lastState != .began && LeftPanState.lastState != .changed {
return
}
let translation: CGPoint = panGesture.translation(in: panGesture.view!)
leftContainerView.frame = applyLeftTranslation(translation, toFrame: LeftPanState.frameAtStartOfPan)
applyLeftOpacity()
applyLeftContentViewScale()
case UIGestureRecognizerState.ended, UIGestureRecognizerState.cancelled:
if LeftPanState.lastState != .changed {
setCloseWindowLevel()
return
}
let velocity:CGPoint = panGesture.velocity(in: panGesture.view)
let panInfo: PanInfo = panLeftResultInfoForVelocity(velocity)
if panInfo.action == .open {
if !LeftPanState.wasHiddenAtStartOfPan {
leftViewController?.beginAppearanceTransition(true, animated: true)
}
openLeftWithVelocity(panInfo.velocity)
track(.leftFlickOpen)
} else {
if LeftPanState.wasHiddenAtStartOfPan {
leftViewController?.beginAppearanceTransition(false, animated: true)
}
closeLeftWithVelocity(panInfo.velocity)
setCloseWindowLevel()
track(.leftFlickClose)
}
case UIGestureRecognizerState.failed, UIGestureRecognizerState.possible:
break
}
LeftPanState.lastState = panGesture.state
}
And I'm trying to count the navigaion ViewControllers, but it didn't help.
Actaully I found the solution which might help someone.
You need to add this to ViewController and conform to UIGestureRecognizerDelegate protocol
override func viewDidAppear(_ animated: Bool) {
slideMenuController()?.removeLeftGestures()
navigationController?.interactivePopGestureRecognizer?.delegate = self
}
extension AdditionalInformationController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.isEqual(navigationController?.interactivePopGestureRecognizer) {
navigationController?.popViewController(animated: true)
}
return false
}
}
Related
I want to make a 3d game on Xcode, SceneKit. To test it out, I started a simple project about a box, that if you tapped, moves in X direction. My question is how do you make the tap gesture work in a 3d game? I've been trying to add it but it just doesn't work. Here is all the code that I've written in case I'm doing something wrong:
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene(named:"art.scnassets/Box.scn")
let newscene = self.view as! SCNView
newscene.scene = scene
let tap = UITapGestureRecognizer(target: self, action: #selector(taps(tap:)));newscene.addGestureRecognizer(tap)
tap.numberOfTapsRequired = 1
tap.numberOfTouchesRequired = 1
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
#objc func taps(tap:UITapGestureRecognizer){
let newscene = self.view as! SCNView
let scene = SCNScene(named: "art.scnassets/Box.scn")
let box = scene?.rootNode.childNode(withName: "Box", recursively: true)
let place = tap.location(in: newscene)
let tapped = newscene.hitTest(place, options: [:])
if tapped.count > 0{
box?.runAction(SCNAction.repeatForever(SCNAction.moveBy(x: 5, y: 0, z: 0, duration: 1)))
}
}
}
Try this...
#objc func handleTap(recognizer: UITapGestureRecognizer)
{
if(data.isNavigationOff == true) { return } // No panel select if Add, Update, EndWave, or EndGame
if(gameMenuTableView.isHidden == false) { return } // No panel if game menu is showing
let location: CGPoint = recognizer.location(in: gameScene)
if(data.isAirStrikeModeOn == true)
{
let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
let scenePoint = gameScene.unprojectPoint(SCNVector3(location.x, location.y, CGFloat(projectedPoint.z)))
gameControl.airStrike(position: scenePoint)
}
else
{
let hitResults = gameScene.hitTest(location, options: hitTestOptions)
for vHit in hitResults
{
if(vHit.node.name?.prefix(5) == "Panel")
{
// May have selected an invalid panel or auto upgrade was on
if(gameControl.selectPanel(vPanel: vHit.node.name!) == false) { return }
return
}
}
}
}
If Airstrike - Tap drops a bomb on the screen where you touched, translating it to 3D
Else hittest looks for panels which may return multiple results
As we all know, to avoid clicking twice, we can set the code bellow on the tap method and add a HUD such as SVProgress.show().
isUserInteractionEnabled = false
After the network request, set it to true and SVProgress.dismiss().
I wonder if there is a method to extract the function for those button which needs to send a request. I have thought to use method swizzling. Add this to the button extension, the codes is bellow. It seems not good. Do you guys have some good ways to extract the function? Using inheritance, protocol or something else?
extension UIButton {
private struct AssociatedKeys {
static var cp_submitComplete = "cp_submitComplete"
static var cp_defaultMessage:String = NSLocalizedString("Loading", comment: "prompt")
static var cp_customMessage = "cp_customMessage"
}
var submitNotComplete: Bool {
get {
let objc_Get = objc_getAssociatedObject(self, &AssociatedKeys.cp_submitComplete)
if objc_Get != nil {
if let objc_Get = objc_Get as? Bool, objc_Get == true {
return true
}
return false
} else {
return false
}
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.cp_submitComplete, newValue as Bool, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
if !newValue {
isUserInteractionEnabled = true
SVProgressHUD.dismiss()
}
}
}
var customMessage: String {
get {
let cp_customMessage = objc_getAssociatedObject(self, &AssociatedKeys.cp_customMessage)
if let message = cp_customMessage {
return message as! String
} else {
return AssociatedKeys.cp_defaultMessage
}
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.cp_customMessage, newValue as String, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
override open class func initialize() {
if self == UIButton.self {
DispatchQueue.once(NSUUID().uuidString, block: {
let systemSel = #selector(UIButton.sendAction(_:to:for:))
let swizzSel = #selector(UIButton.cpSendAction(_:to:for:))
let systemMethod = class_getInstanceMethod(self, systemSel)
let swizzMethod = class_getInstanceMethod(self, swizzSel)
let isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod))
if isAdd {
class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
} else {
method_exchangeImplementations(systemMethod, swizzMethod);
}
})
}
}
private dynamic func cpSendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
cpSendAction(action, to: target, for: event)
if submitNotComplete {
//begin submit
isUserInteractionEnabled = false
SVProgressHUD.show(withStatus: customMessage)
}
}
}
I think it's a bad idea to handle this kind of logic in UIButton. I would rather make the view controller responsible for enabling/disabling the button.
func handleTap(_ sender: UIButton) {
sender.isEnabled = false
SVProgressHUD.show(withStatus: customMessage)
doSomeTaskAsync(withCompletion: {
sender.isEnabled = true
SVProgressHUD.dismiss()
})
}
I need to access the value of a variable in a if statement, this is my code:
var codeError : Int?
#IBAction func mySwitch(_ sender: UISwitch) {
if sender.isOn {
codeError = 1
} else {
codeError = 2
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if codeError == 1 {
front()
} else if codeError == 2 {
back()
}
}
My two functions, this is my front() function:
func front() {
let deviceDiscoverySession = AVCaptureDeviceDiscoverySession(deviceTypes: [AVCaptureDeviceType.builtInDualCamera, AVCaptureDeviceType.builtInTelephotoCamera,AVCaptureDeviceType.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: AVCaptureDevicePosition.unspecified)
for device in (deviceDiscoverySession?.devices)! {
if device.position == AVCaptureDevicePosition.front {
do {
let input = try AVCaptureDeviceInput(device: device)
if captureSession.canAddInput(input) {
captureSession.addInput(input)
if captureSession.canAddOutput(photoOutput) {
captureSession.addOutput(photoOutput)
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer?.connection.videoOrientation = AVCaptureVideoOrientation.portrait
cameraView.layer.addSublayer(previewLayer!)
}
}
} catch {
print("Error")
}
}
}
captureSession.startRunning()
print("😇")
}
And back() function:
func back() {
let deviceDiscoverySession = AVCaptureDeviceDiscoverySession(deviceTypes: [AVCaptureDeviceType.builtInDualCamera, AVCaptureDeviceType.builtInTelephotoCamera,AVCaptureDeviceType.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: AVCaptureDevicePosition.unspecified)
for device in (deviceDiscoverySession?.devices)! {
if device.position == AVCaptureDevicePosition.back {
do {
let input = try AVCaptureDeviceInput(device: device)
if cS.canAddInput(input) {
cS.addInput(input)
if cS.canAddOutput(photoOutput) {
cS.addOutput(photoOutput)
previewLayer = AVCaptureVideoPreviewLayer(session: cS)
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer?.connection.videoOrientation = AVCaptureVideoOrientation.portrait
cameraView.layer.addSublayer(previewLayer!)
}
}
} catch {
print("Error")
}
}
}
cS.startRunning()
print("🙃")
}
The problem is when I run the app. I get a nil value of the codeError when I should get 1 o 2.
How do I solve this?
I'm not sure why you're accessing a variable set by user when interacting with the view on viewWillAppear. I suggest spending some time and studying UIViewController life cycles.
But if you just want to print error codes on switch changing values, this should do it.
var codeError : Int?
#IBAction func mySwitch(_ sender: UISwitch) {
if sender.isOn {
codeError = 1
} else {
codeError = 2
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// will print nothing since errorCode is not set
printCodeError()
}
func printCodeError() {
if let codeError = self.codeError {
print("\(codeError)")
}
}
it is working like you original code. but you print it inside viewWillAppear. but at this time it is not initialized from you uiswitch.
one solution: give it default value:
var codeError: Int = 1
or shorter
var codeError = 1
Note:
viewWillAppear(_:)
Notifies the view controller that its view is about to be added to a view hierarchy.
see https://developer.apple.com/reference/uikit/uiviewcontroller/1621510-viewwillappear
but it is called on the view where your UISwitch is on. for your main goal (hidden in comments on the other answer) it is the wrong place to print out the variable
I would like to hide the Statusbar when i hide Navigationbar and Toolbar.
How i can make that that the Statusbar is hidden in if NavigationBar.hidden == false && Toolbar.hidden == false{} ??
I have no idea how i can make that, i know the func to return the Statusbarhidden but thats in the whole ViewController and i would to hide it in the func.
Thanks for your Help.
func ImageTapGesture () {
if NavigationBar.hidden == false && Toolbar.hidden == false{
NavigationBar.hidden = true
Toolbar.hidden = true
} else if NavigationBar.hidden == true && Toolbar.hidden == true {
NavigationBar.hidden = false
Toolbar.hidden = false
}
}
Under the Swift language, you can refer to the following code hidden, don't need animation effects can be commented out.
var isHidden:Bool = false
#IBAction func clicked(_ sender: AnyObject) {
isHidden = !isHidden
UIView.animate(withDuration: 0.5, animations: { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
})
}
override var preferredStatusBarUpdateAnimation : UIStatusBarAnimation {
return UIStatusBarAnimation.slide
}
override var prefersStatusBarHidden : Bool {
return isHidden
}
Can also refer to the following link to the Demo, is a time I write the project requirements.
Github:https://github.com/ReverseScale/HiddenStatusBar
Wish I could help you.
A Swift 2.x compatible workaround to make what do you need to do :
func hideStatusBar(yOffset:CGFloat) { // -20.0 for example
let statusBarWindow = UIApplication.sharedApplication().valueForKey("statusBarWindow") as! UIWindow
statusBarWindow.frame = CGRectMake(0, yOffset, statusBarWindow.frame.size.width, statusBarWindow.frame.size.height)
}
func showStatusBar() {
let statusBarWindow = UIApplication.sharedApplication().valueForKey("statusBarWindow") as! UIWindow
statusBarWindow.frame = CGRectMake(0, 0, statusBarWindow.frame.size.width, statusBarWindow.frame.size.height)
}
To use it for example you can launch:
hideStatusBar(-20.0)
I'm having trouble making it only slide from left to right. When I try to get to the side bar menu from swiping left to right it opens but when I slide from right to left it leaves a black space unoccupied. I want it to just work for the slide guesture from left to right to open my side bar menu. Let me know if you need more information. Anything will help thanks!
import UIKit
import QuartzCore
enum SlideOutState {
case LeftCollapsed
case LeftPanelExpanded
}
class ContainerViewController: UIViewController, CenterViewControllerDelegate, UIGestureRecognizerDelegate {
var centerNavigationController: UINavigationController!
var centerViewController: CenterViewController!
var currentState: SlideOutState = .LeftCollapsed {
didSet {
let shouldShowShadow = currentState != .LeftCollapsed
showShadowForCenterViewController(shouldShowShadow)
}
}
var leftViewController: SidePanelViewController?
let centerPanelExpandedOffset: CGFloat = 60
override init() {
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
centerViewController = UIStoryboard.centerViewController()
centerViewController.delegate = self
// wrap the centerViewController in a navigation controller, so we can push views to it
// and display bar button items in the navigation bar
centerNavigationController = UINavigationController(rootViewController: centerViewController)
view.addSubview(centerNavigationController.view)
addChildViewController(centerNavigationController)
centerNavigationController.didMoveToParentViewController(self)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: "handlePanGesture:")
centerNavigationController.view.addGestureRecognizer(panGestureRecognizer)
}
func handlePanGesture(recognizer: UIPanGestureRecognizer) {
let gestureIsDraggingFromLeftToRight = (recognizer.velocityInView(view).x > 0)
switch(recognizer.state) {
case .Began:
if (currentState == .LefCollapsed) {
if (gestureIsDraggingFromLeftToRight) {
addLeftPanelViewController()
}
showShadowForCenterViewController(true)
}
case .Changed:
recognizer.view!.center.x = recognizer.view!.center.x + recognizer.translationInView(view).x
recognizer.setTranslation(CGPointZero, inView: view)
case .Ended:
if (leftViewController != nil) {
// animate the side panel open or closed based on whether the view has moved more or less than halfway
let hasMovedGreaterThanHalfway = recognizer.view!.center.x > view.bounds.size.width
animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway)
}
default:
break
}
}
}
Add in the Began case an else-Statement where u disable the recognizer. Enable it again before break.
func handlePanGesture(recognizer: UIScreenEdgePanGestureRecognizer) {
let gestureIsDraggingFromLeftToRight = (recognizer.velocityInView(view).x > 0)
switch(recognizer.state) {
case .Began:
if (currentState == .BothCollapsed) {
if (gestureIsDraggingFromLeftToRight) {
addLeftPanelViewController()
} else {
recognizer.enabled = false
}
showShadowForCenterViewController(true)
}
case .Changed:
recognizer.view!.center.x = recognizer.view!.center.x + recognizer.translationInView(view).x
recognizer.setTranslation(CGPointZero, inView: view)
case .Ended:
if (leftViewController != nil) {
// animate the side panel open or closed based on whether the view has moved more or less than halfway
let hasMovedGreaterThanHalfway = recognizer.view!.center.x > view.bounds.size.width
animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway)
}
default:
recognizer.enabled = true
break
}
}