iOS 13 modalPresentationStyle = UIModalPresentationFullScreen not working for MFMessageComposeViewController - swift

As we all know from the iOS 13+ modal presentation style for normal UIViewController will default to .pageSheet. If you want to change it you can change it to your's desired style when presenting it. I am using MFMailComposeViewController and MFMessageComposeViewController in my app to share content. In case of MFMailComposeViewController when i choose modalPresentationStyle = .fullScreen it works perfectly fine...
but not in case of MFMessageComposeViewController. Please find the code snippet below
if (MFMessageComposeViewController.canSendText()) {
let controller = MFMessageComposeViewController()
controller.body = “Message Body”
controller.messageComposeDelegate = self
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil)
self.trackEvent(shareType: "SMS")
}
}

While I can't answer your question on *why you cant use .fullScreen for MFMessageComposeViewController, I can tell you that if you implement your own custom transition you will get the desired behavior.
This is how you set custom transition for the view controller :
var customTransitionDelegate = CustomTransition(presentAnimation: CustomActionSheetPresentationTransition(), dismissAnimation: CustomActionSheetDismissalTransition())
if (MFMessageComposeViewController.canSendText()) {
let controller = MFMessageComposeViewController()
controller.body = "Message Body"
controller.messageComposeDelegate = self
controller.modalPresentationStyle = .custom
controller.transitioningDelegate = customTransitionDelegate
self.present(controller, animated: true, completion: nil)
}
You should also conform to the delegate and check the result to dismiss it after the user clicked cancel if he did :
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
switch result {
case .cancelled:
self.presentedViewController?.dismiss(animated: true, completion: nil)
default: break
}
}
The presentation logic
class CustomActionSheetPresentationTransition: NSObject, UIViewControllerAnimatedTransitioning {
private let duration = 0.6
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.viewController(forKey: .from) else { return }
guard let toView = transitionContext.viewController(forKey: .to) else { return }
var screenOffUp = CGAffineTransform()
let container = transitionContext.containerView
screenOffUp = CGAffineTransform(translationX: 0, y: -fromView.view.frame.height)
toView.view.frame = CGRect(x: 0, y: UIScreen.main.bounds.height, width: fromView.view.frame.width, height: fromView.view.frame.height)
toView.view.center.x = container.center.x
container.addSubview(toView.view)
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: {
toView.view.transform = screenOffUp
}, completion: { (success) in
transitionContext.completeTransition(success)
})
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
}
The dismissal logic
class CustomActionSheetDismissalTransition: NSObject, UIViewControllerAnimatedTransitioning {
private let duration = 0.8
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.viewController(forKey: .from) else { return }
let screenOffDown = CGAffineTransform(translationX: 0, y: fromView.view.frame.height)
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: {
fromView.view.transform = screenOffDown
}, completion: { (success) in
transitionContext.completeTransition(success)
})
}
}

Related

UIViewController + NSNotification extension protocol based #objc issue

