Is it bad to use static functions to handle popups in Swift? - ios

I have a lot of view controllers that use the same two functions that show and hide popup for me. Everytime I use them, I ask myself if it wouldn't be better to put them in a global class called PopupUtils for example and set the functions as static functions.
I did it and it worked but I'm not sure if it's a good thing to do because I have to pass to my function three arguments: the parent view controller, the child view controller and the popup_container view
Since it's all passed by val, is there not a problem with memory ? or any other problem I should be aware of ?
Here is my static class called Popup Utils
class PopupUtils {
static func showPopupView(parentViewController: UIViewController, childViewController: UIViewController, popupContainer: UIView) {
parentViewController.addChild(childViewController)
popupContainer.addSubview(childViewController.view)
childViewController.view.frame = popupContainer.bounds
childViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
childViewController.didMove(toParent: parentViewController)
UIView.transition(with: popupContainer, duration: 0.2, options: .transitionCrossDissolve, animations: {
popupContainer.isHidden = false
})
}
static func removePopupView(childViewController: UIViewController, popupContainer: UIView){
// Remove pop up VC from children
childViewController.willMove(toParent: nil)
childViewController.view.removeFromSuperview()
childViewController.removeFromParent()
// Hide pop up container
popupContainer.isHidden = true
// Release language menu
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "releaseMenuSwipe"), object: nil)
}
}

It’s not really bad but how about an extension of UIViewController
extension UIViewController {
func showPopupView(childViewController: UIViewController, popupContainer: UIView) {
addChild(childViewController)
popupContainer.addSubview(childViewController.view)
childViewController.view.frame = popupContainer.bounds
childViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
childViewController.didMove(toParent: self)
UIView.transition(with: popupContainer, duration: 0.2, options: .transitionCrossDissolve, animations: {
popupContainer.isHidden = false
})
}
func removePopupView(childViewController: UIViewController, popupContainer: UIView) {
// Remove pop up VC from children
childViewController.willMove(toParent: nil)
childViewController.view.removeFromSuperview()
childViewController.removeFromParent()
// Hide pop up container
popupContainer.isHidden = true
// Release language menu
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "releaseMenuSwipe"), object: nil)
}
}
An alternative to get rid of the parameters is a protocol extension. It assumes that the adopting UIViewController has two properties popupContainer and childViewController, if they are optional change and handle the type accordingly.
The two methods in the extension are available for any UIViewController which adopts the protocol
protocol PopupManageable {
var popupContainer: UIView { get }
var childViewController: UIViewController { get }
}
extension PopupManageable where Self : UIViewController {
func showPopupView() {
self.addChild(childViewController)
popupContainer.addSubview(childViewController.view)
childViewController.view.frame = popupContainer.bounds
childViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
childViewController.didMove(toParent: self)
UIView.transition(with: popupContainer, duration: 0.2, options: .transitionCrossDissolve, animations: {
self.popupContainer.isHidden = false
})
}
func removePopupView() {
// Remove pop up VC from children
childViewController.willMove(toParent: nil)
childViewController.view.removeFromSuperview()
childViewController.removeFromParent()
// Hide pop up container
popupContainer.isHidden = true
// Release language menu
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "releaseMenuSwipe"), object: nil)
}
}

I think you have to keep everything tracked. You are passing the view controller around and adding a child view controller. This may lead to the memory leak if you are not aware of complexity in the future. Keep tracking allocation whenever you add new task. in it.

Related

Child view controller's view ignores Auto Layout constraints

