swift call a func from another viewcontroller - swift

I would like to call a func from another viewcontroller.
here with the code in pubListViewController: It is working fine.
override func viewDidAppear(_ animated: Bool) {
navigationBarTitleImage(imageTitle: "IconTitle")
}
func navigationBarTitleImage(imageTitle: String) {
// 1
// let nav = self.navigationController?.navigationBar
// 2
// nav?.barStyle = UIBarStyle.black
// nav?.tintColor = UIColor.yellow
// 3
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
imageView.contentMode = .scaleAspectFit
// 4
let image = UIImage(named: imageTitle)
imageView.image = image
// 5
navigationItem.titleView = imageView
}
now I try to call it in another viewcontroller as below, but it shows nothing.
override func viewDidAppear(_ animated: Bool) {
pubListViewController().navigationBarTitleImage(imageTitle: "addTitle")
}

When you're using notation like that pubListViewController() you call free empty initializer of pubListViewController which creates new instance of class pubListViewController, but you do already have one in your screens flow I bet, so all changes made by function you're calling later are being applied to invisible instance of pubListViewController.
To solve this problem you need a reference actually displaying instance of pubListViewController from your another viewcontroller
In another viewcontroller you can create a property of type pubListViewController, then before showing another viewcontroller set its property to self, and use that property from wherever you want in another viewcontroller.
class PubListViewController: UIViewController {
func prepareForSegue(/**/){ // actually do that in the place where you showing your another viewcontroller, I don't know if you're using segues or not
destinationViewController.parentPubListViewController = self
}
}
class AnotherViewController: UIViewController {
// declare property (weak and optional to avoid crashes or memory leaks if you forget to set that property from parent view controller
weak var parentPubListViewController: PubListViewController?
// use it anywhere you need
parentPubListViewController?.navigationBarTitleImage(imageTitle: "addTitle")
}

Related

How to present a ViewController as a Modal Sheet without the background shadow

Is there any way to present a ViewController as a Modal Sheet without the background shadow as shown in the first image below using swift. Is there an easy way or should we need to write custom UIPresentationController? [![The required output][1]][1]
[1]: https://i.stack.imgur.com/QAEEn.png![enter image description here](https://i.stack.imgur.com/q4JD5.jpg)
You can use a smaller size view controller as per your need. Firstly add a class.
class SmallSizePresentationController : UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
get {
guard let theView = containerView else {
return CGRect.zero
}
return CGRect(x: 0, y: theView.bounds.height * (281 / 896), width: theView.bounds.width, height: theView.bounds.height)
}
}
}
Then when you want to present this type of view controller just add the extension of your current view controller.
extension YourCurrentViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return SmallSizePresentationController(presentedViewController: presented, presenting: presenting)
}
}
Then present your new view controller like this
let VC = withoutShadowVC()
VC.transitioningDelegate = self
VC.modalPresentationStyle = .custom
self.present(VC, animated: true, completion: nil)
You can modify the view controller height.

Swift sub view hold old values (stacking new values on top of it)

