SubView(nib) wont remove after calling removeFromSuperView() - ios

I have an overlay view to segregate content, I'm checking for authentication in viewWillAppear() and I have a Notification subscribed to my Auth method. If I authenticate before any of my other views appear the overlay does not show up, however it does on the first view and will not go away even after calling removeFromSuperView().
import UIKit
import FirebaseAuth
class ProtectedViewController: UIViewController, ForceSignInBannerDelegate,
SignUpViewControllerDelegate, LoginViewControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
NotificationCenter.default.addObserver(self, selector: #selector(checkAuthentication), name: .myNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.checkAuthentication()
}
func checkAuthentication() {
let bannerViewController = ForceSignInBanner.instanceFromNib() as! ForceSignInBanner
bannerViewController.delegate = self
if (!AuthenticationService.sharedInstance.isAuthenticated()) {
self.setView(view: bannerViewController, hidden: false)
print("Need to login")
} else if(AuthenticationService.sharedInstance.isAuthenticated()) {
self.setView(view: bannerViewController, hidden: true)
}
}
func setView(view: UIView, hidden: Bool) {
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: { _ in
view.isHidden = hidden
if hidden {
view.removeFromSuperview()
} else {
self.view.addSubview(view)
}
}, completion: nil)
}

It's because you're trying to remove a new ForceSignInBanner each time. Ideally you should create it once and keep a reference to the ForceSignInBanner created (as an optional property of ProtectedViewController).
Then remove the ForceSignInBanner that you've stored in the property.
class ProtectedViewController: UIViewController, ForceSignInBannerDelegate {
// This lazily loads the view when the property is first used and sets the delegate.
// Ideally you wouldn't force-case the `as` but I've left it for simplicity here.
private lazy var forceSignInBannerView: ForceSignInBanner = {
let forceSignInBannerView = ForceSignInBanner.instanceFromNib() as! ForceSignInBanner
forceSignInBannerView.delegate = self
return forceSignInBannerView
}()
// ... your other code ... //
fun toggleForceSignInBannerViewVisibility(isVisible: Bool) {
if isVisible {
view.addSubview(forceSignInBannerView)
} else {
forceSignInBannerView.removeFromSuperview()
}
}
}

Related

viewController Present when I dismissed it

I have a viewcontroller which is segue to a tableViewController and I want to present it with transition and as a sideMenu from top I find some code and it's working fine but i cant understand the part when i dismiss the ViewController it present itself
//
// ViewController.swift
// ProTansition
//
// Created by Teodik Abrami on 11/1/18.
// Copyright © 2018 Teodik Abrami. All rights reserved.
//
import UIKit
class ViewController: UIViewController, MenuTransitionManagerDelegate {
func dismiss() {
dismiss(animated: true, completion: nil)
print("dismiss run")
}
var menuTransition = MenuTransitionManager()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
print("ViewController Appear")
}
override func viewDidDisappear(_ animated: Bool) {
print("viewcontroller disapear")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinaion = segue.destination
destinaion.transitioningDelegate = menuTransition
menuTransition.delegate = self
}
}
tableView is a normal tableview with 5 rows and no special code in it
and the transition
//
// MenuTransitionManager.swift
// ProTansition
//
// Created by Teodik Abrami on 11/1/18.
// Copyright © 2018 Teodik Abrami. All rights reserved.
//
import Foundation
import UIKit
#objc protocol MenuTransitionManagerDelegate {
func dismiss()
}
class MenuTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
let duration = 2.0
var isPresenting = false
var delegate: MenuTransitionManagerDelegate?
var snapShot: UIView? {
didSet {
if let delegate = delegate {
let tap = UITapGestureRecognizer(target: delegate, action: #selector(delegate.dismiss))
snapShot?.addGestureRecognizer(tap)
}
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {
return
}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
return
}
let container = transitionContext.containerView
let moveDown = CGAffineTransform.init(translationX: 0, y: container.frame.height - 150)
if isPresenting {
container.addSubview(toView)
snapShot = fromView.snapshotView(afterScreenUpdates: true)
container.addSubview(snapShot!)
}
UIView.animateKeyframes(withDuration: duration, delay: 0, options: [], animations: {
if self.isPresenting {
self.snapShot?.transform = moveDown
} else {
self.snapShot?.transform = CGAffineTransform.identity
}
}) { (finished) in
transitionContext.completeTransition(true)
if !self.isPresenting {
self.snapShot?.removeFromSuperview()
}
}
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = true
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = false
return self
}
}
I add tapgesture to snapshot and when its tapped protocol works and dismiss in viewcontroller works and the viewController appears I dont understand why and even why codes run on a controller that are not presented
this is the view controller that i cant figured out why it present when i dismiss it
this happend when menuButton pressed
Your whole strategy of setting isPresenting = true or isPresenting = false is doomed to failure, as both pieces of code will run on both occasions. You have to distinguish presentation from dismissal by using two different animationController objects (instead of returning self both times) or by looking to see which view controller is the from view controller and which is the to view controller.

UISearchBar resignFirstResponder not working