I would like to add child view controller to UINavigationController's view. I have hierarchy like this
NavigationController -> View(drawer) -> View(contentContainer) -> child view controller should be pinned to the contentContainer
But for some reason it ignores the constraints and I get weird results. Please see the screenshot, drawer is green, contentContainer is yellow and the child controller is placed almost outside of the screen and has frame with 0 height.
The critical code is inside the drawerContentController didSet and setupView method. Please note that I'm using SnapKit for constraints but the same problem was happening by setting the NSLayoutConstraints traditionally
class NavigationController: UINavigationController {
private var drawerHeightConstraint: Constraint!
fileprivate lazy var drawer = UIView()
private lazy var contentContainer = UIView()
var drawerContentController: UIViewController? {
didSet {
guard let new = drawerContentController else {
oldValue?.removeFromParent(animated: true)
return
}
if let old = oldValue {
old.removeFromParent(animated: true) { _ in
self.add(child: new, superview: self.contentContainer, animated: true, completion: nil)
}
} else {
add(child: new, superview: contentContainer, animated: true, completion: nil)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
view.addSubview(drawer)
drawer.backgroundColor = .green
drawer.snp.makeConstraints { make in
make.leading.bottom.trailing.equalToSuperview()
drawerHeightConstraint = make.height.equalTo(360).constraint
}
let blur = UIBlurEffect(style: .default)
let visualView = UIVisualEffectView(effect: blur)
drawer.addSubview(visualView)
visualView.pinToSuperView()
contentContainer.backgroundColor = .yellow
drawer.addSubview(contentContainer)
contentContainer.snp.makeConstraints { make in
make.edges.equalTo(drawer.safeAreaLayoutGuide)
}
}
func add(child controller: UIViewController, superview: UIView? = nil, animated: Bool, completion: ((Bool) -> Void)? = nil) {
controller.willMove(toParent: self)
addChild(controller)
controller.view.alpha = 0
(superview ?? view).addSubview(controller.view)
controller.view.pinToSuperView()
UIView.animate(withDuration: animated ? 0.3 : 0, animations: {
controller.view.alpha = 1
}) { finished in
controller.didMove(toParent: self)
completion?(finished)
}
}
}
extension UIView {
func pinToSuperView() {
snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
When I add a simple UIView to the contentContainer it works as expected
var drawerContentController: UIViewController? {
didSet {
let v = UIView()
v.backgroundColor = .purple
contentContainer.addSubview(v)
v.pinToSuperView()
}
}
Okay, I "solved" the issue.
It seems that UINavigationController does not like having child view controllers added to it. As seen on the screenshots, when child controller is added the entire view hierarchy of navigation controller is broken. On the second screenshot, the table view controller (white view) is displayed correctly, but in the first case it is not even in the view hierarchy!
So the moral is, try to not add child view controllers directly to UINavigationController, in the end it is not a normal UIViewController and doesn't even have a normal UIView. I have added another UIViewController to act as a container for UINavigationController and added the child controller to it. It now works as expected.

How to dismiss UICollectionViewController from button in UICollectionViewCell

I am making an onboarding screen, on the last screen I have a button that says "continue" and is supposed to dismiss the onboarding screen. The onboarding screen is a collection view controller with cells as each page. Please do not hesitate to ask for clarification I am don't know what else to add.
Thanks,
Edit
So I implemented user Francesco Deliro's answer, first problem was that I accidentally added the "delegate = self" into the viewDidLoad(). I fixed that but still the viewController does not dismiss.
My code is as follow in my viewController cell for item:
let loginCell = LoginCell()
loginCell.delegate = self
Here is the extension
extension TutorialViewController: LoginCellDelegate {
func didCompleteOnboarding() {
print("I should dimiss")
self.dismiss(animated: true, completion: nil)
}
Do I not need to call that function anywhere in the class just leave it outside the main class.
Edit 2
Here is how I connected my button action to the originial
#objc func continueTapped() {
...
continueButton.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: 1.0, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1, options: .allowUserInteraction, animations: { [weak self] in
self?.continueButton.transform = .identity
let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
self?.window!.layer.add(transition, forKey: kCATransition)
self?.delegate?.didCompleteOnboarding()
}, completion: { (success) in
token = true
defaults.set(token, forKey: "DidSee")
})
}
You can use delegation, for example:
protocol YourCellDelegate: class {
func didCompleteOnboarding()
}
Then in your cell:
class CustomCell: UICollectionViewCell {
weak var delegate: YourCellDelegate?
// in your button action
func dismissAction() {
delegate.didCompleteOnboarding()
}
}
Finally in your view controller set the cell delegate in the cellForItem function:
yourCell.delegate = self
And add:
extension YourViewController: YourCellDelegate {
func didCompleteOnboarding() {
// dismiss here
}
}

UIPageViewController transition bugged if animation starts in the meanwhile

I have a strange behaviour in my app using a UIPageViewController.
The layout of my app is a PageViewController (camera roll like) with a ads banner on bottom.
The banner's container starts as hidden and, when the ad gets loaded, i set the isHidden=false with an animation.
My problem is that when the banner gets into the screen it breaks the UIPageViewController transition if in progress as shown in this video:
I made a new project that reproduces the error very easy with a few lines, you can checkout it in GITHUB: You just need to spam the "Next" button until the banner gets loaded. It also can be reproduced by swipping the PageViewController but is harder to reproduce.
The full example code is:
class TestViewController: UIViewController,UIPageViewControllerDelegate,UIPageViewControllerDataSource {
#IBOutlet weak var constraintAdviewHeight: NSLayoutConstraint!
weak var pageViewController : UIPageViewController?
#IBOutlet weak var containerAdView: UIView!
#IBOutlet weak var adView: UIView!
#IBOutlet weak var containerPager: UIView!
var currentIndex = 0;
var clickEnabled = true
override func viewDidLoad() {
super.viewDidLoad()
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
pageViewController = pageVC
pageVC.delegate = self
pageVC.dataSource = self
addChildViewController(pageVC)
pageVC.didMove(toParentViewController: self)
containerPager.addSubview(pageVC.view)
pageVC.view.translatesAutoresizingMaskIntoConstraints = true
pageVC.view.frame = containerPager.bounds
pushViewControllerForCurrentIndex()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.containerAdView.isHidden = true
DispatchQueue.main.asyncAfter(deadline: .now()+4) {
self.simulateBannerLoad()
}
}
#IBAction func buttonClicked(_ sender: Any) {
guard clickEnabled else {return}
currentIndex -= 1;
pushViewControllerForCurrentIndex()
}
#IBAction func button2Clicked(_ sender: Any) {
guard clickEnabled else {return}
currentIndex += 1;
pushViewControllerForCurrentIndex()
}
private func simulateBannerLoad(){
constraintAdviewHeight.constant = 50
pageViewController?.view.setNeedsLayout()
UIView.animate(withDuration: 0.3,
delay: 0, options: .allowUserInteraction, animations: {
self.containerAdView.isHidden = false
self.view.layoutIfNeeded()
self.pageViewController?.view.layoutIfNeeded()
})
}
//MARK: data source
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
return getViewControllerForIndex(currentIndex+1)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
return getViewControllerForIndex(currentIndex-1)
}
func getViewControllerForIndex(_ index:Int) -> UIViewController? {
guard (index>=0) else {return nil}
let vc :UIViewController = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: "pageTest")
vc.view.backgroundColor = (index % 2 == 0) ? .red : .green
return vc
}
func pushViewControllerForCurrentIndex() {
guard let vc = getViewControllerForIndex(currentIndex) else {return}
print("settingViewControllers start")
clickEnabled = false
pageViewController?.setViewControllers([vc], direction: .forward, animated: true, completion: { finished in
print("setViewControllers finished")
self.clickEnabled = true
})
}
}
Note: Another unwanted effect is that the last completion block when the bug occurs does not get called, so it leaves the buttons disabled:
func pushViewControllerForCurrentIndex() {
guard let vc = getViewControllerForIndex(currentIndex) else {return}
print("settingViewControllers start")
clickEnabled = false
pageViewController?.setViewControllers([vc], direction: .forward, animated: true, completion: { finished in
print("setViewControllers finished")
self.clickEnabled = true
})
}
Note2: The banner load event is something I can't control manually. Due to the library used for displaying ads its a callback in the main thread that can happen in any moment. In the sample proyect this is simulated with a DispatchQueue.main.asyncAfter: call
How can I fix that? Thanks
What wrong?
Layout will interrupt animation.
How to prevent?
Not change layout when pageViewController animating.
When the ad is loaded:
Confirm whether pageViewController is animating, if so, wait until the animation is completed and then update, or update
Sample:
private func simulateBannerLoad(){
if clickEnabled {
self.constraintAdviewHeight.constant = 50
} else {
needUpdateConstraint = true
}
}
var needUpdateConstraint = false
var clickEnabled = true {
didSet {
if clickEnabled && needUpdateConstraint {
self.constraintAdviewHeight.constant = 50
needUpdateConstraint = false
}
}
}
For us, it was due to a device rotation causing a layout pass at the same time as an animation to a new view controller. We could not figure out how to stop the layout pass in this situation.
This worked for us:
let pageController = UIPageViewController()
func updatePageController() {
pageController.setViewControllers(newViewControllers, direction: .forward, animated: true, completion: {[weak self] _ in
// There is a bug with UIPageViewController where a layout pass during an animation
// to a new view controller can cause the UIPageViewController to display the old and new view controller
// In our case, we can compare the view controller `children` count against the the `viewControllers` count
// In other cases, we may need to examine `children` more closely against `viewControllers` to see if there discrepancies.
// Since this is on the animation callback we need to dispatch to the main thread to work around another bug: https://stackoverflow.com/a/24749239/2191796
DispatchQueue.main.async { [weak self] in
guard let self = self else {return}
if self.pageController.children.count != self.pageController.viewControllers?.count {
self.pageController.setViewControllers(self.pageController.viewControllers, direction: .forward, animated: false, completion: nil)
}
}
})
}

