PresentViewController presents compile error in static/class functions - ios

I have a menu bar which I want to use on every page(UIViewController) in an app. So I figured I'd just write it once and use it on all the pages, instead of reproducing the same code on every page. To go about that, I created a new class which extends UIViewController(named it MenuBarViewController), with its own XIB file, created a function within that class which contains the code for generating the menubar, and just make a call to that function in any UIViewController where I want to place the menubar. Seems to work pretty seemlessly, but I run into problems when I tried to create some helper functions within the MenuBarViewController class, which trigger a navigation to another view, when one of the menu icons is tapped. I will include a simplified version of the code I'm working with below which illustrates the entirety of the problem.
class MenuBarViewController: UIViewController {
func menuBar(viewController:UIViewController){
let menuBar = UIView(frame:CGRectMake(0,0,100,20 ))
menuBar.backgroundColor = UIColor.blackColor()
viewController.view.addSubview(menuBar) //Add the newly created view to the main view(self)
//Add an icon view to the menubar
let iconView = UIImageView(frame: CGRectMake(5,0,5,5))
menuBar.addSubview(iconView)
//Add a gesture recognizer for the icon view
iconView.userInteractionEnabled = true
let tapHomeButton = UITapGestureRecognizer(target: self, action:#selector(MenuBarViewController.goToHomeView(_:)))
iconView.addGestureRecognizer(tapHomeButton)
//Add an icon to the icon view
let iconImageName = UIImage(named:"home.png")!
iconView.image = iconImageName
}
//Helper function to handle the gesture recognizer, helps transition to another view
func goToHomeView(sender: UIImageView!) {
appData.lastView = "GameViewController"
let settingsVC = SettingsViewController(nibName: "SettingsViewController", bundle: nil)
presentViewController(settingsVC, animated: true, completion: nil)
}
}
Now when I tap on the icon, there is no response. So I figured I'd declare the class functions(menuBar and goToHomeView) as either "class" or "static" funcs, and then call the menu bar directly with the class name. But then I get this compile error "extra argument 'animated' in call" on the this line:
presentViewController(settingsVC, animated: true, completion: nil)
So something must be wrong with the presentViewController method when it is declared within a static or class function because when I have the goToHomeView function just do something as simple as printing some text, it works fine. So I'm at a total loss at this point. What to do? Am I going about this the completely wrong way?

Related

Add and Edit Data using Single View Controller?

I am a beginner, I am trying to add and edit data using a single view controller, process works fine, but when I try to click add button, view controller is pushed twice.
When I try to Open Click
But When I click Back in the navigation bar, the Screen appears with my designs.
Code For Launching Add Screen
let MenuAdd = self.storyboard?.instantiateViewController(identifier: "MenuEdit") as! AdminMenuVC
MenuAdd.IsEdit = true
self.navigationController?.pushViewController(MenuAdd, animated: true)
Code For Launching Edit Screen
let MenuEdit = self.storyboard?.instantiateViewController(identifier: "MenuEdit") as! AdminMenuVC
MenuEdit.IsEdit = true
self.navigationController?.pushViewController(MenuEdit, animated: true)
There are possible explanation for this:
You also have a storyboard segue going from the add button to the AdminMenuVC- if this is the case, just delete it (you are already pushing it from code)
You call from the first VC both self.navigationController?.pushViewController(MenuAdd, animated: true) and self.navigationController?.pushViewController(MenuEdit, animated: true) - they both refer to the same VC (AdminMenuVC). I cannot say if this is the case as you haven't posted the code. Be careful to check that you are not calling the two lines of code at the same time
Check button action you may be setting action and touch up inside actions on same button. You need to remove one action from reference inspector

Recreate iOS 13' share sheet modal in swift (not the share sheet itself, but the way it's presented)

