Issue performing a segue when function called from another class - ios

I have an Intro video section for an app. The storyboard has a segue "toMainMenu" that links to another storyboard view controller.
The IntroVideoVC has two classes inside it:-
class IntroVideoVC: UIViewController {
var videoView: IntroVideoView!
override func viewDidLoad() {
videoView = IntroVideoView(controller: self)
self.view.addSubview(videoView)
let tap = UITapGestureRecognizer(target: self, action: #selector(tappedVideo))
view.addGestureRecognizer(tap)
}
#objc func tappedVideo() {
videoFinished();
}
func videoFinished() {
self.performSegue(withIdentifier: "toMainMenu", sender: self)
}
}
class IntroVideoView: UIView {
init(controller: UIViewController) {
super.init(frame: CGRect(x: 0, y: 0, w: 0, h: 0))
self.controller = controller
...
}
func handleVideo(videoPlayer: AVPlayer) {
...
IntroVideoVC().videoFinished()
}
}
If i tap the video it correctly performs the segue, however if i let the video end which in-turn calls IntroVideoViews -> handleVideo() method i get a crash stating it has no segue with identifier "toMainMenu".
I think i understand why but unsure how to get around the issue being new to swift. I believe it is because handleVideo() is using IntroVideoVC as a singleton and so is losing reference to IntroVideoVC's controller! I maybe (read: likely) be wrong but that is what i feel may be causing this issue.
Just looking for a nudge in the right direction
thanks in advance

You could use protocol to solve that, it would be something like this:
protocol IntroVideoDelegate {
func videoFinished()
}
class IntroVideoVC: UIViewController, IntroVideoDelegate {
var videoView: IntroVideoView!
override func viewDidLoad() {
videoView = IntroVideoView(controller: self)
videoView.videoDelegate = self
self.view.addSubview(videoView)
let tap = UITapGestureRecognizer(target: self, action: #selector(tappedVideo))
view.addGestureRecognizer(tap)
}
#objc func tappedVideo() {
videoFinished();
}
func videoFinished() {
self.performSegue(withIdentifier: "toMainMenu", sender: self)
}
}
class IntroVideoView: UIView {
videoDelegate: IntroVideoDelegate?
init(controller: UIViewController) {
super.init(frame: CGRect(x: 0, y: 0, w: 0, h: 0))
self.controller = controller
...
}
func handleVideo(videoPlayer: AVPlayer) {
...
videoDelegate.videoFinished()
}
}
Remember that the videoView is still a subview to IntroVideoVC, so if the app goes back to this VC the user will see it, so if you don't want that to happen, be sure to remove it with:
videoView.removeFromSuperview()
In the videoFinished() func
If you want to do running a VC func from the view you should get the parent of IntroVideoView, here is how: Given a view, how do I get its viewController? But i'm pretty sure it breaks MVC

Related

UIScrollView zoomScale not updating after initial update

I have a tap gesture that runs this code and it works once but then stops updating the zoomScale.
#objc func sampleTapGestureTapped(_ recognizer: UITapGestureRecognizer) {
print("tapped")
if self.scrollView_Image.zoomScale > self.scrollView_Image.minimumZoomScale {
scrollView_Image.setZoomScale(1, animated: false)
} else {
scrollView_Image.setZoomScale(3, animated: false)
}
}
The function runs and the tapped print is logged out but the zoomScale doesn't seem to change.
Perhaps the problem is your hard coded numbers. Here is how I do it:
if sv.zoomScale < sv.maximumZoomScale {
sv.setZoomScale(sv.maximumZoomScale, animated:anim)
}
else {
sv.setZoomScale(sv.minimumZoomScale, animated:anim)
}
Notice there are no hard coded numbers here. It works for any scroll view.
I've tried this in a small View Controller and it is working fine. It is possible that an action you are taking in the selector is stopping the gesture from working. You should probably post the selector function code as well, and anything relevant to setting up the Recognizer and the Image View
class ViewController: UIViewController {
var tappableImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
tappableImageView = UIImageView(frame: CGRect(x: 40, y: 40, width: 100, height: 100))
tappableImageView.backgroundColor = .red
view.addSubview(tappableImageView)
tappableImageView.isUserInteractionEnabled = true
let t = UITapGestureRecognizer(target: self, action: #selector(imageViewDoubleTapped(_:)))
t.numberOfTapsRequired = 2
tappableImageView.addGestureRecognizer(t)
}
#objc func imageViewDoubleTapped(_ recognizer: UITapGestureRecognizer) {
print("Double Tapped")
}
}

Status Bar Hides The Navigation Bar