UINavigationBar below StatusBar after Hotspot/Call when using custom transition

I've a strange issue with the NavigationBar behind the Statusbar.
It only occurs when the default statusbar changes to an "active" statusbar like the one that appears during an active call or a wifi hotspot.
Before the "active" statusbar appears, it looks like this (which is perfectly fine):
When I enable the wifi hotspot it's still fine:
But when I disable the wifi hotspot or end a call the statusbar size shrinks back to its previous size but the ViewController (in this case a UITableViewController) doesn't move up again. It looks like it has a top margin of the statusbars' size. In addition the statusbar is transparent and I can see the background of the view controller below the actual table view controller.
Any ideas on this issue?
Update:
I figured out that it's because of a custom modal transition that I've implemented.
It should be a dissolve animation.
That's the code:
class DissolveTransition: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
// vars
private var duration: NSTimeInterval = 0.3
private var presenting = true
// MARK: - UIViewControllerAnimatedTransitioning
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return self.duration
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let destination = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
if (destination?.isBeingPresented() == true) {
self.animatePresentation(transitionContext)
}
else {
self.animateDismissal(transitionContext)
}
}
private func animatePresentation(transitionContext: UIViewControllerContextTransitioning) {
self.animateDissolve(transitionContext)
}
private func animateDismissal(transitionContext: UIViewControllerContextTransitioning) {
self.presenting = false
self.animateDissolve(transitionContext)
}
private func animateDissolve(transitionContext: UIViewControllerContextTransitioning) {
let source = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let destination = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let container = transitionContext.containerView()!
destination.beginAppearanceTransition(true, animated: true)
let snapshotFromView = source.view.snapshotViewAfterScreenUpdates(true)
// 1. adding real view at the bottom of the view hierarchy
if (self.presenting) {
container.addSubview(destination.view)
}
// 2. adding snapshot of previous view to view hierarchy
container.addSubview(snapshotFromView)
// 3. removing (fade) prev snapshot view and show real VC
UIView.animateWithDuration(self.duration, animations: {
snapshotFromView.alpha = 0.0
}, completion: { (finished) in
if (finished) {
snapshotFromView.removeFromSuperview()
container.bringSubviewToFront(destination.view)
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
destination.endAppearanceTransition()
})
}
// MARK: - UIViewControllerTransitioningDelegate
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
}
I found out that it was because of my custom modal transition that presented this view.
There is an odd bug in iOS that views inside the screen are not resized after the statusBar is changed. This also appears in many well-known Apps.
I fixed it by resizing the views when the statusbar-size changes.
Use the following code in your AppDelegate:
func application(application: UIApplication, willChangeStatusBarFrame newStatusBarFrame: CGRect) {
if (newStatusBarFrame.size.height < 40) {
if let window = self.window, subviews = self.window?.subviews {
for view in subviews {
UIView.animateWithDuration(0.3, animations: {
view.frame = window.bounds
})
}
}
}
}