is there a way to easily recreate the modal presentation style of ios 13' new share sheet? (At first, it's only presented halfway and you can swipe up to make it a "full" modal sheet) I can do it using a completely custom presentation and stuff but is there a "native" api for this behavior so that you don't have to use custom code?
Thanks!
Here's what I've tried. I've created a new ViewController class extending UIActivityViewController. And in the viewDidLoad function, I removed all the child views from the controller and added my viewController as a child to it. It seems to be working fine. Although, it is more of a work around it is still sufficient enough for the requirement it seems. Code snippet is as follows. Give it a try.
import UIKit
class CustomActivityViewController: UIActivityViewController {
private let controller: UIViewController!
required init(controller: UIViewController) {
self.controller = controller
super.init(activityItems: [], applicationActivities: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let subViews = self.view.subviews
for view in subViews {
view.removeFromSuperview()
}
self.addChild(controller)
self.view.addSubview(controller.view)
}
}
Above is the CustomActivityViewController. And you can add your viewController into it as follows.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YourViewController")
let activityViewController = CustomActivityViewController(controller: controller)
self.present(activityViewController, animated: true, completion: nil)
If you are interested in private API, this is how Apple does it:
There is a class named _UISheetDetent. With this class, you can either create system defined "detents"—medium and large—or provide your own block-based logic (input param is the presentation controller's container view, and the return value is a double—the percent to open the sheet).
You create an array of these "detents" and provide them to the sheet presentation controller using the _setDetents: method (or setValue:forKey:). To replicate the share sheet behavior, you need an array with two "detents": medium and large.
There is also the _indexOfLastUndimmedDetent property, which controls which "detent" starts the dimming process.
I'm not sure why Apple hasn't exposed this as public API. It is concise, simple and works well.
You should probably not use this API, but if you decide to use it, it should be very easy to hide it. In any case, make sure to open a Feedback with Apple to expose this API in a future version of the SDK.

Call a function in a detail PageViewController from the ViewController that loads it

I have a PageViewController that loads different photo albums, each foto album is loaded from a different view controller (album1ViewController... album3ViewController), as shown below.
Storyboard
Each view controller (album1... album3) is loaded from a navigation view controller so they load a nav bar. I wanted to add a save to camera roll button to the navigation bar so I put this code in the ItemVC (the one that loads the image) and also in the PageViewController, but it didn't activate the save button:
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveToCameraRoll))
So I figured that the only way to add the button programatically was inside the albumsVCs. (I try adding the bar in the storyboard in ItemsVC but it didn't show properly). But now I want to call the function SaveToCameraRoll in a button called by album#ViewController with the image in ItemsViewController. I've tryied first leaving the function inside ItemsVC and trying to call it from albumVC like this:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ItemController") as! ItemViewController
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Guardar en Fotos", style: .plain, target: self, action: #selector(vc.saveToCameraRoll))
But the app crashes. with this error:
unrecognized selector sent to instance 0x7fca05d32e60
Then I tried in albumVC adding the SaveToCameraRoll function like this:
#objc func saveToCameraRoll() {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ItemController") as! ItemViewController
let imageData = UIImagePNGRepresentation(vc.contentImageView.image!)
let compresedImage = UIImage(data: imageData!)
UIImageWriteToSavedPhotosAlbum(compresedImage!, nil, nil, nil)
}
In the second case I algo get an error and a crash, in the line:
let imageData = UIImagePNGRepresentation(vc.contentImageView.image!)
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
Is there a way to add this function? or is there a simpler way to archive this?
Thanks!
I would suggest setting up your album view controllers to have a delegate property.
Define a protocol ParentProtocol that implements your saveToAlbum method.
Make you ItemController conform to that protocol.
In your page view controller, as you load pages, set their delegate property to self.
In your Album view controllers, create an IBAction method in the Album view controller that sends a saveToAlbum method to the delegate.
That's a pretty common pattern for forwarding messages from child view controllers up to the parent.
I created a project on Github recently called TabBarControllers that does something very similar with tab bar controllers (A tab bar controller manages 2 or more child view controllers as tabs.) in that case the child view controllers are loaded from the storyboard at startup through a segue. In your case you'll have to write code that sets the delegate as it loads the album view controllers, but the idea is very similar.

Presenting View Controller loses subviews when dismissing presented VC

