Disable UIButton within IBAction Closure - Swift 4 - uibutton

When the user is playing my app, there are certain buttons that I do not want them to accidentally press. So, I would like to grey out and disable them when the app is in gameMode = 1. The following code disables the button when it is supposed to but then does not enable again it when I need it to (when it is in gameMode = 0). Also the button does not grey out.
#IBAction func menuButton(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myVC = storyboard.instantiateViewController(withIdentifier: "viewController")
if gameMode == 0 {
sender.isEnabled = true
self.present(myVC, animated: false, completion: nil)
} else if gameMode == 1 {
sender.isEnabled = false
}
}

#IBAction func menuButton(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myVC = storyboard.instantiateViewController(withIdentifier: "viewController")
if gameMode == 0 {
sender.isUserInteractionEnabled = true
self.present(myVC, animated: false, completion: nil)
} else if gameMode == 1 {
sender.isUserInteractionEnabled = false
}
}

Related

How do I get a back button in pushed ViewController (Xcode)

I have a viewController with a menu button set as navigationItem like this:
self.navigationItem.setLeftBarButton(leftButton, animated: false).
It brings up a drawer menu, which is fine in its context. But what if I want to push the viewController from elsewhere in the project where a back button is more appropriate instead of a menu button? That is, I just want to go back to the previous viewController instead of bringing up the menu. How do I get rid of the menu button and instead get the back button in that particular instance?
Here is the code for the class. As you can see, the navigationItem is set here.
override func viewDidLoad() {
super.viewDidLoad()
self.setUserInterfaceStyleLight()
loginStoryboard = UIStoryboard(name: "StoryboardOne", bundle: nil)
let leftButton = UIBarButtonItem(image: UIImage(named: "menu-icon"), style: .plain, target: self, action: #selector(self.leftSideMenuButtonPressed(_:)))
self.navigationItem.setLeftBarButton(leftButton, animated: false)
self.segmentController.tintColor = .white
if showContacts == true {
lastSegmentViewed = 1
}
Analytics.logEvent("contact_book", parameters: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidLogin(_:)), name: NSNotification.Name.UserDidLogin, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationAndViewWasResumed(_:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
self.view.backgroundColor = ThemTemplate.shared.getThem().secondaryColor
if #available(iOS 13.0, *) {
segmentController.backgroundColor = UIColor.gray
} else {
segmentController.backgroundColor = UIColor.clear
}
}
Is it possible to hide and disable the navigationItem from the previous viewController or change it to a back button?
This is how I push the viewController:
UIStoryboard *containerStoryboard = [UIStoryboard storyboardWithName:#"Login" bundle:nil];
BaseViewController *v = [containerStoryboard instantiateViewControllerWithIdentifier:#"base"];
[self.navigationController pushViewController:v animated:YES];
In the class before the push to the new viewController I tried something like:
v.navigationItem.leftBarButtonItem = nil;
v.navigationItem.hidesBackButton = NO;
...which did nothing (and yes, setting it to nil is dumb, just wanted to see if something happened and it did not).
The viewController I'm pushing from is not embedded in a navigationController, if that helps. I tried embedding it on one, but with the same results.
Left me know if there is anything else that can be helpful.
Add a Bool var in BaseViewController:
class BaseViewController: UIViewController {
var bShowMenu: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
self.setUserInterfaceStyleLight()
loginStoryboard = UIStoryboard(name: "StoryboardOne", bundle: nil)
if bShowMenu {
let leftButton = UIBarButtonItem(image: UIImage(named: "menu-icon"), style: .plain, target: self, action: #selector(self.leftSideMenuButtonPressed(_:)))
self.navigationItem.setLeftBarButton(leftButton, animated: false)
}
self.segmentController.tintColor = .white
if showContacts == true {
lastSegmentViewed = 1
}
Analytics.logEvent("contact_book", parameters: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidLogin(_:)), name: NSNotification.Name.UserDidLogin, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationAndViewWasResumed(_:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
self.view.backgroundColor = ThemTemplate.shared.getThem().secondaryColor
if #available(iOS 13.0, *) {
segmentController.backgroundColor = UIColor.gray
} else {
segmentController.backgroundColor = UIColor.clear
}
}
}
then, when you instantiate the controller, tell it whether or not to use the "menu" button:
UIStoryboard *containerStoryboard = [UIStoryboard storyboardWithName:#"Login" bundle:nil];
BaseViewController *v = [containerStoryboard instantiateViewControllerWithIdentifier:#"base"];
// if you do NOT want the menu button
v.bShowMenu = false
[self.navigationController pushViewController:v animated:YES];

how do I link a button to the webview of another controller having a link that changes based on the cell pressed?

I have a button that needs to open a web view on another controller in modally. currently the button executes the code you see below. I would like the button to open the webview directly. the link changes because it is an rss reader and therefore, based on the cell pressed, changes the link of the button that must open the webview.
this is the code that manages the controller that appears after the cell has been pressed
class FeedItemWebViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var textView: UITextView!
var link: String? = nil
var descriptionTesto:String? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.textView.text = descriptionTesto
}
#IBAction func apri(_ sender: UIBarButtonItem) {
guard let url = URL(string: self.link ?? "") else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
here is where it manages the controller where I entered the webview
class OpenSafariController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var myWebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func ritornaLista(_ sender: UIBarButtonItem) {
self.presentingViewController?.presentingViewController?.dismiss(animated: false, completion: nil)
}
}
delete the webview controller that is not needed. import SafariServices. here is the code to put on the button
#IBAction func apri(_ sender: UIBarButtonItem) {
let svc = SFSafariViewController(url: URL(string: self.link ?? "")!)
self.present(svc, animated: true, completion: nil)
}

How to refer UICollectionView from UICollectionViewCell?

Can I refer UICollectionView from UICollectionViewCell?
I finally want to refer ViewController from UICollectionViewCell.
Following codes are in my CustomCollectionViewCell Class.
I want to archive tweet support in this class.
#IBAction func tweetBtnTapped(_ sender: Any) {
let cvc = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
if let c = cvc {
c.setInitialText("test tweet from iOS App")
### How can I refer ViewController??
viewController = ???????
if let vc = viewController {
vc.present(c, animated: true, completion: nil)
}
}
}
Please provide the code sample or what you are trying to achieve. What I understand is that you want some action back in your view controller when you perform some action on your cell. Am I correct?
So As per the comments, Custom collection view cell :
class CustomeCell: UICollectionViewCell {
#IBAction func tweetButtonPressed() {
let cvc = SLComposeViewController(forServiceType:SLServiceTypeTwitter)
if let c = cvc {
c.setInitialText("test tweet from iOS App")
self.parentViewController?.presentViewController(c, animated: true, completion: nil)
}
}
}
Create a Extension.Swift file :
import UIKit
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.nextResponder()
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
This is working for me. All the best!