I am developing an iOS application where I am streaming a live video from youtube. After exiting the full-screen mode the status bar overlays the navigation bar in all views (as seen in this picture)
I have searched for this problem and found a solution which is adding the following code in a function that is executed after exiting the full-screen mode:
#objc func videoExitFullScreen (_ sender: Any?){
navBar.frame.origin = CGPoint(x: 0, y: 20)
}
This code successfully solved the problem. However, I need to place this code in every controller of my application. When I tried to place it in the app delegate as the following it did not solve the problem:
#objc func videoExitFullScreen (_ sender: Any?){
UINavigationBar.appearance().frame.origin = CGPoint(x: 0, y: 20)
}
Do you have any suggestions where I can place the code in one place and it will solve the problem?
try like this
// create a new class of type UIViewController
class BaseViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.videoExitFullScreen()
}
func videoExitFullScreen (){
navBar.frame.origin = CGPoint(x: 0, y: 20)
}
}
// replace UIViewController to BaseViewController
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
Use Auto Layout and pin your view to Top Layout Guide, or use the new Safe Area Layout Guides. To enable it, in your storyboard's File inspector tick the following option.

How to add an action to a UIButton that is in another class

The code below compiles fine, but crashes with an unrecognized selector sent to instance error.
I have one class that inherits from UIViewController:
class Controller: UIViewController {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
... Other code ...
}
}
And another class that is just a wrapper for a UIView and contains buttons:
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
let button = UIButton()
... Some button layout code ...
button.addTarget(target, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc static func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
For the sake of clarity, I left out a large chunk of code and kept what I thought was necessary. I think that my code doesn't work because of my misunderstanding of the how the target works in the addTarget function. Normally, I would just use self as the target of my button's action, so I just tried to pass along self from the view controller to the CustomToolbarWrapper's init function.
What else I have tried:
Changing the button's target from target to self like this:
button.addTarget(self, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
results in the app not crashing anymore. Instead, however, I believe that line of code fails to do anything (which doesn't throw an error for some reason?) because attempting to print button.allTargets or even button.allTargets.count results in the app crashing at compile time, with an EXC_BREAKPOINT error and no error description in the console or the XCode UI (which just confuses me even more because there are no breakpoints in my code!).
Also, making buttonPressed(_:) non-static does not change any of the previously mentioned observations.
Also, to make sure the button could in fact be interacted with, I added this in the viewDidLoad() of Controller:
for subview in toolbar.subviews? {
if let button = subview as? UIButton {
button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
}
}
and added a simple testing method to Controller for the button:
#objc func buttonPressed(_ sender: UIButton) {
print("Button Pressed")
}
And running the code did result in "Button Pressed" being printed in the console log, so the button should be able to be interacted with by the user.
Feel free to let me know if you think this is not enough code to figure out the problem, and I will post more details.
Edit
I prefer to keep the implementation of the button's action in the CustomToolbarWrapper class to prevent repeating code in the future, since the action will be the same no matter where an instance of CustomToolbarWrapper is created.
The best option would be to add the target in your controller and then call a method in your toolbarWrapper on button press. But if you really need to keep this design, you should have a strong reference to your toolbarWrapper in your controller class, otherwise your toolbarWrapper is deallocated and nothing gets called. Also, the buttonTapped(_:) method does not need to be static. Thus, in your controller:
class Controller: UIViewController {
var toolbarWrapper: CustomToolbarWrapper?
override func viewDidLoad() {
toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
... Other code ...
}
}
And in your wrapper:
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height,width: view.frame.width, height: height))
let button = UIButton()
... Some button layout code ...
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
There is another way I would use which is delegation. The target does not necessarily have to be a controller, it can be the CustomToolbarWrapper itself.
First, declare a protocol
protocol CTDelegate: AnyObject {
func didClickButton()
}
Then in CustomToolbarWrapper add a property, weak var delegate: CTDelegate? and a button action:
#objc func buttonTapped(_ sender: UIButton) {
delegate?.didClickButton()
}
So in your case, it becomes:
button.addTarget(self, action: #selector(CustomToolbarWrapper.buttonTapped(_:)), for: .touchUpInside)
Then when you go to any ViewController, conform to CTDelegate and initialize the CustomToolbarWrapper, you can set its delegate to the controller.
e.g
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
toolbarWrapper.delegate = self
and implement your action inside the method you are conforming to in your controller i.e.
func didClickButton()
Your problem is right here:
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
You're passing an instance of Controller class which doesn't implement the buttonTapped(_:) selector. It is implemented by your CustomToolbarWrapper class. This is a bad design in general. You should either follow a delegate pattern, or a callback pattern.
Updated Answer:
Delegate pattern solution:
class Controller: UIViewController, CustomToolbarWrapperDelegate {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, buttonDelegate: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
}
// MARK: - CustomToolbarWrapperDelegate
func buttonTapped(inToolbar toolbar: CustomToolbarWrapper) {
print("button tapped")
}
}
protocol CustomToolbarWrapperDelegate: AnyObject {
func buttonTapped(inToolbar toolbar: CustomToolbarWrapper) -> Void
}
class CustomToolbarWrapper {
var toolbarView: UIView
weak var buttonDelegate: CustomToolbarWrapperDelegate?
init(view: UIView, buttonDelegate: CustomToolbarWrapperDelegate?) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
self.buttonDelegate = buttonDelegate
let button = UIButton()
button.addTarget(self, action: #selector(self.buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc private func buttonTapped(_ sender: Any) {
// Your button's logic here. Then call the delegate:
self.buttonDelegate?.buttonTapped(inToolbar: self)
}
}
If you'd rather stick to your current design then just implement the following changes:
class Controller: UIViewController {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self, selector: #selector(self.buttonTapped(_:)), events: .touchUpInside)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
}
#objc private func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any?, selector: Selector, events: UIControlEvents) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
let button = UIButton()
button.addTarget(target, action: selector, for: events)
toolbarView.addSubview(button)
}
}