I'm having some trouble playing around with two viewcontrollers that interact in a straightforward manner:
The homeViewController shows a to-do list, with an addTask button.
The addTask button will launch an additional viewController that acts as a "form" for the user to fill.
However, upon calling
self.dismissViewControllerAnimated(true, completion: nil);
inside the presented view controller I return to my home page, but it's blank white and it seems nothing can be seen except the highest-level view on the storyboard can be seen (i.e. the one that covers the entire screen).
All of my views, scenes, etc. were set up with autolayout in storyboard. I've looked around on Stack Overflow, which lead to me playing around with the auto-resizing subview parameter i.e.:
self.view.autoresizesSubviews = false;
to no avail. I'm either fixing the auto-resizing parameter wrong (in the wrong view of interest, or simply setting it wrong), or having some other problem.
Thanks in advance
edit:
I present the VC as follows:
func initAddNewTaskController(){
let addNewTaskVC = self.storyboard?.instantiateViewControllerWithIdentifier("AddNewTaskViewController") as! AddNewTaskViewController;
self.presentViewController(addNewTaskVC, animated: true, completion: nil);
}
edit2:
While I accept that using delegates or unwinding segue can indeed circumvent the problem I'm encountering (as campbell_souped suggests), I still don't understand what's fundamentally happening when I dismiss my view controller that causes a blank screen.
I understand that calling dismissViewControllerAnimated is passed onto the presenting view controller (in this case my homeViewController). Since I don't need to do any pre or post-dismissal configurations, the use of a delegate is (in my opinion) unnecessary here.
My current thought is that for some reason, when I invoke
dismissViewControllerAnimated(true, completion:nil);
in my addNewTaskViewController, it is actually releasing my homeViewController. I'm hoping someone can enlighten me regarding what it is exactly that I'm not understanding about how view controllers are presented/dismissed.
In a situation like this, I usually take one of two routes. Either set up a delegate on AddNewTaskViewController, or use an unwind segue.
With the delegate approach, set up a protocol:
protocol AddNewTaskViewControllerDelegate {
func didDismissNewTaskViewControllerWithSuccess(success: Bool)
}
Add an optional property that represents the delegate in your AddNewTaskViewController
var delegate: AddNewTaskViewControllerDelegate?
Then invoke the didDismissNewTaskViewControllerWithSuccess whenever you are about to dismiss AddNewTaskViewController:
If the record was added successfully:
self.delegate?.didDismissNewTaskViewControllerWithSuccess(true)
self.dismissViewControllerAnimated(true, completion: nil);
Or if there was a cancelation/ failure:
self.delegate?.didDismissNewTaskViewControllerWithSuccess(false)
self.dismissViewControllerAnimated(true, completion: nil);
Finally, set yourself as the delegate, modifying your previous snippet:
func initAddNewTaskController(){
let addNewTaskVC = self.storyboard?.instantiateViewControllerWithIdentifier("AddNewTaskViewController") as! AddNewTaskViewController;
self.presentViewController(addNewTaskVC, animated: true, completion: nil);
}
to this:
func initAddNewTaskController() {
guard let addNewTaskVC = self.storyboard?.instantiateViewControllerWithIdentifier("AddNewTaskViewController") as AddNewTaskViewController else { return }
addNewTaskVC.delegate = self
self.presentViewController(addNewTaskVC, animated: true, completion: nil);
}
...
}
// MARK: AddNewTaskViewControllerDelegate
extension homeViewController: AddNewTaskViewControllerDelegate {
func didDismissNewTaskViewControllerWithSuccess(success: Bool) {
if success {
self.tableView.reloadData()
}
}
}
[ Where the extension is outside of your homeViewController class ]
With the unwind segue approach, take a look at this Ray Wenderlich example:
http://www.raywenderlich.com/113394/storyboards-tutorial-in-ios-9-part-2
This approach involves Ctrl-dragging from your IBAction to the exit object above the view controller and then picking the correct action name from the popup menu

Why does initializing a view controller with nibName not set the new instance as file owner?

I am trying to display one view or another view inside the detail view of a master/detail based on a conditional.
These views will contain outlets and elements, so I would like to have view controllers for each that I can play with.
So I created a new UIViewController called AddPhotoViewController. This is how I add AddPhotoViewController.xib inside DetailViewController:
let photoVC = AddPhotoViewController(nibName: "AddPhotoViewController", bundle: nil)
let photoView = photoVC.view
photoVC.delegate = self
photoView.autoresizingMask = UIViewAutoresizing.FlexibleWidth
photoView.frame = area.bounds
area.addSubview(photoView)
The view loads properly in the detail view and looks like this:
AddPhotoViewController.xib's owner class has been set as well here:
When I tap the button, though the action is set properly in AddPhotoViewController to print a message, Xcode crashes.
Am I doing this correctly? Is there a more common practice for loading view X or view Y inside a view controller depending on user data?
Button action:
#IBAction func ButtonPressed(sender: AnyObject) {
println("worked!")
}
Button connection:
Console output:
I think you need to add the viewController:
addChildViewController(PhotoVC)
//and then
PhotoVC.didMoveToParentViewController(self)

Resources