I use this example to avoid keyboard frame when it appears. Now I want to make it protocol based to avoid extending all view controllers with this functionality. Now I am getting two errors which looks like conflict each other (one is want to add #objc and another one remove it):
My Code:
import UIKit
protocol KeyboardFrameIgnorable {
func listenForKeyboardFrameChange()
func stopListenKeyboardFrameChange()
}
extension KeyboardFrameIgnorable where Self: UIViewController {
func listenForKeyboardFrameChange()
{
NotificationCenter.default
.addObserver(self,
selector: #selector(onKeyboardFrameWillChangeNotificationReceived(_:)),
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}
func stopListenKeyboardFrameChange()
{
NotificationCenter.default
.removeObserver(self,
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}
#objc
private func onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification)
{
guard
let userInfo = notification.userInfo,
let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
else {
return
}
let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
let intersection = safeAreaFrame.intersection(keyboardFrameInView)
let keyboardAnimationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]
let animationDuration: TimeInterval = (keyboardAnimationDuration as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw)
UIView.animate(withDuration: animationDuration,
delay: 0,
options: animationCurve,
animations: {
self.additionalSafeAreaInsets.bottom = intersection.height + 10
self.view.layoutIfNeeded()
}, completion: nil)
}
}

Massive CPU overuse whenever I push ViewController - Swift - Programmatically

I have a high CPU level whenever I push a new ViewController from the navigationController.
I used the Time Profiler tool and it turns out that the issue that is causing this CPU overuse is related to a ViewController I embedded in the rootViewController.
The ViewController I'm talking about is as follows:
class QuoteGeneratorController : UIViewController {
let interactiveShowTextView : UITextView = {
let textView = UITextView()
textView.font = UIFont(name: "avenir-black", size: 35)
textView.textColor = .white
textView.textAlignment = .left
textView.textContainer.maximumNumberOfLines = 3
textView.backgroundColor = .clear
textView.isEditable = false
textView.isSelectable = false
textView.isUserInteractionEnabled = false
return textView
}()
let progressBar : UIProgressView = {
let progressView = UIProgressView(progressViewStyle: .bar)
progressView.trackTintColor = UIColor.clear
progressView.tintColor = UIColor.white
return progressView
}()
let quotesArray = ["Hello", "Hello2", "Hello3"]
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("I'm called")
view.subviews.forEach { subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = progressBar.frame.height / 2.0
}
}
override func viewDidAppear(_ animated: Bool) {
setUpAnimation()
}
override func viewDidLoad() {
setUpUI()
setUpAnimation()
}
fileprivate func setUpUI(){
view.addSubview(interactiveShowTextView)
view.addSubview(progressBar)
interactiveShowTextView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor)
interactiveShowTextView.heightAnchor.constraint(equalToConstant: 150).isActive = true
progressBar.anchor(top: interactiveShowTextView.bottomAnchor, leading: interactiveShowTextView.leadingAnchor, bottom: nil, trailing: nil, padding: .init(top: 10, left: 0, bottom: 0, right: 0), size: .init(width: 70, height: 9))
}
var iterator : Int = 0
fileprivate func setUpAnimation(){
switch iterator {
case 0:
animateAndIterate()
case 1:
animateAndIterate()
case 2:
animateAndIterate()
default:
self.iterator = 0
self.setUpAnimation()
}
}
fileprivate func animateAndIterate(){
UIView.animate(withDuration: 0.0, animations: {
self.progressBar.layoutIfNeeded()
}, completion: { finished in
self.progressBar.progress = 1.0
self.interactiveShowTextView.text = self.quotesArray[self.iterator]
self.interactiveShowTextView.fadeIn()
UIView.animate(withDuration: 3, delay: 0.0, options: [.curveLinear], animations: {
self.progressBar.layoutIfNeeded()
self.perform(#selector(self.afterAnimation), with: nil, afterDelay: 2.5)
}, completion: { finished in
self.interactiveShowTextView.fadeOut()
self.progressBar.progress = 0
self.iterator = self.iterator + 1
self.setUpAnimation()
})
})
}
#objc func afterAnimation() {
self.interactiveShowTextView.fadeOut()
}
}
I actually don't know what could have caused the issue and since I'm new to time profiler I though that some of you have encountered the same process through development.
In Time Profiler I'm getting this indications:
When using completion blocks it's recommended to use weak references to the result. Otherwise the ARC will count every reference you used inside those block.
Use closures like this:
view.subviews.forEach { [weak self] subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = progressBar.frame.height / 2.0
}
// (...)
fileprivate func animateAndIterate() {
UIView.animate(withDuration: 0.0,
animations: { [weak self] in
self?.progressBar.layoutIfNeeded()
}, completion: { [weak self] finished in
self?.progressBar.progress = 1.0
self?.interactiveShowTextView.text = self.quotesArray[self.iterator]
self?.interactiveShowTextView.fadeIn()
UIView.animate(withDuration: 3,
delay: 0.0,
options: [.curveLinear],
animations: { [weak self] in
self?.progressBar.layoutIfNeeded()
self?.perform(#selector(self.afterAnimation),
with: nil, afterDelay: 2.5)
}, completion: { finished in
self?.interactiveShowTextView.fadeOut()
self?.progressBar.progress = 0
self?.iterator = self.iterator + 1
self?.setUpAnimation()
})
})
}

MKAnnotationView image toggling's transition issue

