Here is the sample source code:
ViewController:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view = Home()
}
}
Home
class Home: UIView {
...
func parent() -> UIViewController {
var responder: UIResponder? = self.next
while responder != nil {
if let c = responder as? UIViewController { return c }
else { responder = responder?.next }
}
return nil
}
}
For the line
var responder: UIResponder? = self.next
The responder is always nil, that I cannot get the ViewController.
Something strange is that, I got no problem with the same function for all the subviews of Home.
Related
So I am trying to display a splitView with a tabBar but the NavigationView does not stretch all the way to the bottom. Is there something I am missing?
here is the code I am using:
import Foundation
import SwiftUI
protocol SettingsCoordinatorInput: ManagedCoordinator {
}
class SettingsCoordinator: ManagedCoordinator, SettingsCoordinatorInput {
var navigationController = UINavigationController()
weak var viewModel: SettingsViewModel?
lazy var viewController: UIHostingController<SettingsSplitView> = {
let viewModel = SettingsViewModel(coordinator: self)
self.viewModel = viewModel
return UIHostingController(rootView: SettingsSplitView())
}()
override func topController() -> UIViewController? {
return viewController
}
convenience init(delegate: ManagedCoordinator) {
self.init()
self.delegate = delegate
}
override func start() {
navigationController.viewControllers = [viewController]
}
#objc private func close() {
finish()
}
override func finish() {
super.finish()
viewController.navigationController?.dismiss(animated: true, completion: nil)
}
}
and the result is like this:
I need to get a reference (object) of UIViewController in sub class of UIButton. Here I've tried something but failed.
class NavigationBarButton: UIButton {
override func didMoveToSuperview() {
super.didMoveToSuperview()
var viewController: UIViewController? {
var nextResponder: UIResponder? = self
repeat {
nextResponder = nextResponder?.next
if let viewController = nextResponder as? UIViewController {
return viewController
}
} while nextResponder != nil
return nil
}
guard let vcViewController = self.viewController else { print("NavigationBarButton view controller could not found"); return }
print("handle further operations with ViewController of Button")
}
}
class FirstVC: UIViewController {
#IBOutlet weak var myButton: NavigationBarButton?
}
Result:
NavigationBarButton view controller could not found
Is there any other way, without updating UIViewController, I can get view controller reference in sub class of UIButton. Any other UIButton method can help me here, where I can get view controller of button.
Similar SO Que. but not useful for this issue: Get current UIViewController from UIButton's class
This may work for you.
class NavigationBarButton: UIButton {
override func didMoveToWindow() {
super.didMoveToWindow()
var viewController: UIViewController? {
var nextResponder: UIResponder? = self
repeat {
nextResponder = nextResponder?.next
if let viewController = nextResponder as? UIViewController {
return viewController
}
} while nextResponder != nil
return nil
}
guard let vcViewController = self.viewController else { print("NavigationBarButton view controller could not found"); return }
print("handle further operations with ViewController of Button")
}
}
class FirstVC: UIViewController {
#IBOutlet weak var myButton: NavigationBarButton?
}
I want to trigger Navigation controller to some other screen when i press the button in UIView class. How can i do this?
//Code for UIView Class in Which Button Iboutlet is created
import UIKit
protocol ButtonDelegate: class {
func buttonTapped()
}
class SlidesVC: UIView {
var delegate: ButtonDelegate?
#IBAction func onClickFinish(_ sender: UIButton) {
delegate?.buttonTapped()
}
#IBOutlet weak var imgProfile: UIImageView!
}
//ViewController Class code in Which Button Protocol will be entertained
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
var slidesVC = SlidesVC()
override func viewDidLoad() {
super.viewDidLoad()
slidesVC = SlidesVC()
// add as subview, setup constraints etc
slidesVC.delegate = self
}
extension BaseVC: ButtonDelegate {
func buttonTapped() {
self.navigationController?.pushViewController(SettingsVC.settingsVC(),
animated: true)
}
}
A more easy way is to use typealias. You have to write code in 2 places. 1. your viewClass and 2. in your View Controller.
in your SlidesView class add a typealias and define param type if you need otherwise leave it empty.
class SlidesView: UIView {
typealias OnTapInviteContact = () -> Void
var onTapinviteContact: OnTapInviteContact?
#IBAction func buttonWasTapped(_ sender: UIButton) {
if self.onTapinviteContact != nil {
self.onTapinviteContact()
}
}
}
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let slidesView = SlidesView()
slidesView.onTapinviteContact = { () in
// do whatever you want to do on button tap
}
}
You can use the delegate pattern to tell the containing ViewController that the button was pressed and let it handle whatever is needed to do next, The view doesn't really need to know what happens.
A basic example:
protocol ButtonDelegate: class {
func buttonTapped()
}
class SomeView: UIView {
var delegate: ButtonDelegate?
#IBAction func buttonWasTapped(_ sender: UIButton) {
delegate?.buttonTapped()
}
}
class ViewController: UIViewController {
var someView: SomeView
override func viewDidLoad() {
someView = SomeView()
// add as subview, setup constraints etc
someView.delegate = self
}
}
extension ViewController: ButtonDelegate {
func buttonTapped() {
self.showSomeOtherViewController()
// or
let vc = NewViewController()
present(vc, animated: true)
}
}
I need a UIPageViewController to display as many UIViewControllers as the data available in an array.
Let's say I have an array of Int and for each of this Int I want a UIViewController to display one of them in a UILabel.
To accomplish this I've created in my Main storyboard both the UIPageViewController and UIViewController with its UILabel.
This is the code I've written for the UIPageViewController:
import UIKit
import RealmSwift
class PageViewController: UIPageViewController, UIPageViewControllerDataSource {
lazy var subViewControllers: [UIViewController] = {
return [
self.createNewVC(withName: "CounterViewController"),
self.createNewVC(withName: "CounterViewController"),
self.createNewVC(withName: "CounterViewController")
]
}()
var dataArray: [Int] = [30, 134, 345]
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
setViewControllers([subViewControllers[0]], direction: .forward, animated: true, completion: nil)
}
// Creates a new ViewController
func createNewVC(withName name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
// Returns the previous ViewController
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex: Int = subViewControllers.index(of: viewController) ?? 0
if (currentIndex <= 0) {
return nil
}
if let previous = subViewControllers[currentIndex - 1] as? CounterViewController {
previous.daysLeftLabel.text = String(dataArray[currentIndex])
return previous
} else {
return nil
}
}
// Returns the next ViewController
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex: Int = subViewControllers.index(of: viewController) ?? 0
if (currentIndex >= subViewControllers.count - 1) {
return nil
}
if let next = subViewControllers[currentIndex + 1] as? CounterViewController {
next.daysLeftLabel.text = "0"
return next
} else {
return nil
}
}
}
and this is the code for the UIViewController
import UIKit
class CounterViewController: UIViewController {
#IBOutlet weak var daysLeftLabel: UILabel!
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.
}
}
NB: "CounterViewController" is also the UIViewController StoryBoardID
The problem is that when I scroll for the first time to the right the app crashes at this line next.daysLeftLabel.text = "0" saying that it found a nil value while unwrapping Optional.
What am I doing wrong here?
Also I want to take the opportunity to ask something.
In this case, as you may have guessed, I am using a UIPageViewController which holds the same UIViewControllers but with different data displayed, which is the best way to accomplish this? Am I doing it correctly or there is an easier and cleaner way?
This line crashes
next.daysLeftLabel.text = "0"
because daysLeftLabel is nil until the VC loads , you need
class CounterViewController: UIViewController {
#IBOutlet weak var daysLeftLabel: UILabel!
var sendedText = ""
override func viewDidLoad() {
super.viewDidLoad()
daysLeftLabel.text = sendedText
// Do any additional setup after loading the view.
}
}
Then use
next.sendedText = "0"
I'm trying to use the change event on a UISwitch to move from the current ViewController to a new one. My UISwitch is registered in my CustomView for the Custom Cell in my UITableView. The action is registered an calls a class in my View Controller as below
import UIKit
public class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var operatedSwitch: UISwitch!
#IBAction func operatedSwitchChange() {
updateValveOps.valveUpdate()
}
When it gets to my class in my ViewController it calls a method in the main class which should move to my new ViewController as below
import UIKit
class updateValveOps {
class func valveUpdate() {
let valveOps = ValveOperationsController()
valveOps.ValveOpsUpdate()
}
}
class ValveOperationsController: UIViewController {
.
.
func ValveOpsUpdate() {
performSegueWithIdentifier("ValveOpsToUpdateSegue", sender: nil)
}
However, this causes a Sigabrt error. I've also tried pushing from the current view to the new View Controller but then for some reason it returns back to the calling View Controller! What am I doing wrong?
Try this in your #IBAction func operatedSwitchChange() { .. }
UIApplication.sharedApplication().keyWindow?.visibleViewController()?.performSegueWithIdentifier("ValveOpsToUpdateSegue", sender: nil)
And add this extension to your project (copy-paste this into new swift file):
public extension UIWindow {
func visibleViewController() -> UIViewController? {
if let rootViewController: UIViewController = self.rootViewController {
return UIWindow.getVisibleViewControllerFrom(rootViewController)
}
return nil
}
class func getVisibleViewControllerFrom(vc:UIViewController?) -> UIViewController? {
if vc == nil {
return nil
}
if let navigationController = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom( navigationController.visibleViewController)
} else if let tabBarController = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController)
} else {
if let presentedViewController = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController)
} else {
return vc
}
}
}
}
I managed to find a way to get this working by using Protocol. First I added a Protocol to my ValveOperationsController and referenced it as below
protocol CustomCellDelegator {
func callSegueFromCell()
}
class ValveOperationsController: UIViewController, CustomCellDelegator {
Then I added a delegate to my cell in cellForRowInIndexPath
cell.delegate = self
Then I added the method called in my Protocol in to my ViewController
func callSegueFromCell() {
performSegueWithIdentifier("ValveOpsToUpdateSegue", sender: nil )
}
Then going to my CustomTableViewCell I added my delegate
var delegate:CustomCellDelegator!
Then in the event called when the Switch changes I added the call to my Protocol
if(self.delegate != nil){ //Just to be safe.
self.delegate.callSegueFromCell()
}
When the event is called when the switch changes it calls the Protocol which passes it to my method and the Segue operates successfully