Swift3 unable to implement the function in another class

I have added gesture in classA for upswipe, If swipe up then the function available in classB has to be called, while calling classB function in classA the function is getting called but the UI changes are not made in classB.
Thanks in advance
class A: UIViewController {
var swipeUp = UISwipeGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGestureUp))
swipeUp.direction = UISwipeGestureRecognizerDirection.up
self.view.addGestureRecognizer(swipeUp)
}
func respondToSwipeGestureUp(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.up:
UserDefaults.standard.set("open", forKey: "status")
UserDefaults.standard.synchronize()
print("Swiped up")
B().moveUp()
default:
break
}
}
}
}
class B: UIViewController {
func moveUp() {
let status = UserDefaults.standard.object(forKey: "status") as! String
if status .isEqual("open")
{
let fullView1: CGFloat = 100
self.view.frame = CGRect(x: 0, y: fullView1, width: self.view.frame.width, height: self.view.frame.height)
}
else
{
}
}
}
Class B UI does not change because you didn't present B controller on navigation. I mean to say If you controller A is currently present on screen and on SwipeGesture event you are updating the UI using controller B whose is not present on screen yet. Why are you doing this and when Did you Push controller B.
I hope you got your answer.
Make your method as class method
class func moveUp() {
// your code
}
And call it as:
B.moveUp()

Container view, Failed use delegate

i wanna designed slide function.So i use container view in storyboard without any segue.there is a button in centerView to open or close leftView.but this function is written in container.swift and i don't know how to set delegate..please help me,thanks.(All codes as below)
Container.swift
import UIKit
class ContainerViewController: UIViewController,test {
var leftViewController: UIViewController? {
willSet{
if self.leftViewController != nil {
if self.leftViewController!.view != nil {
self.leftViewController!.view!.removeFromSuperview()
}
self.leftViewController!.removeFromParentViewController()
}
}
didSet{
self.view!.addSubview(self.leftViewController!.view)
self.addChildViewController(self.leftViewController!)
}
}
var rightViewController: UIViewController? {
willSet {
if self.rightViewController != nil {
if self.rightViewController!.view != nil {
self.rightViewController!.view!.removeFromSuperview()
}
self.rightViewController!.removeFromParentViewController()
}
}
didSet{
self.view!.addSubview(self.rightViewController!.view)
self.addChildViewController(self.rightViewController!)
}
}
var menuShown: Bool = false
func showMenu() {
print(__FUNCTION__,__LINE__)
UIView.animateWithDuration(0.3, animations: {
self.rightViewController!.view.frame = CGRect(x: self.view.frame.origin.x + 235, y: self.view.frame.origin.y, width: self.view.frame.width, height: self.view.frame.height)
}, completion: { (Bool) -> Void in
self.menuShown = true
})
}
func hideMenu() {
UIView.animateWithDuration(0.3, animations: {
self.rightViewController!.view.frame = CGRect(x: 0, y: self.view.frame.origin.y, width: self.view.frame.width, height: self.view.frame.height)
}, completion: { (Bool) -> Void in
self.menuShown = false
})
}
var centerView:CenterViewController!
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let CenterNC:UINavigationController = storyboard.instantiateViewControllerWithIdentifier("CenterNC") as! UINavigationController
let leftVC:LeftViewController = storyboard.instantiateViewControllerWithIdentifier("LeftVC") as! LeftViewController
centerView = storyboard.instantiateViewControllerWithIdentifier("CenterVC") as! CenterViewController
self.leftViewController = leftVC
self.rightViewController = CenterNC
self.centerView.delegate = self
// Do any additional setup after loading the view.
}
}
CenterViewController.swift
import UIKit
protocol test{
func showMenu()
func hideMenu()
}
class CenterViewController: UIViewController {
var delegate:test! = nil
#IBAction func pressed(sender: AnyObject) {
if (delegate != nil) {
delegate.showMenu()
}
else{
print("Error")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Debug ContainerView.swift
Debug CenterViewController.swift
Usually you use whatever.delegate = self but I'm not quite sure what you want to do. (Also whatever.datasource = self). I dont know if this works for VCs.
As far as I know it is bad practice to have one UIViewController in the storyboard that contains two other VCs.
Rather you should either transition programmatically or with a storyboard segue.
self.presentViewController(LeftViewController(), animated: true, completion: nil)
self.dismissViewController(true, completion: nil)
View Controllers are automatically removed from memory (look up ARC if your curious) (as long as you don't have variables in the VC to be removed that are (or can be) accessed from another class that wasn't deinitialized - if that's confusing ignore it)
If you want a sliding animation or something like this, it's probably best to find something on GitHub to clone and import into your project. I've made an app with a tab bar that slides between views with some code I found online.
I've been droning but having two VCs in one VC is terrible for memory and in general bad practice. Utilize Custom Segues instead.

Resources