Application stops after swiping - ios

I have an app reader like Apple Books. And I have a problem. I have NavigationViewController next FirstViewController with book button, SecondViewController it is a reader and TableViewController it is settings for text size. But if I open book in FirstViewController go to SecondViewController and in navigation bar tab aA button and open TableViewController all works fine. But after if I want back to FirstViewController I not close TableViewController and do swipe from left to right my app stops. How to fix it?
SecondViewController:
#IBOutlet weak var textSettings: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupGestures()
}
// MARK: - Text Settings View
private func setupGestures() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
tapGesture.numberOfTapsRequired = 1
textSettings.addGestureRecognizer(tapGesture)
}
#objc private func tapped(){
guard let popVC = storyboard?.instantiateViewController(withIdentifier: "popVC") else { return }
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = self.textSettings
popOverVC?.sourceRect = CGRect(x: self.textSettings.bounds.midX, y: self.textSettings.bounds.maxY, width: 0, height: 0)
popVC.preferredContentSize = CGSize(width: 250, height: 250)
self.present(popVC, animated: true)
}
}
extension SecondViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Other controllers have no special code. Video illustrating the problem - https://drive.google.com/file/d/1HUV26H4VFoKglh8FbkNP0y2Y3rYl4Q_f/view?usp=sharing

Related

How to change label text of a label in a popover view controller when pressing a button in the main view?

I'm relatively new to Swift. I have a main view controller, ViewControllerMain, and a popover view controller, PopUpVC, which only has a label. I have a function, infoClicked that displays the popover with another function (showPopover) when the info button is clicked. When I click the button, I want to change the label text of the popover. However, with the current code, the label always displays "Default".
Here is my code:
class ViewControllerMain: UIViewController, UIPopoverPresentationControllerDelegate, GetTimesUsed {
let tipController: PopUpVC = PopUpVC().self
#IBAction func infoClicked(_ sender: UIButton) {
tipController.tipText = "Success"
showPopover()
}
func showPopover() {
let myViewController = storyboard?.instantiateViewController(withIdentifier: "popupController")
myViewController?.preferredContentSize = CGSize(width: 350, height: 200)
myViewController?.modalPresentationStyle = .popover
let popOver = myViewController?.popoverPresentationController
popOver?.delegate = self
UIView.animate(withDuration: 1, animations: {
self.GifView.alpha = 0.7
})
DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) {
UIView.animate(withDuration: 0.5, animations: {
self.present(myViewController!, animated: true, completion: nil)
})
}
popOver?.permittedArrowDirections = .down
popOver?.sourceView = self.view
var passthroughViews: [AnyObject]?
passthroughViews = [infoButton]
myViewController?.popoverPresentationController?.passthroughViews = (NSMutableArray(array: passthroughViews!) as! [UIView])
popOver?.sourceRect = infoButton.frame
}
}
class PopUpVC: UIViewController {
#IBOutlet weak var tip: UILabel!
var tipText: String = "Default"
override func viewDidLoad() {
super.viewDidLoad()
tip.text = tipText
// Do any additional setup after loading the view.
}
}
Thank you for your help.
As mentioned in comments, you seem to be instantiating the popup controller 2 times, so try it like this in your showPopOver code:
let myViewController = storyboard?.instantiateViewController(withIdentifier: "popupController") as! PopUpVC
myViewController.preferredContentSize = CGSize(width: 350, height: 200)
myViewController.modalPresentationStyle = .popover
myViewController.tipText = '' //set to what ever

User Input Data does not remain after viewing other ViewControllers

