UIWebView inside UIPageViewController crashes app - swift

I have an app which needs a fix for the crash details below. Flow goes like this , user launches app, news articles are displayed on uitableview each cell has one article details. When user clicks on any cell we set all articles on uipageviewcontroller using pageViewController.setViewControllers([newsDetails!], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
If the articles are displayed only on text view, swiping controller to left and right works perfectly. But if there are 2 consecutive web view articles, swipe right (from web view article move to next web view article) and then left (back to web view article) the app crashes with this crash log.
Just to experiment I put my data into textview instead of uiwebview and it still crashed with same crash log, this articles use iframe tag in it. Code is here :
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
//----------------------------------------------------------------------
// Returning the article's detail view controller for the previous index
print("VIEW CONTROLLER AFTER")
return articleDetailViewController(for: detailPageIndex()-1,fromDetail:false)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
//------------------------------------------------------------------
// Returning the article's detail view controller for the next index
print("VIEW CONTROLLER BEFORE")
return articleDetailViewController(for: detailPageIndex()+1,fromDetail:false)
}
// We create a new instance of detail View Controller when ever a new VC is requested
func articleDetailViewController(for index: Int,fromDetail :Bool) -> MISNewsDetailViewController? {
let allNews:Bool
if(UserDefaults.standard.object(forKey: "IsAllNewsSelected") == nil || (UserDefaults.standard.object(forKey: "IsAllNewsSelected") as! String) == "YES"){
allNews = true
}else{
allNews = false
}
//--------------------------------------------
// If the index is out of bound, nil is return
if(allNews){
if (index == -1 || index >= (allArticles.count)) {
return nil
}
}else{
if (index == -1 || index >= (myArticles.count)) {
return nil
}
}
//---------------------------------------------------
// Getting the article object for the requested index
let article: MISArticle?
let detailViewController = storyboard?.instantiateViewController(withIdentifier: "MISNewsDetailViewController") as? MISNewsDetailViewController
let myAppDelegate = (UIApplication.shared.delegate as! AppDelegate)
if(myAppDelegate.isLaunchedFromWidget){
article = self.getTheWidgetSelectedArticleBaasedOnGlobalId();
}else{
if(allNews){
article = allArticles[index]
detailViewController?.isAllNewsPost = true
}else{
article = myArticles[index]
detailViewController?.isAllNewsPost = false
}
}
detailViewController?.mPost = article
detailViewController?.isRefreshPost = fromDetail
return detailViewController
}

Related

How to i fix the "Cannot find 'present' and 'dismiss' in scope" error in swift

I am trying to allow a user to pick an image and set it as there banner on there profile page but when i write my code i keep getting the error "Cannot find 'present' in scope" when trying to present the imagePicker and i also get "Cannot find 'dismiss' in scope" when trying to dismiss the imagePicker
here is my code for the above:
//this function gets called when the user tapes on the banner to change the image
class ProfileHeader: UICollectionViewCell, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#objc func handleBannerTapped() {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.allowsEditing = true
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let bannerPicture = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else {
imageSelected = true
return
}
imageSelected = true
profileImage.layer.masksToBounds = true
profileBanner.setImage(bannerPicture.withRenderingMode(.alwaysOriginal), for: .normal)
dismiss(animated: true, completion: nil)
}
}
I have used the same function in my sign up page where the user chooses a profile picture and it is working fine so i am confused as to why it doesn't work now for the banner
You can only present and dismiss on a view controller. But ProfileHeader is not a view controller. It's a cell.
So where's the view controller? It's up the responder chain from the cell. So walk up the responder chain until you come to the view controller, and present and dismiss on that.
Here's a utility method that will help you:
extension UIResponder {
func next<T:UIResponder>(ofType: T.Type) -> T? {
let r = self.next
if let r = r as? T ?? r?.next(ofType: T.self) {
return r
} else {
return nil
}
}
}
So now you can say:
if let vc = self.next(ofType: UIViewController.self) {
vc.present(imagePicker, animated: true, completion: nil)
}
And so on.
However, although that will cause your code to compile, and perhaps even appear to work correctly, I think your approach here is completely wrong-headed:
You should not be doing this work in the cell in the first place.
You should not be having a cell telling a view controller what to present / dismiss.
You should not be wantonly modifying the content of the cell.
A cell is a transient reusable object, so you cannot even really guarantee that the cell will still be there (or will still occupy the same row of the table) when the image has been chosen.
Instead, the cell should just tell the view controller, hey, the user wants to pick a photo, and stand back and let the view controller do the work. The work should consist of modifying the data model (and then reloading the cell).

Is it possible to observe changes in presentingViewController?

