Spinner don't want to remove from superview - ios

I have a UIActivityIndicatorView so when I try to search in search bar, It will loading first before the data come in. I make the spinner in UIViewController extension for animating and remove the animating from the view. but somehow I can't figure it out why the view I want to remove is not removing, when I try to use break point. the function dismissLoading I create is not being called, why it happen? can someone help me. this is my code
fileprivate var containerView: UIView!
extension UIViewController {
func showLoadingView() {
containerView = UIView(frame: view.bounds)
view.addSubview(containerView)
containerView.backgroundColor = .systemBackground
containerView.alpha = 0.0
UIView.animate(withDuration: 0.25) { containerView.alpha = 0.8 }
let activityIndicator = UIActivityIndicatorView(style: .large)
containerView.addSubview(activityIndicator)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor),
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
activityIndicator.startAnimating()
}
func dismissLoadingView() {
DispatchQueue.main.async {
containerView.removeFromSuperview()
containerView = nil
}
}
}
// This is my PhotosViewController
extension PhotosViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text, !searchText.isEmpty else {
dataSource.data.value.removeAll()
viewModel.isSearching.value = false
return
}
photoName = searchText
viewModel.isSearching.value = true
showLoadingView()
if let photoName = photoName {
viewModel.getPhoto(query: photoName) { [weak self] error in
self?.dismissLoadingView()
print(error)
}
}
}
}

You're not seeing it disappear because you're adding a new instance of the container view and activity indicator every time the search query changes. If the query updates a second time before the first query has returned results, the reference to your first containerView will be reassigned to a new view instance.
If you attempt to fix this by checking if containerView == nil before presenting, you will also run into issues if you have multiple search results requests out, one returns, and then the loading view gets dismissed despite there being other requests out.

Related

Swift: Bring custom alert view over nav bar

I use UIView as alert view in my app, and i want to show it as banner on top of screen, when device is not connected to internet. So my issue that this view appears under my nav bar, how can i bring it to front ? I've tried to us UIApplication.shared.keyWindow! and add my backgroundView as subview to it, but it causes other issues.
This is my alert view class: I'll provide all class, but my realisation is in show() method.
import Foundation
import UIKit
import SnapKit
class ConnectionAlertView: UIView, UIGestureRecognizerDelegate {
internal var backgroundView: UIView = {
let view = UIView()
view.backgroundColor = Theme.Color.alertLabelBackgroundColor
view.alpha = 0
view.layer.cornerRadius = 15
return view
}()
internal var dismissButton: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "close_icon")
imageView.layer.cornerRadius = 15
imageView.isUserInteractionEnabled = true
return imageView
}()
internal var descriptionTitleLabel: UILabel = {
let label = UILabel()
label.text = "Відсутнє підключення до Інтернету"
label.font = Theme.Font.fontBodyLarge
label.textColor = .white
return label
}()
internal var descriptionLabel: UILabel = {
let label = UILabel()
label.text = "Перевірте налаштування мережі"
label.font = Theme.Font.fontBodyMedium
label.textColor = .white
return label
}()
// MARK: - Private Methods -
internal func layout() {
backgroundView.addSubview(descriptionTitleLabel)
backgroundView.addSubview(descriptionLabel)
backgroundView.addSubview(dismissButton)
descriptionTitleLabel.snp.makeConstraints { make in
make.trailing.equalTo(backgroundView).offset(54)
make.leading.equalTo(backgroundView).offset(16)
make.top.equalTo(backgroundView).offset(12)
}
descriptionLabel.snp.makeConstraints { make in
make.leading.equalTo(descriptionTitleLabel.snp.leading)
make.top.equalTo(descriptionTitleLabel.snp.bottom).offset(4)
}
dismissButton.snp.makeConstraints { make in
make.width.height.equalTo(30)
make.centerY.equalTo(backgroundView)
make.trailing.equalTo(backgroundView).offset(-16)
}
}
internal func configure() {
let tap = UITapGestureRecognizer(target: self, action: #selector(dismiss(sender:)))
tap.delegate = self
dismissButton.addGestureRecognizer(tap)
}
// MARK: - Public Methods -
func show(viewController: UIViewController) {
guard let targetView = viewController.view else { return }
backgroundView.frame = CGRect(x: 10, y: 50, width: targetView.frame.width - 20 , height: 67)
targetView.addSubview(targetView)
layout()
configure()
// show view
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
UIView.transition(with: self.backgroundView, duration: 0.6,
options: .transitionCrossDissolve,
animations: {
self.backgroundView.alpha = 1
})
}
// hide view after 5 sec delay
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
UIView.transition(with: self.backgroundView, duration: 1,
options: .transitionCrossDissolve,
animations: {
self.backgroundView.alpha = 0
})
}
}
// MARK: - Objc Methods -
#objc internal func dismiss(sender: UITapGestureRecognizer) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
UIView.transition(with: self.backgroundView, duration: 1,
options: .transitionCrossDissolve,
animations: {
self.backgroundView.alpha = 0
})
}
}
}
My viewController:
class PhoneNumViewController: UIViewController {
let alert = ConnectionAlertView()
private func checkInternetConnection() {
if !NetworkingMonitor.isConnectedToInternet {
log.error("No internet connection!")
alert.show(viewController: self)
}
}
}
Since you have a navigation controller and do not wish to add this view to the window directly, I can offer the following idea which could work.
Your UIViewController is contained with the UINavigationController so if you add the alert to your UIViewController, you will notice it below the UINavigationBar.
You could instead show the alert from your UINavigationController instead with the following changes.
1.
In the func show(viewController: UIViewController) in your class ConnectionAlertView: UIView I changed the following line:
targetView.addSubview(targetView)
to
targetView.addSubview(backgroundView)
This does not directly relate to your issue but seems to be a bug and causes a crash as it seems like you want to add the background view on the target view.
2.
In your class ViewController: UIViewController, when you want to show your alert view, pass the UINavigationController instead like this:
if let navigationController = self.navigationController
{
alert.show(viewController: navigationController)
}
This should give you the desired result I believe (The image and font looks different as I do not have these files but should work fine at your end):