The user input is typed into the TextFields and TextViews on ViewController1, but when I select a button to show either ViewController2 or ViewController3 via a segue, on my return to ViewController1 (also via another segue), the input is no longer there, as if the app was just reopened.
How do I make the initial user input remain in the text fields and text views while the user switches to a different view and also until the user hits the "Send Email" button back on ViewController1?
Below is my code
ViewController1
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var DateTextField: UITextField!
#IBOutlet weak var ScrollView: UIScrollView!
#IBOutlet weak var FirstTextView: UITextField!
#IBOutlet weak var FirstName: UITextField!
#IBOutlet weak var OtherDetailsField: UITextView!
lazy var datePicker: UIDatePicker = {
let picker = UIDatePicker()
picker.datePickerMode = .date
picker.addTarget(self, action: #selector(datePickerChanged(_:)), for: .valueChanged)
return picker
}()
lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
// Adjust Scroll for Keyboard ------------------
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
ScrollView.contentInset = .zero
} else {
ScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
}
ScrollView.scrollIndicatorInsets = ScrollView.contentInset
// let selectedRange = OtherDetailsField.selectedRange
// OtherDetailsField.scrollRangeToVisible(selectedRange)
}
override func viewDidLoad(){
super.viewDidLoad()
// Adjust Scroll for Keyboard ---------------
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
// Date Picker ---------------
DateTextField.inputView = datePicker}
#objc func datePickerChanged(_ sender: UIDatePicker){
DateTextField.text = dateFormatter.string(from: sender.date)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){view.endEditing(true)
}
// Dismiss Keyboard ------------------
func setupKeyboardDismissRecognizer(){
let tapRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(ViewController.dismissKeyboard))
tapRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(tapRecognizer)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
// Add Done Button to keypad toolbar -----------------
extension UITextField{
#IBInspectable var doneAccessory: Bool{
get{
return self.doneAccessory
}
set (hasDone) {
if hasDone{
addDoneButtonOnKeyboard()
}
}
}
func addDoneButtonOnKeyboard() {
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
let items = [flexSpace, done]
doneToolbar.items = items
doneToolbar.sizeToFit()
self.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction()
{
self.resignFirstResponder()
}
}
extension UITextView{
#IBInspectable var doneAccessory: Bool{
get{
return self.doneAccessory
}
set (hasDone) {
if hasDone{
addDoneButtonOnKeyboard()
}
}
}
func addDoneButtonOnKeyboard() {
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
let items = [flexSpace, done]
doneToolbar.items = items
doneToolbar.sizeToFit()
self.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction() {
self.resignFirstResponder()
}
}
ViewController2
import UIKit
class ViewController2: UIViewController {
#IBOutlet weak var ScrollView: UIScrollView!
// Adjust Scroll for Keyboard ------------------
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
ScrollView.contentInset = .zero
} else {
ScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
}
ScrollView.scrollIndicatorInsets = ScrollView.contentInset
//let selectedRange = yourTextView.selectedRange
//yourTextView.scrollRangeToVisible(selectedRange)
}
override func viewDidLoad() {
super.viewDidLoad()
// Adjust Scroll for Keyboard ---------------
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
// Dismiss Keyboard ------------------
func setupKeyboardDismissRecognizer(){
let tapRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(ViewController.dismissKeyboard))
tapRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(tapRecognizer)
}
func dismissKeyboard()
{
view.endEditing(true)
}
}
ViewController3
import UIKit
class ViewController3: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
short answer: backwards segues that You used create new instances, You could use 'dismiss' method instead or use Tab bar controller.
long answer:
Firstly, I assume You created Single View Controller when creating project in Xcode, and all segues You used are of "show" type.
Lets say You create segue 'ViewController1->ViewController2' and button 'NEXT' to execute it.
When You click on 'NEXT' You create a new instance of ViewController2, something kinda "new copy".
Next You create another segue to go back:
'ViewController2->ViewController1' and button 'BACK' to execute.
when You click on 'BACK' button (which triggers segue), You're NOT going back to the original ViewController1, but You're creating a new instance of ViewController1.
So as You can see, using segues this way can create future memory problems when users could go back and forth, every time creating new instance, stacking windows on top of each other.
The easiest solution is to delete segues that point backwards and instead put 'dismiss' method inside your 'BACK' button codeblock:
self.dismiss(animated: true, completion: nil)
Every time You use 'dismiss' method, You close the actual ViewController and go back to the previous one.
If You want something like 'HOME' button to go from any ViewController back to the first one (root), You can use this code:
self.view.window!.rootViewController?.dismiss(animated: true,completion: nil)
But remember - If You don't write some code to save Your data before dismissing, You will still lose the ViewController2 and ViewController3 data when You dismiss them.
Finally, if You need to keep Your data displayed when user switches between ViewControllers, I would use Tab bar controller. You can create it using template 'Tabbed App' when You create Xcode project.
Hope this helps! And don't take it as 100%, I'm still a Swift Rookie :)

ARC Memory Management Changing Per Example

