Swift remove subview from another class - swift

I have my ViewController 'chatPage' in which I am presenting a UIView using a xib file called 'chatOverlay'. I have placed a button inside the xib which when pressed I would like to remove the subview from the chatPage ViewController.
I am not sure how to go about this as the button is in a different class to the subview and has no reference to the ViewController I wish to remove the sub view from.
I am aware I could use a notification observer to notify the viewController but is there an alternative way?
How would I go about removing the subview from within the chatOverlay class when the button is tapped?
chatOverlay:
import Foundation
class chatOverlay: UIView {
#IBAction func closeOverlay(_ sender: UIButton) {
print("CLOSE OVERLAY")
}
}
chatPage:
class chatPage: UIViewController {
override func viewDidLoad(){
view.addSubview(overlayAdd().show(height: view.frame.size.height, width: view.frame.size.width, x: view.frame.origin.x, y: view.frame.origin.y, tag: 101))
}
}
overlayAdd:
import Foundation
import UIKit
class overlayAdd {
func show(height: CGFloat, width: CGFloat, x: CGFloat, y: CGFloat, tag: Int) -> UIView{
let chatOverlay = xibLoad().chatOverlay()
chatOverlay.frame.size.height = height
chatOverlay.frame.size.width = width
chatOverlay.frame.origin.x = x
chatOverlay.frame.origin.y = y
chatOverlay.tag = tag
let view = chatOverlay
return view
}
func remove(tag: Int){ }
}

You can use self.removeFromSuperview()
// Completely agree with comments, change to ChatOverlay
class chatOverlay: UIView {
#IBAction func closeOverlay(_ sender: UIButton) {
self.removeFromSuperview()
}
}
If you want it to fade away
// Again, completely agree with comments, change to ChatOverlay
class chatOverlay: UIView {
#IBAction func closeOverlay(_ sender: UIButton) {
UIView.animate(withDuration: 0.5, animations: {
self.alpha = 0.0
}) { (_) in
self.removeFromSuperview()
}
}
}
I am really curious how you are using a UIView inside chatOverlay when you appear to only be importing Foundation? Also, cannot figure out why in overlayAdd you are importing both Foundation and UIKit? UIKit itself imports Foundation. The biggest confussion to me is why do you have a class overlayAdd at all? Shouldn't the show function be more of a create function (you are not showing it till you add it as a subview) and just be a part of chatOverlay? Something like:
// Completely agree with comments, change to ChatOverlay
class chatOverlay: UIView {
#IBAction func closeOverlay(_ sender: UIButton) {
UIView.animate(withDuration: 0.5, animations: {
self.alpha = 0.0
}) { (_) in
self.removeFromSuperview()
}
}
static func create(withFrame: frame, tag: Int) -> UIView{
let chatOverlay = xibLoad().chatOverlay()
chatOverlay.frame = frame
chatOverlay.tag = tag
/*
What in the world are you trying to do here???
let view = chatOverlay
return view
get rid of these two completely useless lines!
just return chatOverlay like below...
*/
return chatOverlay
}
}
and then in chatPage:
// Completely agree with comments, change to ChatPage
class chatPage: UIViewController {
override func viewDidLoad(){
view.addSubview(chatOverlay.create(withFrame: view.frame, tag: 101))
}
}

Related

How to present a ViewController as a Modal Sheet without the background shadow

Is there any way to present a ViewController as a Modal Sheet without the background shadow as shown in the first image below using swift. Is there an easy way or should we need to write custom UIPresentationController? [![The required output][1]][1]
[1]: https://i.stack.imgur.com/QAEEn.png![enter image description here](https://i.stack.imgur.com/q4JD5.jpg)
You can use a smaller size view controller as per your need. Firstly add a class.
class SmallSizePresentationController : UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
get {
guard let theView = containerView else {
return CGRect.zero
}
return CGRect(x: 0, y: theView.bounds.height * (281 / 896), width: theView.bounds.width, height: theView.bounds.height)
}
}
}
Then when you want to present this type of view controller just add the extension of your current view controller.
extension YourCurrentViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return SmallSizePresentationController(presentedViewController: presented, presenting: presenting)
}
}
Then present your new view controller like this
let VC = withoutShadowVC()
VC.transitioningDelegate = self
VC.modalPresentationStyle = .custom
self.present(VC, animated: true, completion: nil)
You can modify the view controller height.

How to handle a built in AlertController / Email Prompt from appearing behind my view