Custom Alert (UIAlertView) with swift

How can i create a custom alert with Swift? I try translating a guide from Objective c but loads a full screen layout
for do it easy i can load a new layout with the transparent background i try this:
listaalertviewcontroller.view.backgroundColor = UIColor.clearColor()
let purple = UIColor.purpleColor() // 1.0 alpha
let semi = purple.colorWithAlphaComponent(0.5)
listaalertviewcontroller.view.backgroundColor = semi
presentingViewController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.presentViewController(listaalertviewcontroller, animated: true, completion: nil)
in the animation it's transparent but when the animation ends it's opaque... and i turn off opaque option in the view... what i'm doing wrong?
Code tested in Swift 5 and Xcode 10
How to make your own custom Alert
I was wanting to do something similar. First of all, UIAlertView is deprecated in favor of UIAlertController. See this answer for the standard way to display an alert:
How would I create a UIAlertView in Swift?
And both UIAlertView and UIAlertController do not really allow much customization. One option is to use some third party code. However, I discovered that it isn't that difficult to create your own Alert by displaying another view controller modaly.
The example here is just a proof-of-concept. You can design your alert any way you want.
Storyboard
You should have two View Controllers. Your second view controller will be your alert. Set the class name to AlertViewContoller and the Storyboard ID to alert. (Both of these are names that we defined ourselves in the code below, nothing special about them. You can add the code first if you want. It might actually be easier if you add the code first.)
Set the background color for the root view (in your Alert View Controller) to clear (or translucent black is nice for an alert). Add another UIView and center it with constraints. Use that as your alert background and put whatever you want inside. For my example, I added a UIButton.
Code
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBAction func showAlertButtonTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "alert")
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert, animated: true, completion: nil)
}
}
AlertViewController.swift
import UIKit
class AlertViewController: UIViewController {
#IBAction func dismissButtonTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
}
Don't forget to hook up the outlets.
You can add an onTouchUp event listener to the background view to dismiss the popup when the user clicks outside of it.
That's it. You should be able to make any sort of alert that you can imagine now. No need for third party code.
Here is another custom alert I made. Still ugly, but it shows more things you can do.
Other options
Sometimes there is no need to reinvent the wheel, though. I'm impressed with the third party project SDCAlertView (MIT license). It is written in Swift but you can use it with Objective-C projects as well. It offers a wide range of customability.
Here is the Swift 3 code. Thanks a lot #Suragch for the awesome approach to create a custom AlertView.
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBAction func showAlertButtonTapped(sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "storyboardID")
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert, animated: true, completion: nil)
}
AlertViewController.swift
import UIKit
class AlertViewController: UIViewController {
#IBAction func dismissButtonTapped(sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
}
To make it a little more interesting or to make the default effect in iOS, you could add either a VisualEffectView or change the color of the main UIView to a dark color and set its alpha to 70%. I prefer the second approach since the blur effect is not as smooth as the one with the view with 70 alpha.
Effect with VisualEffectView:
Effect using a UIView with 70 Alpha:
Nowadays, an alert is merely a simple presented view controller. You can write a presented view controller that behaves similarly to an alert — that is, it pops onto the screen and dims whatever is behind it — but it's your view controller and you are free to give it any interface you like.
To get you started, I've written a github project that you can download and run, and modify to suit your actual needs.
I'll show the key part of the code. The "alert" view controller, in its initializers, sets its own modal presentation style as custom and sets a transitioning delegate:
class CustomAlertViewController : UIViewController {
let transitioner = CAVTransitioner()
override init(nibName: String?, bundle: Bundle?) {
super.init(nibName: nibName, bundle: bundle)
self.modalPresentationStyle = .custom
self.transitioningDelegate = self.transitioner
}
convenience init() {
self.init(nibName:nil, bundle:nil)
}
required init?(coder: NSCoder) {
fatalError("NSCoding not supported")
}
}
All the work is done by the transitioning delegate:
class CAVTransitioner : NSObject, UIViewControllerTransitioningDelegate {
func presentationController(
forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController)
-> UIPresentationController? {
return MyPresentationController(
presentedViewController: presented, presenting: presenting)
}
}
class MyPresentationController : UIPresentationController {
func decorateView(_ v:UIView) {
// iOS 8 doesn't have this
// v.layer.borderColor = UIColor.blue.cgColor
// v.layer.borderWidth = 2
v.layer.cornerRadius = 8
let m1 = UIInterpolatingMotionEffect(
keyPath:"center.x", type:.tiltAlongHorizontalAxis)
m1.maximumRelativeValue = 10.0
m1.minimumRelativeValue = -10.0
let m2 = UIInterpolatingMotionEffect(
keyPath:"center.y", type:.tiltAlongVerticalAxis)
m2.maximumRelativeValue = 10.0
m2.minimumRelativeValue = -10.0
let g = UIMotionEffectGroup()
g.motionEffects = [m1,m2]
v.addMotionEffect(g)
}
override func presentationTransitionWillBegin() {
self.decorateView(self.presentedView!)
let vc = self.presentingViewController
let v = vc.view!
let con = self.containerView!
let shadow = UIView(frame:con.bounds)
shadow.backgroundColor = UIColor(white:0, alpha:0.4)
shadow.alpha = 0
con.insertSubview(shadow, at: 0)
shadow.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let tc = vc.transitionCoordinator!
tc.animate(alongsideTransition: { _ in
shadow.alpha = 1
}) { _ in
v.tintAdjustmentMode = .dimmed
}
}
override func dismissalTransitionWillBegin() {
let vc = self.presentingViewController
let v = vc.view!
let con = self.containerView!
let shadow = con.subviews[0]
let tc = vc.transitionCoordinator!
tc.animate(alongsideTransition: { _ in
shadow.alpha = 0
}) { _ in
v.tintAdjustmentMode = .automatic
}
}
override var frameOfPresentedViewInContainerView : CGRect {
// we want to center the presented view at its "native" size
// I can think of a lot of ways to do this,
// but here we just assume that it *is* its native size
let v = self.presentedView!
let con = self.containerView!
v.center = CGPoint(x: con.bounds.midX, y: con.bounds.midY)
return v.frame.integral
}
override func containerViewWillLayoutSubviews() {
// deal with future rotation
// again, I can think of more than one approach
let v = self.presentedView!
v.autoresizingMask = [
.flexibleTopMargin, .flexibleBottomMargin,
.flexibleLeftMargin, .flexibleRightMargin
]
v.translatesAutoresizingMaskIntoConstraints = true
}
}
extension CAVTransitioner { // UIViewControllerTransitioningDelegate
func animationController(
forPresented presented:UIViewController,
presenting: UIViewController,
source: UIViewController)
-> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(
forDismissed dismissed: UIViewController)
-> UIViewControllerAnimatedTransitioning? {
return self
}
}
extension CAVTransitioner : UIViewControllerAnimatedTransitioning {
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?)
-> TimeInterval {
return 0.25
}
func animateTransition(
using transitionContext: UIViewControllerContextTransitioning) {
let con = transitionContext.containerView
let v1 = transitionContext.view(forKey: .from)
let v2 = transitionContext.view(forKey: .to)
// we are using the same object (self) as animation controller
// for both presentation and dismissal
// so we have to distinguish the two cases
if let v2 = v2 { // presenting
con.addSubview(v2)
let scale = CGAffineTransform(scaleX: 1.6, y: 1.6)
v2.transform = scale
v2.alpha = 0
UIView.animate(withDuration: 0.25, animations: {
v2.alpha = 1
v2.transform = .identity
}) { _ in
transitionContext.completeTransition(true)
}
} else if let v1 = v1 { // dismissing
UIView.animate(withDuration: 0.25, animations: {
v1.alpha = 0
}) { _ in
transitionContext.completeTransition(true)
}
}
}
}
It looks like a lot of code, and I suppose it is, but it's almost entire confined to a single class, which is entirely boilerplate; just copy and paste. All you have to do is write the internal interface and behavior of your "alert" view controller, giving it buttons and text and whatever else you want, just as you would do for any other view controller.
Custom Alert UIView Class in swift 4. And Usage ##
import UIKit
class Dialouge: UIView {
#IBOutlet weak var lblTitle: UILabel!
#IBOutlet weak var lblDescription: UILabel!
#IBOutlet weak var btnLeft: UIButton!
#IBOutlet weak var btnRight: UIButton!
#IBOutlet weak var viewBg: UIButton!
var leftAction = {}
var rightAction = {}
override func draw(_ rect: CGRect)
{
self.btnRight.layer.cornerRadius = self.btnRight.frame.height/2
self.btnLeft.layer.cornerRadius = self.btnLeft.frame.height/2
self.btnLeft.layer.borderWidth = 1.0
self.btnLeft.layer.borderColor = #colorLiteral(red: 0.267678082, green: 0.2990377247, blue: 0.7881471515, alpha: 1)
}
#IBAction func leftAction(_ sender: Any) {
leftAction()
}
#IBAction func rightAction(_ sender: Any) {
rightAction()
}
#IBAction func bgTapped(_ sender: Any) {
self.removeFromSuperview()
}
}
strong text
## Usage Of Custom Alert with Tabbar.
let custView = Bundle.main.loadNibNamed("Dialouge", owner: self, options:
nil)![0] as? Dialouge
custView?.lblDescription.text = "Are you sure you want to delete post?"
custView?.lblTitle.text = "Delete Post"
custView?.btnLeft.setTitle("Yes", for: .normal)
custView?.btnRight.setTitle("No", for: .normal)
custView?.leftAction = {
self.deletePost(postId: self.curr_post.id,completion: {
custView?.removeFromSuperview()
})
}
custView?.rightAction = {
custView?.removeFromSuperview()
}
if let tbc = self.parentt?.tabBarController {
custView?.frame = tbc.view.frame
DispatchQueue.main.async {
tbc.view.addSubview(custView!)
}
}else if let tbc = self.parView?.parenttprof {
custView?.frame = tbc.view.frame
DispatchQueue.main.async {
tbc.view.addSubview(custView!)
}
}
else
{
custView?.frame = self.parView?.view.frame ?? CGRect.zero
DispatchQueue.main.async {
self.parView?.view.addSubview(custView!)
}
}
Use https://github.com/shantaramk/Custom-Alert-View
It is effortless to implement this. Follow the steps below:
Drag down the AlertView folder in project directory
Show AlertView Popup
func showUpdateProfilePopup(_ message: String) {
let alertView = AlertView(title: AlertMessage.success, message: message, okButtonText: LocalizedStrings.okay, cancelButtonText: "") { (_, button) in
if button == .other {
self.navigationController?.popViewController(animated: true)
}
}
alertView.show(animated: true)
}

Resources