Note: I created a Test program so I can better understand how ARC works - I am having trouble implementing it in my real project.
I created a Test program to determine how ARC works - it works great! Here it is.
ViewController1:
import UIKit
class ViewController: UIViewController {
weak var vc2:ViewController2?
override func viewDidLoad() {
super.viewDidLoad()
print("VC1 Initialized")
addButton()
}
func addButton() {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
button.backgroundColor = .red
button.setTitle("Go to VC2", for: .normal)
self.view.addSubview(button)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
}
#objc func buttonAction(sender: UIButton!) {
let vx = ViewController2()
vx.VC1 = self
vc2 = vx
self.present(vc2!, animated: false, completion: nil)
}
}
ViewController2:
import UIKit
class ViewController2: UIViewController {
weak var VC1:ViewController?
weak var VC3:ViewController3?
override func viewDidLoad() {
super.viewDidLoad()
print("VC2 Initialized")
addButton()
}
deinit {
print("VC2 Deinitialized")
}
func addButton() {
for i in 0..<2 {
let button = UIButton(frame: CGRect(x: 0, y: self.view.frame.height*0.5*CGFloat(i), width: self.view.frame.width, height: self.view.frame.height*0.5))
button.backgroundColor = i == 0 ? .red : .blue
button.setTitle("Go to VC\(i*2+1)", for: .normal)
self.view.addSubview(button)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.tag = i
}
}
#objc func buttonAction(sender: UIButton!) {
let tag = sender.tag
if(tag == 0) {
self.dismiss(animated: true, completion: nil)
}
else {
let vc3 = ViewController3()
vc3.ViewController2 = self
VC3 = vc3
self.present(VC3!, animated: true, completion: nil)
}
}
}
ViewController3:
import UIKit
class ViewController3: UIViewController {
var ViewController2:ViewController2?
override func viewDidLoad() {
super.viewDidLoad()
print("VC3 Initialized")
addButton()
}
deinit {
print("VC3 Deinitialized")
}
func addButton() {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
button.backgroundColor = .red
button.setTitle("Go to VC1", for: .normal)
self.view.addSubview(button)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
}
#objc func buttonAction(sender: UIButton!) {
self.ViewController2!.VC1!.dismiss(animated: true, completion: nil)
}
}
What I was looking at was how to remove multiple pages at a time (as indicated by the button push on VC3 and it goes back to the main page) and still remove ARC memory. All goes well. You can see the output below of me doing these operations.
Start program - push to page 2 - push to page. 1 - push to page 2 - push to page 3 - go to page 1
VC1 Initialized
VC2 Initialized
VC2 Deinitialized
VC2 Initialized
VC3 Initialized
VC3 Deinitialized
VC2 Deinitialized
The problem I am incurring is in my actual program - when I use these methods above, it automatically calls deinit on the last object I created when I CREATE my new object. But it doesn't actually delete anything - the memory graph will show x number of items even though the deinit method is called.
import UIKIT
class myClass: UIViewController {
weak var pageController:PageController?
override func viewDidLoad() {
super.viewDidLoad()
print("Initialized PC")
presentThemes()
}
func presentThemes() {
let pg = PageController(x: 1, controller: self)
pageController = pg
self.present(pageController!, animated: true, completion: nil)
}
deinit {
print("Deinit")
}
}
The output I am being provided is:
Initialized PC
On the first instance and is:
Deinit
Initialized PC
On the second instance. Any idea why Deinit is being called but the memory graph shows it there?

UIPopoverPresentationController showing as full-window modal popup

I have a project for which I'm using a UIPopoverPresentationController, which I want to show as a popover for both iPhone and iPad. Even in a standalone project with just a blank Storyboard with a single UIButton, the following code keeps resulting in a full-screen modal popup instead. I've seen a question similar to this asked before on SO multiple times, but everyone seems to solve it by returning .None for adaptivePresentationStyleForPresentationController, but even including that isn't fixing it for me. I have a single ViewController with the following code--what am I missing?
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBOutlet weak var thatButton: UIButton!
#IBAction func presentPopoverController(sender: UIButton) {
let popVC = UIViewController() //(nibName: "CameraPopover", bundle: nil)
popVC.view.backgroundColor = UIColor.blueColor()
popVC.modalPresentationStyle = .Popover
popVC.preferredContentSize = CGSize(width: 100, height: 100)
popVC.view.layer.borderWidth = 5
popVC.view.layer.borderColor = UIColor.blackColor().CGColor
self.presentViewController(popVC, animated: true, completion: nil)
let popController = popVC.popoverPresentationController
popController!.delegate = self
}
func prepareForPopoverPresentation(popoverPresentationController: UIPopoverPresentationController) {
popoverPresentationController.permittedArrowDirections = .Down
popoverPresentationController.sourceView = self.thatButton
popoverPresentationController.sourceRect = self.thatButton.frame
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .None
}
}

UITabBarController height decreases everytime it's loaded