UIViewController needs to hide keyboard inside viewWillDisappear or viewDidDisappear methods. UIViewController stays in memory after disappearing and can be presented again. On first appearance UISearchBar is not firstResponder and keyboard is hidden. But if I pop UIViewController with keyboard shown and then push it again - keyboard is not hidden, however I call:
override func viewDidLoad() {
super.viewDidLoad()
instrumentsTableView.register(UINib(nibName: kDealsFilterInstrumentTableViewCellNib, bundle: nil), forCellReuseIdentifier: kDealsFilterInstrumentTableViewCellReusableId)
instrumentsTableView.dataSource = self
instrumentsTableView.delegate = self
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if presenter.numberOfInstruments != 0 {
instrumentsTableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
}
KeyboardManager.shared.unsubscribe()
instrumentsSearchBar.text = ""
presenter.findInstruments(with: "") //just sets settings to default/ reloads data
instrumentsSearchBar.endEditing(true)
instrumentsSearchBar.resignFirstResponder()
view.endEditing(true)
view.resignFirstResponder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
KeyboardManager.shared.subscribe(self)
}
KeyboardManager - sends notification if keyboard's state has changed, if relevant:
final class KeyboardManager {
private init() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: Notification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: Notification.Name.UIKeyboardWillHide, object: nil)
}
static let shared = KeyboardManager()
#objc private func keyboardWillShow(_ notification: Notification) {
if let keyboardSize = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let height = keyboardSize.cgRectValue.height
keyboardHeight = height
keyboardState = .shown
}
}
#objc private func keyboardWillHide(_ notification: Notification) {
keyboardHeight = 0
keyboardState = .hidden
}
private weak var subscriber: KeyboardManagerDelegate?
func subscribe(_ delegate: KeyboardManagerDelegate) {
subscriber = delegate
}
func unsubscribe() {
subscriber = nil
}
private var keyboardHeight: CGFloat = 0
private var keyboardState: KeyboardState = .hidden {
didSet {
if keyboardState != oldValue {
subscriber?.keyboardDidChange(state: keyboardState, height: keyboardHeight)
}
}
}
}
enum KeyboardState {
case shown
case hidden
}
protocol KeyboardManagerDelegate: class {
func keyboardDidChange(state: KeyboardState, height: CGFloat)
}
I've tried to use this code inside viewWillAppear and viewWillDisappear - but UISearchBar is still firstResponder. If I pop with keyboard being hidden - it stays hidden. What might be the problem?
Screencast:
Sample project with the same issue on bitbucket
For keyboard issue this will work fine,
self.view.endEditing(true)
Write this in viewWillDisappear or viewDidDisappear
I've tried your code: Sample project with the same issue on bitbucket and its working as expected and fine.
Here is that code.
class ViewController: UIViewController {
#IBAction func btnShowSearch(button: UIButton) {
if let search = self.storyboard?.instantiateViewController(withIdentifier: "SeachBarViewController") {
self.navigationController?.pushViewController(search, animated: true)
}
}
}
// SeachBarViewController
class SeachBarViewController: UIViewController {
#IBOutlet var searchBar: UISearchBar!
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
attemptToHidKeyboard()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
attemptToHidKeyboard()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
attemptToHidKeyboard()
}
override func didMove(toParentViewController parent: UIViewController?) {
if parent == nil {
attemptToHidKeyboard()
}
}
override func willMove(toParentViewController parent: UIViewController?) {
if parent == nil {
attemptToHidKeyboard()
}
}
private func attemptToHidKeyboard() {
self.searchBar.resignFirstResponder()
self.searchBar.endEditing(true)
self.view.resignFirstResponder()
self.view.endEditing(true)
}
}
Here is result:

Swift Touches not recognized in static table view