I have 2 image states for the MKAnnotationView, which are selected and deselected. The issue is that the transition between these two states is poor. There isn't much online about this and I'm having trouble with transitions, generally.
Here is the MKAnnotationView I am using:
class CustomPinView: MKAnnotationView {
func updateImage() {
guard let mapAnnotation = annotation as? MapAnnotation else {return}
if let selectedImageName = mapAnnotation.selectedImageName, isSelected {
image = UIImage(inCurrentBundleWithName: selectedImageName)
} else if let imageName = mapAnnotation.imageName {
image = UIImage(inCurrentBundleWithName: imageName)
} else {
image = nil
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
updateImage()
}
override var annotation: MKAnnotation? {
didSet {
updateImage()
}
}
}
I updated the updateImage function to look like this:
private func updateImage() {
CATransaction.begin()
CATransaction.setAnimationDuration(0.2)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeOut))
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn, animations: {
guard let mapAnnotation = self.annotation as? MapAnnotation else {return}
if let selectedImageName = mapAnnotation.selectedImageName, self.isSelected {
self.image = UIImage(inCurrentBundleWithName: selectedImageName)
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.6)
} else if let imageName = mapAnnotation.imageName {
self.image = UIImage(inCurrentBundleWithName: imageName)
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
} else {
self.image = nil
}
self.centerOffset = CGPoint(x: 0.5, y: 0.5)
}, completion: nil)
CATransaction.commit()
}
This is extracted from this answer here: https://stackoverflow.com/a/54431257/4114335

Warning: Attempt to present error when trying to present UIAlertController on Custom