Is there any equivalent in Swift to RACObserve(self, presentingViewController)?
Or any other why to imitate this behaviour?
My issue is that I want to be notified whenever a view controller is "hidden" by another view controller. In objc what I'd do is to check if self.presentingViewController is nil.
Note that in this scenario there's no knowledge of which view controller is presented, so it's impossible to notify from within its viewDidAppear/viewDidDisappear.
As I understand your question: you need to to know which view controller is presented now and you need notification inviewDidAppear/viewDidDisappear.
So we can get this in several way.
The simple way is:
Get information of which is the top ViewController right now.
2.Call this method in your viewDidAppear/viewDidDisappear
Like this :
Get Which is The Top ViewController
func getTopViewController() -> UIViewController? {
if var topVC = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topVC.presentedViewController {
topVC = presentedViewController
return topVC
}
return topVC
}
return nil
}
Call in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
if let top = getTopViewController() {
print("topView Controller name \(top.title)")
top.view.backgroundColor = .red
}
}
Hope it will help you !

Adding a new page into PageViewController

I am a Swift noob and am making a simple weather app. I used the Page-Based Application template.
The problem I have is the following:
When the user adds a city I call addCity and successfully append the new city name to my cities array. When I print that array in that function, it shows the new city at the end.
However, the function viewControllerAtIndex that creates a new page seems to use the old version of that array, without the new city appended. When I print the cities array, it is missing the new city name. Therefore, when the users swipes there is won't be a new page for the new city rendered. The user has to restart the app in order for the new city to show up.
I created a screen capture video to illustrate the problem.
https://youtu.be/DbMqgJ0lONk
(the cities array should also show "London", I think I just didn't restart the app)
I would appreciate any help here!
import UIKit
class ModelController: NSObject, UIPageViewControllerDataSource {
var rootViewController = RootViewController()
var cities = [""]
let defaults = UserDefaults.standard
override init() {
super.init()
self.cities = self.defaults.stringArray(forKey: "SavedStringArray") ?? [String]()
if self.cities == [""] || self.cities.count == 0 {
self.cities = ["Current Location"]
}
}
func addCity(name:String) {
self.cities.append(name)
self.defaults.set(self.cities, forKey: "SavedStringArray")
print ("cities from addCity:")
print (self.cities)
}
func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> DataViewController? {
// Return the data view controller for the given index.
if (self.cities.count == 0) || (index >= self.cities.count) {
return nil
}
// Create a new view controller and pass suitable data.
let dataViewController = storyboard.instantiateViewController(withIdentifier: "DataViewController") as! DataViewController
//get city name
dataViewController.dataObject = self.cities[index]
print ("cities in viewControllerAtIndex:")
print (self.cities)
return dataViewController
}
func indexOfViewController(_ viewController: DataViewController) -> Int {
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
return self.cities.index(of: viewController.dataObject) ?? NSNotFound
}
// MARK: - Page View Controller Data Source
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if (index == 0) || (index == NSNotFound) {
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if index == NSNotFound {
return nil
}
index += 1
if index == self.cities.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
}
The problem is, that you use two different instances of ModelController. One for the RootViewController and another in the TableViewController. They don't know each other.
A couple of options to address the problem:
1.) Hand over the same instance of ModelController to TableViewController when you segue into it.
E.g. by adding this prepare(for segue:) method toRootViewController`
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "Locations") {
let destVC: TableViewController = segue.destination as! TableViewController;
destVC.modelViewController = self.modelController;
}
}
This will ensure that the same ModelController will be handed over.
Note: you have to add this identifier ("Locations") to the segue going from Edit-button to the TableViewController scene.
Note 2: this code is untested and doesn't probably even compile. I'm not having Xcode available right now.
2.) Ensure that there cannot be more than one instance of ModelController (Singleton)
One random web link: https://thatthinginswift.com/singletons/

How to load tab bar controller from login view - Swift

So I made a view controller for login user with Parse. when the user login by entering the log in button, tab bar view controller load. The issue is if the user open the app and he already login, I don't want him to go without entering his login. I want the signing view controller send him to tab bar view controller.
The initial view controller is Tab bar view controller, I tried many ways to deal with this problem, but nothing seems good.
waiting for all you thoughts.
There are probably a lot of different ways you can achieve this, but one way I've dealt with this type of situation in the past is to create a custom container ViewController. This VC doesn't have a UIView of its own, but instead presents other ViewControllers as its "Views".
Here's a link to some Apple documentation that describes creating a controller of this type.
One of the benefits of this architecture is that I always have a VC in-scope that can take action to reroute my user based on events related to their account status (not logged in, offline limit reached, account suspended, first-time user, logout, etc.).
EDIT: Example Container Controller
Here's an example of how to implement a custom container controller. I'm sure there are some better ways to do a few of the features shown here, but hopefully this gives you a good start.
import UIKit
class ApplicationContainerController: UIViewController {
//MARK: - View Controller Routing Properties
private var _currentClientView:UIView? = nil
private var _currentViewController: UIViewController? = nil
//MARK: - Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//MARK: - UIViewController Members
override func viewDidLoad() {
super.viewDidLoad()
}
override var shouldAutomaticallyForwardAppearanceMethods : Bool {
return true
}
override func viewWillAppear(_ animated: Bool) {
//Get the user and route to the appropriate VC
let yourUserObject: AnyObject? = YourDataSource.TryToGetAUser()
DispatchQueue.main.async {
self.routeUser(yourUserObject)
}
}
//MARK: - Your Custom Routing Logic
func routeUser(_ yourUserObject: AnyObject?) {
//make sure we have an existing user, or else we send them to login
guard let user = yourUserObject
else {
self.displayContentController(YourLoginViewController())
return
}
var destinationViewController:UIViewController
//please use an enum or something (instead of strings) in your code
switch user.signInStatus {
case "loginActive":
let mainMenuViewController = YourMainMenuViewController()
mainMenuViewController.user = user
destinationViewController = mainMenuViewController
case "firstLogin":
let firstLoginViewController = YourFirstLoginViewController()
firstLoginViewController.user = user
destinationViewController = firstLoginViewController
case "giveUsMoney":
let weWantMoneyViewController = YourOtherViewController()
weWantMoneyViewController.user = user
destinationViewController = weWantMoneyViewController
default:
//loginFailed or some other status we don't know how to handle
destinationViewController = YourLoginViewController()
}
if let activeViewController = self._currentViewController,
type(of: activeViewController) !== type(of: destinationViewController) {
//we have an active viewController that is not the destination, cycle
self.cycleFromCurrentViewControllerToViewController(destinationViewController)
} else {
//no active viewControllers
self.displayContentController(destinationViewController)
}
}
//MARK: - Custom Content Controller Routing Methods
private func frameForContentController() -> CGRect {
return self.view.frame
}
private func newViewStartFrame() -> CGRect {
return CGRect(x: self.view.frame.origin.x,
y: self.view.frame.origin.y + self.view.frame.size.width,
width: self.view.frame.size.width,
height: self.view.frame.size.height)
}
private func oldViewEndFrame() -> CGRect {
return CGRect(x: self.view.frame.origin.x,
y: self.view.frame.origin.y - self.view.frame.size.width,
width: self.view.frame.size.width,
height: self.view.frame.size.height)
}
/**
Transitions viewControllers, adds-to/removes-from context, and animates views on/off screen.
*/
private func cycleFromCurrentViewControllerToViewController(_ newViewController: UIViewController) {
if let currentViewController = self._currentViewController {
self.cycleFromViewController(currentViewController, toViewController: newViewController)
}
}
private func cycleFromViewController(_ oldViewController:UIViewController, toViewController newViewController:UIViewController) {
let endFrame = self.oldViewEndFrame()
oldViewController.willMove(toParentViewController: nil)
self.addChildViewController(newViewController)
newViewController.view.frame = self.newViewStartFrame()
self.transition(from: oldViewController, to: newViewController,
duration: 0.5,
options: [],
animations: { () -> Void in
newViewController.view.frame = oldViewController.view.frame
oldViewController.view.frame = endFrame
}) { (finished:Bool) -> Void in
self.hideContentController(oldViewController)
self.displayContentController(newViewController)
}
}
/**
Adds a view controller to the hierarchy and displays its view
*/
private func displayContentController(_ contentController: UIViewController) {
self.addChildViewController(contentController)
contentController.view.frame = self.frameForContentController()
self._currentClientView = contentController.view
self.view.addSubview(self._currentClientView!)
self._currentViewController = contentController
contentController.didMove(toParentViewController: self)
}
/**
Removes a previously added view controller from the hierarchy
*/
private func hideContentController(_ contentController: UIViewController) {
contentController.willMove(toParentViewController: nil)
if (self._currentViewController == contentController) {
self._currentViewController = nil
}
contentController.view.removeFromSuperview()
contentController.removeFromParentViewController()
}
}

UIApplicationShortcutItem in Swift to support 3D touch

How to launch and navigate to a particular view controller using short cut items?
I am using UINavigationController in my app. The issue is when I click on the short cut items, I have to navigate directly to last view controller. In the last view controller, I will have Back button to navigate back to previous screens. Is this possible?
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: Bool -> Void) {
let handledShortCutItem = handleShortCutItem(shortcutItem)
completionHandler(handledShortCutItem)
}
func handleShortCutItem(shortcutItem: UIApplicationShortcutItem) -> Bool {
var handled = false
// Verify that the provided `shortcutItem`'s `type` is one handled by the application.
guard ShortcutIdentifier(fullType: shortcutItem.type) != nil else { return false }
guard let shortCutType = shortcutItem.type as String? else { return false }
switch (shortCutType) {
case ShortcutIdentifier.LastViewController.type:
print("Inside Agent **********")
**// Navigate to view controller**
handled = true
break
default:
break
}