I have a table view with static cells.
My second section has a header but no rows, under that section I have a label that when pressed should call sendEmail() function which will open the email app on their device.
I've tried using a label, text view, button, overriding the didSelectCellForRow function and all have failed.
I'm completely lost what ouches aren't being recognized.
I've added print statements to my touchesBegan function but they never print.
What could be the issue?
import UIKit
import MessageUI
class InfoTVC: UITableViewController, MFMailComposeViewControllerDelegate{
let ownerEmail = "test#email.com"
#IBOutlet weak var contactLbl: UILabel!
// MARK: - View functions
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - IBOutlet methods
#IBAction func backBtn(_ sender: Any) {
_ = navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
}
// MARK: - Email methods
// Open users email app on device
func sendEmail(){
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients([ownerEmail])
mail.setMessageBody("<p>Hello I had the chance to use your app and </p>", isHTML: true)
present(mail ,animated: true, completion: nil)
}
else{
// Failure
print("failed to open mail")
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in: self.view)
if contactLbl.frame.contains(location) {
print("yes")
sendEmail()
}
else{
print("no")
}
}
}
}
try to add tap gesture recognizer instead of handing touches
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.youLabelTapped(_:)))
yourLabel.addGestureRecognizer(tapGesture)
OR alternatively: add target directly to your label
yourLabel.addTarget(self, action: #selector(self.youLabelTapped(_:)), forControlEvents: .TouchUpInside)
and you func that will be called:
func youLabelTapped(_ sender: UITapGestureRecognizer) {
print("label tapped")
}
EDIT:
REQUIRED: don't forget to set user interaction enabled:
yourLabel.isUserInteractionEnabled = true

UITableViewCell setHighlighted, setSelected "animateAlongsideTransition" or similar

I want to be able to add custom animations to subclasses of UITableViewCell that would override methods such as:
override func setHighlighted(highlighted: Bool, animated: Bool) {
}
override func setSelected(selected: Bool, animated: Bool) {
}
and match the animation curve and animation duration for the default animations those methods perform.
In other words, how can I find the information about the current animations provided by Apple. I need this in order to add my own custom animations that perfectly matches the default ones
You can subclass your custom cell in your table view.
And here I create a simple example in Swift where I change the value of a label inside the cell:
import UIKit
class userTableViewCell: UITableViewCell {
#IBOutlet weak var userLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.highlighted = false
self.userLabel.alpha = 0.0
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.highlighted = true
} else {
self.highlighted = false
}
// Configure the view for the selected state
}
override var highlighted: Bool {
get {
return super.highlighted
}
set {
if newValue {
// you could put some animations here if you want
UIView.animateWithDuration(0.7, delay: 1.0, options: .CurveEaseOut, animations: {
self.userLabel.text = "select"
self.userLabel.alpha = 1.0
}, completion: { finished in
print("select")
})
}
else {
self.userLabel.text = "unselect"
}
super.highlighted = newValue
}
}
}
And with the storyboard what you must have:

Container view, Failed use delegate

i wanna designed slide function.So i use container view in storyboard without any segue.there is a button in centerView to open or close leftView.but this function is written in container.swift and i don't know how to set delegate..please help me,thanks.(All codes as below)
Container.swift
import UIKit
class ContainerViewController: UIViewController,test {
var leftViewController: UIViewController? {
willSet{
if self.leftViewController != nil {
if self.leftViewController!.view != nil {
self.leftViewController!.view!.removeFromSuperview()
}
self.leftViewController!.removeFromParentViewController()
}
}
didSet{
self.view!.addSubview(self.leftViewController!.view)
self.addChildViewController(self.leftViewController!)
}
}
var rightViewController: UIViewController? {
willSet {
if self.rightViewController != nil {
if self.rightViewController!.view != nil {
self.rightViewController!.view!.removeFromSuperview()
}
self.rightViewController!.removeFromParentViewController()
}
}
didSet{
self.view!.addSubview(self.rightViewController!.view)
self.addChildViewController(self.rightViewController!)
}
}
var menuShown: Bool = false
func showMenu() {
print(__FUNCTION__,__LINE__)
UIView.animateWithDuration(0.3, animations: {
self.rightViewController!.view.frame = CGRect(x: self.view.frame.origin.x + 235, y: self.view.frame.origin.y, width: self.view.frame.width, height: self.view.frame.height)
}, completion: { (Bool) -> Void in
self.menuShown = true
})
}
func hideMenu() {
UIView.animateWithDuration(0.3, animations: {
self.rightViewController!.view.frame = CGRect(x: 0, y: self.view.frame.origin.y, width: self.view.frame.width, height: self.view.frame.height)
}, completion: { (Bool) -> Void in
self.menuShown = false
})
}
var centerView:CenterViewController!
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let CenterNC:UINavigationController = storyboard.instantiateViewControllerWithIdentifier("CenterNC") as! UINavigationController
let leftVC:LeftViewController = storyboard.instantiateViewControllerWithIdentifier("LeftVC") as! LeftViewController
centerView = storyboard.instantiateViewControllerWithIdentifier("CenterVC") as! CenterViewController
self.leftViewController = leftVC
self.rightViewController = CenterNC
self.centerView.delegate = self
// Do any additional setup after loading the view.
}
}
CenterViewController.swift
import UIKit
protocol test{
func showMenu()
func hideMenu()
}
class CenterViewController: UIViewController {
var delegate:test! = nil
#IBAction func pressed(sender: AnyObject) {
if (delegate != nil) {
delegate.showMenu()
}
else{
print("Error")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Debug ContainerView.swift
Debug CenterViewController.swift
Usually you use whatever.delegate = self but I'm not quite sure what you want to do. (Also whatever.datasource = self). I dont know if this works for VCs.
As far as I know it is bad practice to have one UIViewController in the storyboard that contains two other VCs.
Rather you should either transition programmatically or with a storyboard segue.
self.presentViewController(LeftViewController(), animated: true, completion: nil)
self.dismissViewController(true, completion: nil)
View Controllers are automatically removed from memory (look up ARC if your curious) (as long as you don't have variables in the VC to be removed that are (or can be) accessed from another class that wasn't deinitialized - if that's confusing ignore it)
If you want a sliding animation or something like this, it's probably best to find something on GitHub to clone and import into your project. I've made an app with a tab bar that slides between views with some code I found online.
I've been droning but having two VCs in one VC is terrible for memory and in general bad practice. Utilize Custom Segues instead.

Resources