[solved :)
by adding self.progressScrollView?.removeFromSuperview() ]
I am new to swift.
and having below issue :( please help me.
every time I come back to this main screen (using back button)
my scroll view's content are showing new values on top of old values
for example.
I have 4 items(a,b,c,d) in an array.
and main page show in random order
a
b
c
d
after that, i went to another page and came back to this screen.
it shows new values on top of old values
a -> b (this value is on top of 'a')
b -> d (this value is on top of 'b')
c -> c (this value is on top of 'c')
d -> a (this value is on top of 'd')
what I did?
I re-initialize my array in viewdidappear()
added below in gotopage()
dismiss(animated: true, completion: nil)
add removefromsuperview & view.addsubview in self.displayProgressBar()
(what #sandeep suggested.. but doesn't work)
my flow:
1.call displayprogressbar in viewdidload
self.displayProgressBar()
class ViewController: UIViewController ,ChartViewDelegate, UIScrollViewDelegate {
...
var progressScrollView: UIScrollView!
...
override func viewDidLoad() {
super.viewDidLoad()
self.getfirebasedata()
2.add progress bars and labels dynamically based on the list size.
func getfirebasedata(...){
...
self.displayProgressBar(...)
}
func displayProgressBar(...){
self.progressScrollView?.removeFromSuperview() //this is the fix
self.progressScrollView = UIScrollView()
self.progressScrollView.isScrollEnabled = true
self.progressScrollView.frame = CGRect(x: 0, y: 300,
width: self.view.frame.size.width, height: 200)
self.progressScrollView.contentSize = CGSize(width: self.view.frame.size.width,
height: self.view.frame.size.height)
let axisXstartPoint = ( self.view.frame.size.width - 200 ) / 2
let progressView = UIProgressView(progressViewStyle: .bar)
...
let label = UILabel()
label.frame = CGRect(x: axisXstartPoint, y: CGFloat(axisY-15), width: 200, height: 20)
label.textAlignment = .left
label.text = book.bookName
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tapBookName)
progressScrollView.addSubview(label)
progressScrollView.addSubview(progressView)
self.view.addSubview(progressScrollView)
}
when I go to another page I use this method
#objc #IBAction func goToAddPage(sender: UITapGestureRecognizer) {
let label = sender.view as? UIButton
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let addViewController = storyBoard.instantiateViewController(withIdentifier: "add") as! AddViewController
addViewController.modalPresentationStyle = .fullScreen
self.navigationController?.pushViewController(addViewController, animated: true)
dismiss(animated: true, completion: nil)
}
4.this is my storyboard
Not sure from where you call displayProgressBar as its not clear from your question, what you can do is add these statement as either 1st statment in displayProgressBar or as a statement before calling displayProgressBar
Approach 1:
self.progressScrollView?.removeFromSuperview() //? because for the first time implicit optional will be nil
self.progressScrollView = UIScrollView()
In ViewDidLoad you have self.view.removeFromSuperview() which I have no idea why? Makes no sense to me why would you try to remove ViewController's view? May be you were trying to remove subviews I assume, if yes, thats not the proper way to remove subviews, you iterate over subViews and call removeFromSuperview on each subView, but I dont think you need that, clearly from your code you hold a reference to progressScrollView somewhere in your code, just remove it from subview and recreate the progressScrollView as
progressScrollView = UIScrollView()
And if you are not holding a reference to progressScrollView and instead you were creating it as only local variable in displayProgressBar method, hold a reference to this view, to do that declare a instance variable var progressScrollView: UIScrollView!
Approach 2:
And if you dont prefer creating progressScrollView() again and again, you can always iterate over its subviews and remove them from superview one by one
for view in progressScrollView.subviews {
view.removeFromSuperview()
}
Make sure you run self.view.addSubview(progressScrollView) only once (like in ViewDidLoad or somewhere) if you decide to go with approach 2.
In either case you need to hold a reference to progressScrollView

Error setting values for one VC for another VC

I am trying to set the values of UILabel's on another view controller. However I keep getting a nil value when I am trying to set temp values for the other view controller's UILabels and such. This is the function that executes the segue:
func swipe(Sender: UISwipeGestureRecognizer!) {
print("swiped left")
let vc = self.storyboard!.instantiateViewControllerWithIdentifier("OnDeck") as! OnDeck
vc.firstBase = UIView(frame: CGRectMake(178, 136, 10, 10))
vc.firstBase.backgroundColor = UIColor.redColor()
vc.firstBase.layer.cornerRadius = vc.firstBase.frame.size.width/2
self.presentViewController(vc, animated: true, completion: nil)
}
I am getting the following error on the line vc.firstBase.backgroundColor = UIColor.redColor()
fatal error: unexpectedly found nil while unwrapping an Optional value
is there something I am missing when attempting to declare the temp values for the outlets?
You should not set those related UI elements, because the UI haven't loaded yet. Instead, pass a value and set it in viewDidLoad of destination controller
class OnDeck: UIViewController {
var color = UIColor.white()
override func viewDidLoad() {
...
firstBase = color
}
}
Then in your swipe method, pass the value only rather than modify the unloaded UILabel or UIView. Else, move the UI related code to the destination viewDidLoad() such as the cornerRadius, frame size, etc

pass custom parameter to uibutton #selector swift 3

I have a 2 classes where I am passing uistackviews from one class to other. I want the controls to be created in same stackview. Hence I am passing the view in all the render function parameters. I also want that view to be passed with action #selector of uibutton
class 1:
class ViewController: UIViewController {
func createbutton(parentview: UIStackView) {
let buttn = UIButton()
buttn.backgroundColor = .gray
buttn.setTitle("testttt", for: .normal)
buttn.frame.size.height = 30
buttn.frame.size.width = 40
buttn.addTarget(self, action: #selector(anotherbutton(parentview:)), for: .touchUpInside)
parentview.addArrangedSubview(buttn)
}
func anotherbutton(parentview: UIStackView) {
//another button here
}
func loadpage() {
print("loadpage")
}
}
Class 2:
class plugin : UIViewController {
let vw = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
let parentview = getparentnode()
vw.createbutton(parentview: parentview)
}
func getparentnode() -> UIStackView {
let parentnode = UIStackView()
parentnode.axis = UILayoutConstraintAxis.vertical
parentnode.distribution = UIStackViewDistribution.equalSpacing
parentnode.alignment = UIStackViewAlignment.center
parentnode.spacing = 16.0
parentnode.tag = 50
parentnode.translatesAutoresizingMaskIntoConstraints = false;
self.view.addSubview(parentnode)
//Constraints
parentnode.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
parentnode.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
return parentnode
}
}
but this throws an error unrecognized selector sent to instance 0x7b25e010'
How to pass the UIView in action selector parameter ? Thank you for any help
You can't. The only things that you can pass through a selector is:
Nothing
The object itself (in this case the button)
These scenarios would look like this:
button.addTarget(self, action: #selector(myFunc), ...) //no parameters
or
button.addTarget(self, action: #selector(myFunc(_:)) //passes itself (the button)
If you want to pass the value of a view to another ViewController I recommend using the prepareForSegue method. That is how you are supposed to pass data from ViewController to ViewController.
In terms of the rest of your code, I believe you are breaking the MVC design pattern by creating an instance of your class in another class (this line: let vw = ViewController()). First of all, this will create an entirely new instance if your ViewController, which isn't the same as the one running on your device. Second of all, this is bad practice. You should be allowing each viewController to manage itself and not have outwards interference from other viewControllers. Using prepareForSegue is an example of using the MVC design pattern effectively.
Hope this helped.

performSegueWithIdentifier not working for programmatically generated UIButtons (iOS/Swift)

Xcode 6, Swift, iOS8
I have a view controller that is populated by dynamically generated UIButtons. The number of buttons is dependent on a data feed, and is not static. I count the number of objects in the feed and generate a button for each object. Each button is supposed to segue into a details view that displays the information for its corresponding object.
In the Interface Builder I have created a segue between the two View Controllers and named it. I have not added an IBAction to initiate the segue as I cannot tie it to a specific button.
Inside the View Controller Class I execute the following:
#IBOutlet weak var localScrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
//Define the button dimensions
let buttonWidth:CGFloat = 200
let buttonHeight:CGFloat = 113
var xPos:CGFloat = 0
var scrollViewContent:CGFloat = 0
var thumbURL:String
//keeps track of the number of videos. The count is used to set a tag on the button to help identify it.
var vidCount:Int = 0
//loop through the array of recommended video objects
for index in recommended{
//Create a button for each object
var myButton = UIButton.buttonWithType(UIButtonType.System) as! UIButton
myButton.frame = CGRectMake(xPos, 0.0, buttonWidth, buttonHeight)
//For the button action I call the handleTap function detailed below
myButton.addTarget(self, action: "handleTap:", forControlEvents: .TouchUpInside)
myButton.tag = vidCount
//The image for the button is pulled from a CDN. This code sets the image in the button.
let curVal = index.thumbURL
if let url = NSURL(string: curVal) {
if let data = NSData(contentsOfURL: url){
myButton.setBackgroundImage(UIImage(data:data), forState: UIControlState.Normal)
}
}
//add the button to the scroll view
localScrollView.addSubview(myButton)
let spacer:CGFloat = 10
xPos+=buttonWidth + spacer
scrollViewContent += buttonWidth + spacer
localScrollView.contentSize = CGSize(width: scrollViewContent, height: buttonHeight)
vidCount += 1
}
}
//function to handle the tap action
func handleTap(sender:UIButton){
//Set the variable that will be passed to the next view controller
curRecVid = recommended[sender.tag]
//initiate the segue
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("toSingle", sender: self)
}
}
//prepare the data to be transferred to the next view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "toSingle") {
var targetViewController = segue.destinationViewController as! SingleViewController
targetViewController.targetVid = curRecVid
}
}
}
When I run the app in the simulator, it gets to the self.performSegueWithIdentifier("toSingle", sender: self) call and then terminates to the following uncaught exception: 'NSInvalidArgumentException', reason: '-[UILabel copyWithZone:]: unrecognized selector sent to instance 0x7fd468c8ddc0'
Any assistance in helping to track down the cause of this exception would be greatly appreciated.
It appears that the class file for the target View Controller had some sort of problem. After a comment from Epic Defeater, I wiped out its swift file, re-generated it and put in the exact same code (literally, copy/paste) and that did the trick.