Multiple ios push notification causes causes app to crash. Terminated due to signal 6(SIGABRT)

These are image's before and after the app crashes,the console only show message Message from debugger: Terminated due to signal 6 [[My app launches (when not running) successfully from push notification and also i get the desired screen and result, but crashes when it receive's notification again from tap of notification while the app is active. When the app is launched normally from home screen notification function and action work fine in both while is app active and in background.I am posting my code below please help me.
When I'm launching my application from the notification received, app lunches successfully and goto respected window,now again when i receive notification while app is open then app crashes, means whenever my app is not running in background or foreground and i launch my app using notification.. and then again when i receive notification app crashes
My Code is below please help
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let barAppearace = UIBarButtonItem.appearance()
barAppearace.setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), forBarMetrics:UIBarMetrics.Default) //CODE TO REMOVETITLE OFACK BUTTON ITEM IN NAVIGATIION CONTROLLER
let notificationTypes : UIUserNotificationType = [.Alert, .Badge, .Sound]
let notificationSettings : UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
if let notification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? [String: AnyObject] {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AgentAllTab.readyNotificationAction), name: "AgentReadyNotification", object: nil)
let userInfo = launchOptions![UIApplicationLaunchOptionsRemoteNotificationKey] as? [String: AnyObject]
let aps = userInfo!["aps"] as! [String: AnyObject]
print("Remote noti data from didFinishLaunchingWithOptions \(aps)")
let data = aps["data"] as! [String: AnyObject]
let type = aps["type"] as! Int
print("notification TYPE \(type)")
switch type {
case 0 :
NSUserDefaults.standardUserDefaults().setObject(aps, forKey: "notificationlauch")
break
default:
break
}
return true
}
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings)
{
if notificationSettings.types != .None {
application.registerForRemoteNotifications()
}
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let tokenChars = UnsafePointer<CChar>(deviceToken.bytes)
var tokenString = ""
for i in 0..<deviceToken.length {
tokenString += String(format: "%02.2hhx", arguments: [tokenChars[i]])
}
NSUserDefaults.standardUserDefaults().setObject(tokenString, forKey: "DeviceToken")
NSUserDefaults.standardUserDefaults().synchronize()
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
print(error.localizedDescription)
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let aps = userInfo["aps"] as! [String: AnyObject]
let data = aps["data"] as! [String: AnyObject]
let type = aps["type"] as! Int
//Do something when app is active
if UIApplication.sharedApplication().applicationState == UIApplicationState.Active {
switch type {
case 0:
let custName = data["customerName"] as! String
let notification = CWStatusBarNotification()
notification.notificationStyle = .NavigationBarNotification
notification.notificationAnimationInStyle = .Top
notification.notificationLabelBackgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.8)
notification.notificationLabelTextColor = UIColor.whiteColor()
notification.notificationLabelFont = UIFont.boldSystemFontOfSize(15)
notification.displayNotificationWithMessage("\(custName) shorlisted you", forDuration: 3.0)
notification.notificationTappedClosure = {
NSNotificationCenter.defaultCenter().postNotificationName("AgentReadyNotification", object: self)
notification.dismissNotification()
}
break
default:
break
}
} else {
// Do something else when your app is in the background
switch type {
case 0 :
NSNotificationCenter.defaultCenter().postNotificationName("AgentReadyNotification", object: self)
break
default:
break
}
}
}
func applicationWillResignActive(application: UIApplication) {
print(" applicationWillResignActive")
}
func applicationDidEnterBackground(application: UIApplication) {
print(" applicationDidEnterBackgroundndddddddddddddd")
}
func applicationWillEnterForeground(application: UIApplication) {
print(" applicationWillEnterForeground")
}
func applicationDidBecomeActive(application: UIApplication) {
print(" applicationDidBecomeActive")
}
func applicationWillTerminate(application: UIApplication) {
print(" applicationWillTerminate")
}
}
//This code is from app delegate.swift
//Now code from the view controller where push is handled
import UIKit
class AgentAllTab: UITableViewController ,UIPopoverPresentationControllerDelegate ,AgentFilterDelegate {
var allQuoteId = String()
var filterTitle = "Market"
var quotes: [[String: AnyObject]] = [[:]] //VALUE FOR RESPONSE DICT
var newQuoteDict: Dictionary<String, String> = [String: String]() // DECLARING DICTIONARY FOR POST PARAMETERS
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AgentAllTab.readyNotificationAction), name: "AgentReadyNotification", object: nil)
if ( NSUserDefaults.standardUserDefaults().objectForKey("notificationlauch") != nil){
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AgentAllTab.readyNotificationAction), name: "NotificationLaunchReady", object: nil)
NSNotificationCenter.defaultCenter().postNotificationName("NotificationLaunchReady", object: self)
NSUserDefaults.standardUserDefaults().removeObjectForKey("notificationlauch")
NSUserDefaults.standardUserDefaults().synchronize()
return
}
executeFetch("/Market_all/")
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return quotes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if filterTitle == "Ready"{
cell = tableView.dequeueReusableCellWithIdentifier("AgentAllTabReadyCell", forIndexPath: indexPath) as! AgentAllTabCellSubClass
if(quotes[indexPath.row].count == 0){
//normal code to hide all content of cell
}
}else{
//code in case we get data
}else{
//Code for other filter title same as above
}
return cell
}
func executeFetch(apiurl : String){
//function to fetch data from server and feed into uitableviewcontroller
}
//Function to handle the push notification
func readyNotificationAction(notification:NSNotification) {
filterTitle = "Ready"
self.tabBarController?.tabBar.hidden = true
executeFetch("/agentReady/")
}
}
Just one line of removal of code made things work perfectly, i was adding the observer in didFinishLaunchingWithOptions method.
My corrected code is below,
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let barAppearace = UIBarButtonItem.appearance()
barAppearace.setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), forBarMetrics:UIBarMetrics.Default) //CODE TO REMOVETITLE OFACK BUTTON ITEM IN NAVIGATIION CONTROLLER
let notificationTypes : UIUserNotificationType = [.Alert, .Badge, .Sound]
let notificationSettings : UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
if let notification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? [String: AnyObject] {
//Removed this observer from here and placed it only where it was required
i.e.the desired viewcontroller
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AgentAllTab.readyNotificationAction), name: "AgentReadyNotification", object: nil)
// Also add deinit in the place where you place this observer and remove the observer from the viewcontroller so that observer is removed from notification center when app is terminated
let userInfo = launchOptions![UIApplicationLaunchOptionsRemoteNotificationKey] as? [String: AnyObject]
let aps = userInfo!["aps"] as! [String: AnyObject]
print("Remote noti data from didFinishLaunchingWithOptions \(aps)")
let data = aps["data"] as! [String: AnyObject]
let type = aps["type"] as! Int
print("notification TYPE \(type)")
switch type { case 0 :
NSUserDefaults.standardUserDefaults().setObject(aps, forKey: "notificationlauch")
break
default:
break
}
return true
}
//The rest of things remained same, Thank you #David V