Warning: Attempt to present on
whose view is not in the window
hierarchy!
I have a custom presented view controller and would like to present UIAlertController on top of it. When presenting without custom transition everything works fine.
Tried adding definesPresentationContext = true without luck
Maybe my custom transition should include something like addChildViewController() ?
First VC
let adVC = AdViewController(with: adView)
adVC.setupAd(with: index)
let adNav = UINavigationController(rootViewController: adVC)
adNav.modalPresentationStyle = .custom
adNav.transitioningDelegate = adVC.adCustomTransition
self.present(adNav, animated: true, completion: nil)
AdViewController
class AdViewController: UIViewController {
var adCustomTransition = AdCustomTransition()
override func viewDidLoad() {
super.viewDidLoad()
definesPresentationContext = true;
submitButton.addTarget(self, action: #selector(presentAlert), for: .touchUpInside)
}
func presentAlert() {
DispatchQueue.main.async {
let alertVC = UIAlertController(title: "Alert!", message: "test alert message", preferredStyle: .alert)
alertVC.view.tintColor = .main
alertVC.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
self.present(alertVC, animated: true, completion: nil)
}
}
}
class AdCustomTransition : NSObject, UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AdAnimationController(withDuration: 0.4, forTransitionType: .present)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AdAnimationController(withDuration: 0.4, forTransitionType: .dismiss)
}
}
class AdAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case present, dismiss
}
var duration: TimeInterval
var isPresenting: Bool
init(withDuration duration: TimeInterval, forTransitionType type: TransitionType) {
self.duration = duration
self.isPresenting = type == .present
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return self.duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toFrame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!)
if isPresenting == true {
let toVC = (transitionContext.viewController(forKey: .to) as! MainNavigationController).viewControllers.first as! AdViewController
containerView.addSubview(toVC.view)
containerView.layoutIfNeeded()
let bottomHeight = toVC.getSafeBottomHeight()
let offset = toVC.containerView.bounds.height - 120 - bottomHeight
toVC.containerView.transform = CGAffineTransform(translationX: 0, y: offset)
UIView.animate(withDuration: self.duration, delay: 0, usingSpringWithDamping: 5.4, initialSpringVelocity: 0.8, options: .curveEaseInOut, animations: {
toVC.containerView.transform = CGAffineTransform(translationX: 0, y: -1 * bottomHeight)
}, completion: { (_) in
toVC.view.frame = toFrame
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
} else {
let fromVC = (transitionContext.viewController(forKey: .from) as! MainNavigationController).viewControllers.first as! AdViewController
let toVC = transitionContext.viewController(forKey: .to)!
containerView.addSubview(toVC.view)
containerView.addSubview(fromVC.view)
containerView.layoutIfNeeded()
let bottomHeight = fromVC.getSafeBottomHeight()
let offset = fromVC.containerView.bounds.height - 110 - bottomHeight
fromVC.containerView.transform = .identity
UIView.animate(withDuration: self.duration, delay: 0, usingSpringWithDamping: 5.7, initialSpringVelocity: 0.8, options: .curveEaseInOut, animations: {
fromVC.containerView.transform = CGAffineTransform(translationX: 0, y: offset)
}, completion: { (_) in
toVC.view.frame = toFrame
UIApplication.shared.keyWindow?.addSubview(toVC.view)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
}

interactive viewController using pan Gesture

everyone i've been tearing out my hair trying to find a solution to an interactive view controller transition where you use the pan gesture in the downward direction to bring a full screen view controller from the top to the bottom. Has anyone run across or created any code like this. Below is my code. I already have the dismiss gesture down but cant figure out how to present the view controller by swiping down on the screen. PLEASE HELP!!!
import UIKit
class ViewController: UIViewController {
let interactor = Interactor()
var interactors:Interactor? = nil
let Mview = ModalViewController()
let mViewT: ModalViewController? = nil
var presentedViewControllers: UIViewController?
override func viewDidLoad() {
Mview.transitioningDelegate = self
Mview.modalPresentationStyle = .FullScreen
}
#IBAction func cameraSlide(sender: UIPanGestureRecognizer) {
let percentThreshold:CGFloat = 0.3
// convert y-position to downward pull progress (percentage)
let translation = sender.translationInView(Mview.view)
let verticalMovement = translation.y / UIScreen.mainScreen().bounds.height
let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
let downwardMovementPercent = fminf(downwardMovement, 1.0)
let progress = CGFloat(downwardMovementPercent)
guard let interactor = interactors else { return }
switch sender.state {
case .Began:
interactor.hasStarted = true
self.presentViewController(Mview, animated: true, completion: nil)
case .Changed:
interactor.shouldFinish = progress > percentThreshold
interactor.updateInteractiveTransition(progress)
case .Cancelled:
interactor.hasStarted = false
interactor.cancelInteractiveTransition()
case .Ended:
interactor.hasStarted = false
if !interactor.shouldFinish {
interactor.cancelInteractiveTransition()
} else {
interactor.finishInteractiveTransition()
} default:
break
}
}
}
extension ViewController: UIViewControllerTransitioningDelegate {
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DismissAnimator()
}
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactor.hasStarted ? interactor : nil
}
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return PresentAnimator()
}
func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactor.hasStarted ? interactor : nil
}
}
class PresentAnimator: NSObject {
}
extension PresentAnimator: UIViewControllerAnimatedTransitioning
{
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 1.0
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC2 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),
let toVC2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey),
let containerView2 = transitionContext.containerView() else {return}
let initialFrame = transitionContext.initialFrameForViewController(fromVC2)
toVC2.view.frame = initialFrame
toVC2.view.frame.origin.y = -initialFrame.height * 2
containerView2.addSubview(fromVC2.view)
containerView2.addSubview(toVC2.view)
let screenbounds = UIScreen.mainScreen().bounds
let Stage = CGPoint(x: 0, y: 0)
let finalFrame = CGRect(origin: Stage, size: screenbounds.size)
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
toVC2.view.frame = finalFrame
}, completion: { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
)
}
}
class ModalViewController: UIViewController {
let interactors = Interactor()
var interactor:Interactor? = nil
#IBAction func close(sender: UIButton) {
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func handleGesture(sender: UIPanGestureRecognizer) {
let percentThreshold:CGFloat = 0.3
// convert y-position to downward pull progress (percentage)
let translation = sender.translationInView(self.view)
let verticalMovement = translation.y / -view.bounds.height * 2
let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
let downwardMovementPercent = fminf(downwardMovement, 1.0)
let progress = CGFloat(downwardMovementPercent)
guard let interactor = interactor else { return }
switch sender.state {
case .Began:
interactor.hasStarted = true
dismissViewControllerAnimated(true, completion: nil)
case .Changed:
interactor.shouldFinish = progress > percentThreshold
interactor.updateInteractiveTransition(progress)
case .Cancelled:
interactor.hasStarted = false
interactor.cancelInteractiveTransition()
case .Ended:
interactor.hasStarted = false
if !interactor.shouldFinish {
interactor.cancelInteractiveTransition()
} else {
interactor.finishInteractiveTransition()
} default:
break
}
}
}
import UIKit
class DismissAnimator: NSObject {
}
extension DismissAnimator : UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 1.0
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey),
let containerView = transitionContext.containerView()
else {
return
}
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
let screenBounds = UIScreen.mainScreen().bounds
let topLeftCorner = CGPoint(x: 0, y: -screenBounds.height * 2)
let finalFrame = CGRect(origin: topLeftCorner, size: screenBounds.size)
UIView.animateWithDuration(
transitionDuration(transitionContext),animations: {fromVC.view.frame = finalFrame},
completion: { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
)
}
}
If you want a simple Pan Gesture to switch between UIViewControllers, you can check out this:
http://www.appcoda.com/custom-segue-animations/
If you want it to be interactive, as in you can go back and forth between VCs without having to complete the whole transition, I suggest you check out this:
https://www.youtube.com/watch?v=3jAlg5BnYUU
If you want to go even further and have a custom dismissing animation, then look no further than this:
https://www.raywenderlich.com/110536/custom-uiviewcontroller-transitions