How to fix subview not showing in child view controller when added to a parent view controller

I'm trying to add a child view controller to a parent view controller in a swift ios application, but when I add the child view controller, the activityIndicatorView doesn't appear. What could I be missing?
Here is a snippet that can be tried in a playground:
import PlaygroundSupport
import Alamofire
class LoadingViewController: UIViewController {
private lazy var activityIndicator = UIActivityIndicatorView(style: .gray)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// We use a 0.5 second delay to not show an activity indicator
// in case our data loads very quickly.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) { [weak self] in
self?.activityIndicator.startAnimating()
}
}
}
// methods for adding and removing child view controllers.
extension UIViewController {
func add(_ child: UIViewController, frame: CGRect? = nil) {
addChild(child)
if let frame = frame {
child.view.frame = frame
}
view.addSubview(child.view)
child.didMove(toParent: self)
}
func remove() {
// Just to be safe, we check that this view controller
// is actually added to a parent before removing it.
guard parent != nil else {
return
}
willMove(toParent: nil)
view.removeFromSuperview()
removeFromParent()
}
}
class MyViewController : UITabBarController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.view = view
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let loadingViewController = LoadingViewController()
add(loadingViewController, frame: view.frame)
AF.request("http://www.youtube.com").response { response in
print(String(describing: response.response))
loadingViewController.remove()
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
The loadingViewController view fills the parent view, and i've tried to change the background colour at different points and that works. but activityIndicator or any other subview i try to add just doesn't appear.
Try add the line
activityIndicator.startAnimating()
in your viewDidLoad() method from your LoadingViewController class

Loading animation stucked/freezed when webview loads

I load the progress loading animation and when the response from Alamofire comes I use part of the response to construct the full url I need to load in the wkwebview and then I trigger webview.load(..).
My problem is that the progress loading animation gets stuck as soon as webview.load(..) starts to happen and remain stuck till I hide() it.
How can I actually have my animation to keep moving meanwhile the webview starts loading the page?
MyViewController.swift
class MyViewController: UIViewController, WKScriptMessageHandler {
var webView: WKWebView?
#IBOutlet weak var webViewContainer: UIView!
var webConfig:WKWebViewConfiguration {
get {
let webCfg:WKWebViewConfiguration = WKWebViewConfiguration()
let userController:WKUserContentController = WKUserContentController()
userController.add(self, name: "mycontroller")
webCfg.userContentController = userController;
return webCfg;
}
}
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView (frame: webViewContainer.bounds, configuration: webConfig)
webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView?.scrollView.isScrollEnabled = false
webViewContainer.addSubview(webView!)
loadWebview()
}
func loadWebview(){
Loading.shared.show(self.view)
Alamofire.request(MYAPI, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil)
.responseJSON { response in
let url = URL(string: "https://path-to-load/\(response.key)")
self.webView!.load(URLRequest(url: url!))
}
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let message = message.body as! [String:AnyObject]
let event = message["event"] as? String ?? "empty"
switch (event){
case "loading-finished":
DispatchQueue.main.async {
Loading.shared.hide(animated: true)
}
break
default:
break
}
}
}
Loading.swift
public class Loading {
var blurredEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
let imageView = UIImageView(image: UIImage(named: "loading_image"))
class var shared: Loading {
struct LoadingStatic {
static let instance: Loading = Loading()
}
return LoadingStatic.instance
}
init() {
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
imageView.contentMode = .scaleToFill
blurredEffectView.contentView.addSubview(imageView)
}
public func show(_ view: UIView, inView: Bool = false) {
var window: UIView!
if inView == false, let w = view.window {
window = w
} else {
window = view
}
if blurredEffectView.superview == window {
return
}
let rotation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = NSNumber(value: Double.pi * 2)
rotation.duration = 1
rotation.isCumulative = true
rotation.repeatCount = Float.greatestFiniteMagnitude
imageView.layer.add(rotation, forKey: "rotationAnimation")
imageView.center = window.center
blurredEffectView.frame = window.bounds
window.addSubview(blurredEffectView)
blurredEffectView.fadeIn()
}
}
Possible solutions:
1) Make the loading happens in Application Window when you want a full screen Loading (inview == false by default) and keep loadWebview() in viewDidLoad
public func show(_ view: UIView? = nil, inView: Bool = false) {
var window: UIView!
if inView == false, let w = UIApplication.shared.delegate?.window {
window = w
} else {
window = view
}
...
2) Move loadWebview() from viewDidLoad to viewDidAppear
The important part that is moving here is Loading.shared.show(self.view). I wasn't able to animate the components of my view until it has finished laying out (which happens exactly in viewDidAppear()).
More details: viewDidLoad is called after the MyViewController has been initialised and has initialised the main view but before the view is presented to the UI. This means that viewDidLoad allows me to setup the ViewController and the View, before it is shown to the user for interactions.
Although it was a nice and quick solution, in some situation this may not work as expected as viewDidAppear can be called twice, and hence showing the loading view twice which will result in a weird ux.