Hiding the master view controller with UISplitViewController in iOS8

I have an iOS7 application, which was based on the Xcode master-detail template, that I am porting to iOS8. One area that has changed a lot is the UISplitViewController.
When in portrait mode, if the user taps on the detail view controller, the master view controller is dismissed:
I would also like to be able to programmatically hide the master view controller if the user taps on a row.
In iOS 7, the master view controller was displayed as a pop-over, and could be hidden as follows:
[self.masterPopoverController dismissPopoverAnimated:YES];
With iOS 8, the master is no longer a popover, so the above technique will not work.
I've tried to dismiss the master view controller:
self.dismissViewControllerAnimated(true, completion: nil)
Or tell the split view controller to display the details view controller:
self.splitViewController?.showDetailViewController(bookViewController!, sender: self)
But nothing has worked so far. Any ideas?
Extend the UISplitViewController as follows:
extension UISplitViewController {
func toggleMasterView() {
let barButtonItem = self.displayModeButtonItem()
UIApplication.sharedApplication().sendAction(barButtonItem.action, to: barButtonItem.target, from: nil, forEvent: nil)
}
}
In didSelectRowAtIndexPath or prepareForSegue, do the following:
self.splitViewController?.toggleMasterView()
This will smoothly slide the master view out of the way.
I got the idea of using the displayModeButtonItem() from this post and I am simulating a tap on it per this post.
I am not really happy with this solution, since it seems like a hack. But it works well and there seems to be no alternative yet.
Use preferredDisplayMode. In didSelectRowAtIndexPath or prepareForSegue:
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
self.splitViewController?.preferredDisplayMode = .Automatic
Unfortunately the master view abruptly disappears instead of sliding away, despite the documentation stating:
If changing the value of this property leads to an actual change in
the current display mode, the split view controller animates the
resulting change.
Hopefully there is a better way to do this that actually animates the change.
The code below hides the master view with animation
UIView.animateWithDuration(0.5) { () -> Void in
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
I was able to have the desired behavior in a Xcode 6.3 Master-Detail Application (universal) project by adding the following code in the MasterViewController's - prepareForSegue:sender: method:
if view.traitCollection.userInterfaceIdiom == .Pad && splitViewController?.displayMode == .PrimaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
The complete - prepareForSegue:sender: implementation should look like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
if view.traitCollection.userInterfaceIdiom == .Pad && splitViewController?.displayMode == .PrimaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
}
}
}
Using traitCollection may also be an alternative/supplement to displayMode in some projects. For example, the following code also works for a Xcode 6.3 Master-Detail Application (universal) project:
let traits = view.traitCollection
if traits.userInterfaceIdiom == .Pad && traits.horizontalSizeClass == .Regular {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
Swift 4 update:
Insert it into prepare(for segue: ...
if splitViewController?.displayMode == .primaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .primaryHidden
}
let completion: (Bool) -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .automatic
}
UIView.animate(withDuration: 0.3, animations: animations, completion: completion)
}
Modifying the answers above this is all I needed in a method of my detail view controller that configured the view:
[self.splitViewController setPreferredDisplayMode:UISplitViewControllerDisplayModePrimaryHidden];
Of course it lacks the grace of animation.
try
let svc = self.splitViewController
svc.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden
My solution in the Swift 1.2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
var screen = UIScreen.mainScreen().currentMode?.size.height
if (UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad) || screen >= 2000 && UIDevice.currentDevice().orientation.isLandscape == true && (UIDevice.currentDevice().userInterfaceIdiom == .Phone){
performSegueWithIdentifier("showDetailParse", sender: nil)
self.splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden
} else if (UIDevice.currentDevice().userInterfaceIdiom == .Phone) {
performSegueWithIdentifier("showParse", sender: nil)
}
}
for iPad add Menu button like this
UIBarButtonItem *menuButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"burger_menu"]
style:UIBarButtonItemStylePlain
target:self.splitViewController.displayModeButtonItem.target
action:self.splitViewController.displayModeButtonItem.action];
[self.navigationItem setLeftBarButtonItem:menuButtonItem];
This work great with both landscape and portrait mode.
To programmatically close the popover vc you just need to force the button action like this
[self.splitViewController.displayModeButtonItem.target performSelector:appDelegate.splitViewController.displayModeButtonItem.action];
Very similar to the method by phatmann, but a bit simpler in Swift 5. And it's not technically a 'hack', as it is what the iOS doc suggested.
In your prepareForSegue or other methods that handle touches, in
let barButton = self.splitViewController?.displayModeButtonItem
_ = barButton?.target?.perform(barButton?.action)
According to Apple, the splitViewController's displayModeButtonItem is set up for you to display the master view controller in a way that suits your device orientation. That is, .preferHidden in portrait mode.
All there's to do is to press the button, programatically. Or you can put it in an extension to UISplitViewController, like phatmann did.