When I first load my application and log in. Everything is fine.
However when I log out, then log back in. The height of my view has been decreased. Here's a screenshot of the bug:
I havn't been able to find the cause of this. Making this quite a difficult question to ask help with as I can't specify the precise section of code causing the issue. But I'll try.
The problematic setup is like so:
I have a containerViewController, with 2 childViewControllers, a menu and a UITabBarController. The UITabBarController has 2 UIViewControllers.
To better explain it, here's a visual representation.
_______________________
App Start ->
NavigationController(rootViewController LandingPageVC)
LandingPageVC -> push -> SignInVC(this is where I login from)
SignInVC -> push -> ContainerViewController(this has my UITabBarController and my menu)
ContainerViewController (sets up my menuTabBarController and menu)
menuTabBarController (this tabBarController is used to switch out my content from the menu)
SidePanelViewController (this is my menu)
ContainerViewController -> push(signing out) -> LandingPageVC
_______________________
Here's how I push the containerViewController when a successful login is called.
let mainTableViewController = ContainerViewController()
mainTableViewController.navigationItem.setHidesBackButton(true, animated: false)
navigationController!.pushViewController(mainTableViewController, animated: true)
menuEnabled = true
here's the function called from the containerViewController I use to log out.
func signOut() {
// Set up the landing page as the main viewcontroller again.
let mainTableViewController = LandingPageVC()
mainTableViewController.navigationItem.setHidesBackButton(true, animated: false)
mainTableViewController.skipView = false
self.navigationController!.pushViewController(mainTableViewController, animated: true)
// Disable menu access
menuEnabled = false
}
by printing the height of ContainerViewController and menuTabBarController, I found that it is the UITabBarController's height that's decreasing and not the ContainerViewController.
Here's the code that has to do with the UITabBarController
import UIKit
import QuartzCore
let menuTabBarController = UITabBarController()
var menuButton = UIBarButtonItem()
var menuEnabled = false
class ContainerViewController: UIViewController, CenterViewControllerDelegate, SidePanelViewControllerDelegate, UIGestureRecognizerDelegate {
func needsSignOut(sender: SidePanelViewController) {
// toggling left panel
self.toggleLeftPanel()
// signing out
self.signOut()
}
var centerViewController: UITabBarController!
var leftViewController: SidePanelViewController?
let centerPanelExpandedOffset: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
menuTabBarController.tabBar.hidden = true
menuButton = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: self, action: "toggleLeftPanel")
if let font = UIFont(name: "FontAwesome", size: 20) {
menuButton.setTitleTextAttributes([NSFontAttributeName: font], forState: UIControlState.Normal)
}
self.navigationItem.leftBarButtonItem = menuButton
//let tabBarController = UITabBarController()
let suggestionsVC = SuggestionsViewController()
let testVC = detaiLSuggestion_VC()
let controllers = [suggestionsVC,testVC]
menuTabBarController.setViewControllers(controllers, animated: false)
centerViewController = menuTabBarController
view.addSubview(menuTabBarController.view)
addChildViewController(menuTabBarController)
//centerNavigationController.didMoveToParentViewController(self)
}
// MARK: CenterViewController delegate methods
func toggleLeftPanel() {
let notAlreadyExpanded = (currentState != .LeftPanelExpanded)
if notAlreadyExpanded {
addLeftPanelViewController()
}
animateLeftPanel(shouldExpand: notAlreadyExpanded)
}
func collapseSidePanels() {
switch (currentState) {
case .LeftPanelExpanded:
toggleLeftPanel()
default:
break
}
}
func addLeftPanelViewController() {
if (leftViewController == nil) {
leftViewController = SidePanelViewController()
leftViewController!.delegate = self
addChildSidePanelController(leftViewController!)
}
}
func addChildSidePanelController(sidePanelController: SidePanelViewController) {
view.insertSubview(sidePanelController.view, atIndex: 0)
addChildViewController(sidePanelController)
sidePanelController.didMoveToParentViewController(self)
}
func animateLeftPanel(#shouldExpand: Bool) {
if (shouldExpand) {
currentState = .LeftPanelExpanded
animateCenterPanelXPosition(targetPosition: CGRectGetWidth(centerViewController.view.frame) - centerPanelExpandedOffset)
} else {
animateCenterPanelXPosition(targetPosition: 0) { finished in
self.currentState = .BothCollapsed
self.leftViewController!.view.removeFromSuperview()
self.leftViewController = nil;
}
}
}
func animateCenterPanelXPosition(#targetPosition: CGFloat, completion: ((Bool) -> Void)! = nil) {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
self.centerViewController.view.frame.origin.x = targetPosition
}, completion: completion)
}
Any help deducing where this is coming from or how I can go about fixing it would be greatly appreciated! And again I apologize for the dumb of code. I'll update it further if I am able to rule out parts of it.
rdelmar found a solution for me in chat.
The problem was fixed by specifying the menuTabBarController.view.frame like so:
menuTabBarController.view.frame = self.view.frame

Resources