My app contains a modal UIView that can be presented from anywhere. How this works is the present method attaches the view as a subview on the key window:
func present(_ completion: ((Bool) -> ())? = { _ in }) {
guard !isPresented else {
return
}
if !isBackgroundReady {
initializeBackground()
}
UIApplication.shared.keyWindow?.addSubview(backgroundView)
UIApplication.shared.keyWindow?.addSubview(self)
UIView.animate(withDuration: 0.3, animations: {
self.backgroundView.alpha = 0.35
self.alpha = 1.0
}, completion: { _ in
self.isPresented = true
completion?(true)
})
}
private func initializeBackground() {
backgroundView.backgroundColor = UIColor.black
backgroundView.alpha = 0.0
backgroundView.frame = CGRect(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY, width: UIScreen.main.bounds.width * 1.2, height: UIScreen.main.bounds.height * 1.2)
backgroundView.center = CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY)
}
This modal contains an email link that users can click that opens up an email prompt (or an email action sheet if they long press it). This link is added by using the an NSAttributedString and it's .link attribute on a UITextView:
let supportString = NSMutableAttributedString(
string: "general.supportEmail".localized(),
attributes: [
.link: "mailto:\("general.supportEmail".localized())",
]
)
supportTextView.attributedText = supportString
However, when the email prompt or action sheet appears, it is displayed behind the modal view:
Is it possible to get the prompt/action sheet to appear above the modal view with the current way I present the modal, or will I need to add some sort of recognizer somewhere that detects when one of these views appears and temporarily dismiss the modal until my app view comes back into focus? If it's the later, how would I accomplish that?
The quick answer as to why this is happening is that you are presenting your custom modal view on top of the Window, which will be on top of everything, and your UIAlertController will be presented on the UIViewController presenting it (which is below your custom view).
One quick solution would be to always add your custom view as a subview on the current "top" UIViewController. You can do that with a UIViewController extension - something like this:
extension UIViewController {
static func topViewController(_ viewController: UIViewController? = nil) -> UIViewController? {
let viewController = viewController ?? UIApplication.shared.keyWindow?.rootViewController
if let navigationController = viewController as? UINavigationController, !navigationController.viewControllers.isEmpty {
return self.topViewController(navigationController.viewControllers.last)
} else if let tabBarController = viewController as? UITabBarController,
let selectedController = tabBarController.selectedViewController
{
return self.topViewController(selectedController)
} else if let presentedController = viewController?.presentedViewController {
return self.topViewController(presentedController)
}
return viewController
}
}
This extension will handle any UIViewController that is "on top", whether it's in a UINavigationController, a UITabBarController, or just presented modally, etc. Should cover all cases.
After that you can adjust your present method to take this into account:
func present(_ completion: ((Bool) -> ())? = { _ in }) {
guard !isPresented else {
return
}
if !isBackgroundReady {
initializeBackground()
}
guard let topViewController = UIViewController.topViewController() else { return }
topViewController.view.addSubview(backgroundView)
topViewController.view.addSubview(self)
UIView.animate(withDuration: 0.3, animations: {
self.backgroundView.alpha = 0.35
self.alpha = 1.0
}, completion: { _ in
self.isPresented = true
completion?(true)
})
}
Instead of adding in the window, add the modal in the navicontroller -> topviewcontroller.
Link: https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621849-topviewcontroller.
This might help you.

How to constrain second NSViewController minimum size in OS X app?