tableView.setContentOffset not working on UITableView embedded in UIViewcontroller

I had this code in a UITableViewController and it worked perfectly.
func setupSearchBar() {
let searchBar: UISearchBar = searchController.searchBar
tableView.tableHeaderView = searchBar
let point = CGPoint(x: 0, y: searchBar.frame.size.height)
tableView.setContentOffset(point, animated: true
}
Now I'm refactoring my code to fit more of an MVC style architecture. What I did is create a UITableView in the View class:
class View: UIView {
lazy var tableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
func configureView() {
// tableView
addSubview(tableView)
tableView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
tableView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
}
}
and then use the View class in my ViewController:
class ViewController: UIViewController {
var newView: View! { return self.view as! View }
override func loadView() {
view = View(frame: UIScreen.main.bounds)
newView.configureView()
}
override func viewDidLoad() {
super.viewDidLoad()
setupSearchBar()
}
func setupSearchBar() {
let searchBar: UISearchBar = searchController.searchBar
newView.tableView.tableHeaderView = searchBar
let point = CGPoint(x: 0, y: searchBar.frame.size.height)
newView.tableView.setContentOffset(point, animated: true)
}
The tableView shows up no problem and everything else is fine. The only thing that's not working is the setContentOffset is being called, but it's not offsetting the content. I want the searchbar to be hidden by default when the user first opens this viewController (similar to iMessage), but after I moved the code from a UITableViewController to separate files (UIView + UIViewController) like in this example, the searchbar always shows by default.
I'm not sure why it's not working. Any help would be greatly appreciated.
It's probably a timing problem relative to layout. Instead of calling setUpSearchBar in viewDidLoad, do it later, in viewDidLayoutSubviews, when initial layout has actually taken place. This method can be called many times, so use a flag to prevent it from being called more than once:
var didSetUp = false
override func viewDidLayoutSubviews() {
if !didSetUp {
didSetUp = true
setUpSearchBar()
}
}
Also: Your animated value is wrong:
newView.tableView.setContentOffset(point, animated: true)
You mean false. You don't want this movement to be visible. The table view should just appear with the search bar out of sight.

Sharing an Image between two viewControllers during a transition animation

I have came across really cool transitions between viewControllers since UIViewControllerAnimatedTransitioning protocol was made available in IOS 7. Recently I noticed a particularly interesting one in Intacart's IOS app.
Here is the animation I am talking about in slow motion:
https://www.dropbox.com/s/p2hxj45ycq18i3l/Video%20Oct%2015%2C%207%2023%2059%20PM.mov?dl=0
First I thought it was similar to what the author walks through in this tutorial, with some extra fade-in and fade-out animations: http://www.raywenderlich.com/113845/ios-animation-tutorial-custom-view-controller-presentation-transitions
But then if you look at it closely, it seems like the product image transitions between the two viewControllers as the first viewController fades out. The reason why I think there are two viewControllers is because when you swipe the new view down, you can still see the original view behind it with no layout changes.
Maybe two viewControllers actually have the product image (not faded out) and are somehow animating at the same time with perfect precision and one of them fades in as the other fades out.
What do you think is actually going on there?
How is it possible to program such a transition animation that it looks like an image is shared between two viewControllers?
Here is what we did in order to achieve floating screenshot of the view during animated transition (Swift 4):
Idea behind:
Source and destination view controllers conforms to InterViewAnimatable protocol. We are using this protocol to find source and destination views.
Then we creating snapshots of those views and hiding them. Instead, at the same position snapshots are shown.
Then we animating snapshots.
At the end of transition we unhiding destination view.
As result it looks like source view is flying to destination.
File: InterViewAnimation.swift
// TODO: In case of multiple views, add another property which will return some unique string (identifier).
protocol InterViewAnimatable {
var targetView: UIView { get }
}
class InterViewAnimation: NSObject {
var transitionDuration: TimeInterval = 0.25
var isPresenting: Bool = false
}
extension InterViewAnimation: UIViewControllerAnimatedTransitioning {
func transitionDuration(using: UIViewControllerContextTransitioning?) -> TimeInterval {
return transitionDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard
let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else {
transitionContext.completeTransition(false)
return
}
guard let fromTargetView = targetView(in: fromVC), let toTargetView = targetView(in: toVC) else {
transitionContext.completeTransition(false)
return
}
guard let fromImage = fromTargetView.caSnapshot(), let toImage = toTargetView.caSnapshot() else {
transitionContext.completeTransition(false)
return
}
let fromImageView = ImageView(image: fromImage)
fromImageView.clipsToBounds = true
let toImageView = ImageView(image: toImage)
toImageView.clipsToBounds = true
let startFrame = fromTargetView.frameIn(containerView)
let endFrame = toTargetView.frameIn(containerView)
fromImageView.frame = startFrame
toImageView.frame = startFrame
let cleanupClosure: () -> Void = {
fromTargetView.isHidden = false
toTargetView.isHidden = false
fromImageView.removeFromSuperview()
toImageView.removeFromSuperview()
}
let updateFrameClosure: () -> Void = {
// https://stackoverflow.com/a/27997678/1418981
// In order to have proper layout. Seems mostly needed when presenting.
// For instance during presentation, destination view does'n account navigation bar height.
toVC.view.setNeedsLayout()
toVC.view.layoutIfNeeded()
// Workaround wrong origin due ongoing layout process.
let updatedEndFrame = toTargetView.frameIn(containerView)
let correctedEndFrame = CGRect(origin: updatedEndFrame.origin, size: endFrame.size)
fromImageView.frame = correctedEndFrame
toImageView.frame = correctedEndFrame
}
let alimationBlock: (() -> Void)
let completionBlock: ((Bool) -> Void)
fromTargetView.isHidden = true
toTargetView.isHidden = true
if isPresenting {
guard let toView = transitionContext.view(forKey: .to) else {
transitionContext.completeTransition(false)
assertionFailure()
return
}
containerView.addSubviews(toView, toImageView, fromImageView)
toView.frame = CGRect(origin: .zero, size: containerView.bounds.size)
toView.alpha = 0
alimationBlock = {
toView.alpha = 1
fromImageView.alpha = 0
updateFrameClosure()
}
completionBlock = { _ in
let success = !transitionContext.transitionWasCancelled
if !success {
toView.removeFromSuperview()
}
cleanupClosure()
transitionContext.completeTransition(success)
}
} else {
guard let fromView = transitionContext.view(forKey: .from) else {
transitionContext.completeTransition(false)
assertionFailure()
return
}
containerView.addSubviews(toImageView, fromImageView)
alimationBlock = {
fromView.alpha = 0
fromImageView.alpha = 0
updateFrameClosure()
}
completionBlock = { _ in
let success = !transitionContext.transitionWasCancelled
if success {
fromView.removeFromSuperview()
}
cleanupClosure()
transitionContext.completeTransition(success)
}
}
// TODO: Add more precise animation (i.e. Keyframe)
if isPresenting {
UIView.animate(withDuration: transitionDuration, delay: 0, options: .curveEaseIn,
animations: alimationBlock, completion: completionBlock)
} else {
UIView.animate(withDuration: transitionDuration, delay: 0, options: .curveEaseOut,
animations: alimationBlock, completion: completionBlock)
}
}
}
extension InterViewAnimation {
private func targetView(in viewController: UIViewController) -> UIView? {
if let view = (viewController as? InterViewAnimatable)?.targetView {
return view
}
if let nc = viewController as? UINavigationController, let vc = nc.topViewController,
let view = (vc as? InterViewAnimatable)?.targetView {
return view
}
return nil
}
}
Usage:
let sampleImage = UIImage(data: try! Data(contentsOf: URL(string: "https://placekitten.com/320/240")!))
class InterViewAnimationMasterViewController: StackViewController {
private lazy var topView = View().autolayoutView()
private lazy var middleView = View().autolayoutView()
private lazy var bottomView = View().autolayoutView()
private lazy var imageView = ImageView(image: sampleImage).autolayoutView()
private lazy var actionButton = Button().autolayoutView()
override func setupHandlers() {
actionButton.setTouchUpInsideHandler { [weak self] in
let vc = InterViewAnimationDetailsViewController()
let nc = UINavigationController(rootViewController: vc)
nc.modalPresentationStyle = .custom
nc.transitioningDelegate = self
vc.handleClose = { [weak self] in
self?.dismissAnimated()
}
// Workaround for not up to date laout during animated transition.
nc.view.setNeedsLayout()
nc.view.layoutIfNeeded()
vc.view.setNeedsLayout()
vc.view.layoutIfNeeded()
self?.presentAnimated(nc)
}
}
override func setupUI() {
stackView.addArrangedSubviews(topView, middleView, bottomView)
stackView.distribution = .fillEqually
bottomView.addSubviews(imageView, actionButton)
topView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
middleView.backgroundColor = UIColor.green.withAlphaComponent(0.5)
bottomView.layoutMargins = UIEdgeInsets(horizontal: 15, vertical: 15)
bottomView.backgroundColor = UIColor.yellow.withAlphaComponent(0.5)
actionButton.title = "Tap to perform Transition!"
actionButton.contentEdgeInsets = UIEdgeInsets(dimension: 8)
actionButton.backgroundColor = .magenta
imageView.layer.borderWidth = 2
imageView.layer.borderColor = UIColor.magenta.withAlphaComponent(0.5).cgColor
}
override func setupLayout() {
var constraints = LayoutConstraint.PinInSuperView.atCenter(imageView)
constraints += [
LayoutConstraint.centerX(viewA: bottomView, viewB: actionButton),
LayoutConstraint.pinBottoms(viewA: bottomView, viewB: actionButton)
]
let size = sampleImage?.size.scale(by: 0.5) ?? CGSize()
constraints += LayoutConstraint.constrainSize(view: imageView, size: size)
NSLayoutConstraint.activate(constraints)
}
}
extension InterViewAnimationMasterViewController: InterViewAnimatable {
var targetView: UIView {
return imageView
}
}
extension InterViewAnimationMasterViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = InterViewAnimation()
animator.isPresenting = true
return animator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = InterViewAnimation()
animator.isPresenting = false
return animator
}
}
class InterViewAnimationDetailsViewController: StackViewController {
var handleClose: (() -> Void)?
private lazy var imageView = ImageView(image: sampleImage).autolayoutView()
private lazy var bottomView = View().autolayoutView()
override func setupUI() {
stackView.addArrangedSubviews(imageView, bottomView)
stackView.distribution = .fillEqually
imageView.contentMode = .scaleAspectFit
imageView.layer.borderWidth = 2
imageView.layer.borderColor = UIColor.purple.withAlphaComponent(0.5).cgColor
bottomView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
navigationItem.leftBarButtonItem = BarButtonItem(barButtonSystemItem: .done) { [weak self] in
self?.handleClose?()
}
}
}
extension InterViewAnimationDetailsViewController: InterViewAnimatable {
var targetView: UIView {
return imageView
}
}
Reusable extensions:
extension UIView {
// https://medium.com/#joesusnick/a-uiview-extension-that-will-teach-you-an-important-lesson-about-frames-cefe1e4beb0b
public func frameIn(_ view: UIView?) -> CGRect {
if let superview = superview {
return superview.convert(frame, to: view)
}
return frame
}
}
extension UIView {
/// The method drawViewHierarchyInRect:afterScreenUpdates: performs its operations on the GPU as much as possible
/// In comparison, the method renderInContext: performs its operations inside of your app’s address space and does
/// not use the GPU based process for performing the work.
/// https://stackoverflow.com/a/25704861/1418981
public func caSnapshot(scale: CGFloat = 0, isOpaque: Bool = false) -> UIImage? {
var isSuccess = false
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, scale)
if let context = UIGraphicsGetCurrentContext() {
layer.render(in: context)
isSuccess = true
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return isSuccess ? image : nil
}
}
Result (gif animation):
It's probably two different views and an animated snapshot view. In fact, this is exactly why snapshot views were invented.
That's how I do it in my app. Watch the movement of the red rectangle as the presented view slides up and down:
It looks like the red view is leaving the first view controller and entering the second view controller, but it's just an illusion. If you have a custom transition animation, you can add extra views during the transition. So I create a snapshot view that looks just like the first view, hide the real first view, and move the snapshot view — and then remove the snapshot view and show the real second view.
Same thing here (such a good trick that I use it in a lot of apps): it looks like the title has come loose from the first view controller table view cell and slid up to into the second view controller, but it's just a snapshot view:

Resources