I am a novice Mac OS X developer. I assume this is an easy question, but I haven't been able to find any useful results via searches.
How do I constrain the size of a SECOND view controller?
I started with a simple Mac OS X app, with a single View Controller. I can select the window that contains the View Controller, then select the "Size Inspector" and check the "Minimum Content Size" box, and specify a minimum x and y for the window.
This allows me to specify the minimum size for this first view controller, as I expect. All is good.
Then I add a second view controller, with a Modal segue from the first view controller, triggered by a button press. I add a NSTextView to this second view controller, to display an attributed string. The text view works fine, displaying the attributed string correctly. This text view is a separate window, and has no minimum size constraint.
So how do i specify the minimum size for this second view controller view? Is this typically done in Interface Builder, or programmatically? When I step through the view hierarchy using Document Outline, I don't see how I can specify minimum size using the Size Inspector. Am I missing something??
Here is my simplified code:
file "ViewController.swift"
class ViewController: NSViewController {
...
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
let secondVC: SecondViewController = segue.destinationController as! SecondViewController
secondVC.reportAttrString = myReport.reportText
}
...
}
file "SecondViewController.swift"
class SecondViewController: NSViewController {
var reportAttrString = NSMutableAttributedString()
#IBOutlet var ReportTextView: NSTextView!
}
I would appreciate any suggestions, or pointers to any documentation or tutorials that may help me.
The simplest way to do this would be:
class SecondViewController: NSViewController, NSWindowDelegate {
override func viewWillAppear() {
super.viewWillAppear()
self.view.window?.delegate = self
self.view.window?.minSize = NSSize(width: 100, height: 100)
}
}
override func viewDidAppear() {
super.viewDidAppear()
var frame = self.view.window!.frame
var initialSize = NSSize(width: 100, height: 100)
frame.size = initialSize
self.view.window?.setFrame(frame, display: true)
}
Although if you were looking for a manual approach then the following would work aswell.
class SecondViewController: NSViewController, NSWindowDelegate {
override func viewWillAppear() {
super.viewWillAppear()
self.view.window?.delegate = self
// Set the initial size
var frame = self.view.window?.frame
var initialSize = NSSize(width: 100, height: 100)
frame.size = initialSize
self.view.window?.setFrame(frame, display: true)
}
func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize {
let minimumSize = NSSize(width: 100, height: 100)
var newSize = NSSize()
if(frameSize.width < minimumSize.width) {
newSize.width = minimumSize.width
} else {
newSize.width = frameSize.width
}
if(frameSize.height < minimumSize.height) {
newSize.height = minimumSize.height
} else {
newSize.height = frameSize.height
}
return newSize
}
}
Further reading:
Resize windows event
NSWindow minSize
Resizing the window

Remove any view from any where e.g from window

I have 2 views on the screen, one is overlayView at the bottom and introView at the top of that overlayView. When I tap(tapToContinueAction) on the screen they should both hide or remove themselves.
extension UIView {
....
func hideView(view: UIView, hidden: Bool) {
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
view.isHidden = hidden
})
}
}
class IntroScreen
#IBAction func tapToContinueAction(_ sender: UITapGestureRecognizer) {
self.hideView(view: self, hidden: true)
}
--
class OverlayView : UiView {
...
}
In current situation i can hide introScreen only and i dont know how the other class's action can effect the overlayView at the same time and hide that view as well. Any idea?
You have two different classes for your views. Make an extension of your window to remove your specific views just like I have made removeOverlay and removeIntroView both these computed properties will go and search in subviews list of window and check each view with their type and remove them. Thats how you can remove any view form any where.
class OverLayView: UIView {}
class IntroView: UIView {
#IBAction func didTapYourCustomButton(sender: UIButton) {
let window = (UIApplication.shared.delegate as! AppDelegate).window!
window.removeOverlay
window.removeIntroView
}
}
extension UIWindow {
var removeOverlay: Void {
for subview in self.subviews {
if subview is OverLayView {
subview.removeFromSuperview()// here you are removing the view.
subview.hidden = true// you can hide the view using this
}
}
}
var removeIntroView: Void {
for subview in self.subviews {
if subview is IntroView {
subview.removeFromSuperview()// here you are removing the view.
subview.hidden = true// you can hide the view using this
}
}
}
}

Adding custom buttons to UITabBarController (adding button in the center)

i'm trying to make a custom tab bar with some images i made and i'm having some trouble. I'm trying to add a button to the tab bar and it seems like i can't do it. I want to do something like this:
Then adding some animations to that button. How can i go about adding that button? Do i need to subclass UITabBarController? Thank you!
In TabbarController class, add this code
override func viewDidLoad() {
super.viewDidLoad()
setupMiddleButton()
}
// MARK: - AddButton
func setupCenterButton() {
let centerButton = UIButton(frame: CGRect(x: 0, y: 10, width: 45, height: 45))
var centerButtonFrame = centerButton.frame
centerButtonFrame.origin.y = (view.bounds.height - centerButtonFrame.height) - 2
centerButtonFrame.origin.x = view.bounds.width/2 - centerButtonFrame.size.width/2
centerButton.frame = centerButtonFrame
centerButton.layer.cornerRadius = 35
view.addSubview(centerButton)
centerButton.setBackgroundImage(#imageLiteral(resourceName: "tabPost"), for: .normal)
centerButton.addTarget(self, action: #selector(centerButtonAction(sender:)), for: .touchUpInside)
view.layoutIfNeeded()
}
// MARK: - Center button Actions
#objc private func centerButtonAction(sender: UIButton) {
selectedIndex = 2
}
It will work.. :)
You should implement the delegate method of UITabBarControllerDelegate:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController == (self.tabBarController?.viewControllers?[theIndexOfTheButton])! {
// do my stuffs here
return false
}
return true
}
Don't forget to set self.tabBarController?.delegate = self