Following this repo, I created a custom "tabBar" via a separate uiview that sits behind the native uitabbarcontroller tabBar. The custom tabBar has rounded corners and a shadow.
Everything works great except in the instances I push/pop a new uiviewcontroller onto/from the embedded uinavigationcontroller stack, where I hide the tabBar.
My issue is smoothly toggling the custom tabBar to hide/show during these instances.
I've set .isHidden = true/false for the custom tabBar when a uiviewcontroller is pushed/popped, and either the custom tabBar disappears too early or appears too late relative to the native uitabbarcontroller tabBar.
Any guidance would be appreciated.
class TabBarViewController: UITabBarController {
let customTabBarView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = Constants.style.offWhite
view.layer.cornerRadius = 20
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
view.clipsToBounds = true
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: -8.0)
view.layer.shadowOpacity = 0.12
view.layer.shadowRadius = 10.0
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.tabBar.isHidden = true
self.tabBar.backgroundColor = .clear
self.tabBar.barStyle = .default
self.tabBar.isTranslucent = true
addCustomTabBarView()
hideTabBarBorder()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
customTabBarView.frame = tabBar.frame
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
var newSafeArea = UIEdgeInsets()
newSafeArea.bottom += customTabBarView.bounds.size.height
self.children.forEach({$0.additionalSafeAreaInsets = newSafeArea})
}
//Function invoked when pushing/popping viewControllers onto/from the embedded navigation stack
func toggleCustomTabBarView(shouldHide: Bool) {
self.customTabBarView.isHidden = shouldHide
DispatchQueue.main.async {
UIView.transition(with: self.view, duration: TimeInterval(UINavigationController.hideShowBarDuration), options: .transitionCrossDissolve, animations: {
})
}
}
func addCustomTabBarView() {
customTabBarView.frame = tabBar.frame
view.addSubview(customTabBarView)
view.bringSubviewToFront(self.tabBar)
}
func hideTabBarBorder() {
let tabBar = self.tabBar
tabBar.backgroundImage = UIImage.from(color: .clear)
tabBar.shadowImage = UIImage()
tabBar.clipsToBounds = true
}
}
extension UIImage {
static func from(color: UIColor) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
https://stackoverflow.com/a/65285566/9466631
Couldn't find anything as a solve for the dummy view behind the tabBar, but this new solution worked for me that avoids using the dummy view altogether and adds a CAShapeLayer instead.
I have a UITableViewCell with two images, my goal is to expand these images upon a long press by the user. In the best case scenario the image would cover the entire screen with a small 'x' or something to close.
I have the following function I'm using within the custom UITableViewCell, but the image only expands the size of the cell. I can't figure out how to expand the image over the entire tableview/navBar/tabbar of the superview.
#objc func answerOneLongPress(_ sender: UILongPressGestureRecognizer) {
let imageView = sender.view as! UIImageView
let newImageView = UIImageView(image: imageView.image)
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
newImageView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
self.addSubview(newImageView)
}
Please let me know if you need more information. I feel like this should be happening in the UITableViewController as opposed to the cell, but haven't been able to get it working that way.
You should not add your view to cell but to a view controller or to a key window. That depends on your needs. what happens in your case is your image view is added on a cell and is being clipped and also it is not positioned correctly.
I would use some kind of object that handles presenting of this image. Let the code speak for itself:
class ImageOverlayController {
private var startFrame: CGRect
private var backgroundView: UIView
private var imageView: UIImageView
private init(startFrame: CGRect, backgroundView: UIView, imageView: UIImageView) {
self.startFrame = startFrame
self.backgroundView = backgroundView
self.imageView = imageView
}
private convenience init() { self.init(startFrame: .zero, backgroundView: UIView(), imageView: UIImageView()) }
static func showPopupImage(inController viewController: UIViewController? = nil, fromImageView imageView: UIImageView) -> ImageOverlayController {
guard let targetView = viewController?.view ?? UIApplication.shared.keyWindow else { return ImageOverlayController() } // This should never happen
let startFrame = imageView.convert(imageView.bounds, to: targetView)
let backgroundView: UIView = {
let view = UIView(frame: targetView.bounds)
view.backgroundColor = UIColor.black.withAlphaComponent(0.0)
return view
}()
let newImageView: UIImageView = {
let view = UIImageView(frame: startFrame)
view.image = imageView.image
return view
}()
let controller = ImageOverlayController(startFrame: startFrame, backgroundView: backgroundView, imageView: imageView)
backgroundView.addSubview(newImageView)
targetView.addSubview(backgroundView)
UIView.animate(withDuration: 0.3) {
backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
newImageView.frame = targetView.bounds
}
return controller
}
func dimiss(completion: (() -> Void)? = nil) {
UIView.animate(withDuration: 0.3, animations: {
self.imageView.frame = self.startFrame
self.backgroundView.backgroundColor = self.backgroundView.backgroundColor?.withAlphaComponent(0.0)
}) { _ in
self.backgroundView.removeFromSuperview()
completion?()
}
}
}
As you say a button must still be added which may then call dismiss on the view.
Note: The code I provided was not really tested but just quickly put together. Let me know if there are any issues so I modify it.
On Swift 3.0 how can I apply some animation effect to this slideshow? Cannot find animation effects related to animationWithDuration method.
let image1 = UIImage(named: "image1")!
let image2 = UIImage(named: "image2")!
let image3 = UIImage(named: "image3")!
var imagesArray : [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
imagesArray = [image1, image2, image3]
myView.clipsToBounds = true
myView.animationImages = imagesArray
myView.animationDuration = 10.0
myView.animationRepeatCount = 0
myView.startAnimating()
}
It looks like you are using the build in frame animation for UIImageView. This is designed to just cycle through the images, like an animated gif. It doesn't really have more sophisticated animation than that. What you can do if you want transition effects is alternate between two image views and use the UIView Animation methods to switch between them. This just does a crossfade by manipulating alpha:
var images = [UIImage]()
var currentImageindex = 0
func animateImageViews() {
swap(&firstImageView, &secondImageView)
secondImageView.image = images[currentImageindex]
currentImageindex = (currentImageindex + 1) % images.count
UIView.animate(withDuration: 1, animations: {
self.firstImageView.alpha = 0
self.secondImageView.alpha = 1
}, completion: { _ in
self.animateImageViews()
})
}
Here is playground that shows how it works. You need to drop 2 images into the resources folder named 1.png and 2.png for it to work. Note that setting the view frames like this is horrible programming practice i just did it here for brevity. use interface builder and autolayout in your actual code.
import PlaygroundSupport
import UIKit
class Demo: UIViewController {
let button = UIButton()
var firstImageView = UIImageView()
var secondImageView = UIImageView()
var images = [UIImage]()
var currentImageindex = 0
override func viewDidLoad() {
view.addSubview(firstImageView)
view.addSubview(secondImageView)
images.append(UIImage(named: "1.png")!)
images.append(UIImage(named: "2.png")!)
firstImageView.image = images[0]
secondImageView.image = images[1]
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
firstImageView.frame = view.frame
secondImageView.frame = view.frame
}
override func viewDidAppear(_ animated: Bool) {
animateImageViews()
}
func animateImageViews() {
swap(&firstImageView, &secondImageView)
secondImageView.image = images[currentImageindex]
currentImageindex = (currentImageindex + 1) % images.count
UIView.animate(withDuration: 1, animations: {
self.firstImageView.alpha = 0
self.secondImageView.alpha = 1
}, completion: { _ in
self.animateImageViews()
})
}
}
let viewcontroller = Demo()
viewcontroller.view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
PlaygroundPage.current.liveView = viewcontroller.view
I have a UIScrollView that I want to have paging functionality (think an initial splash screen). I want that content (a UILabel and a UIImageView) to be placed centrally in each paging view on the scrollView. My problem is is that it is always slightly off centre ().
Here is the complete code:
var splashScreenObjects = [SplashScreenObject]()
var imageViewArray = [UIImageView]()
var subtitleViewArray = [UILabel]()
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var pageControl: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
createSplashScreenObjects()
configurePageControl()
configureScrollView()
}
func createSplashScreenObjects() {
let firstScreen: SplashScreenObject = SplashScreenObject(subtitle: "Medication reminders on your phone. Never miss your next dose", image: UIImage(named: "splashScreen1")!)
let secondScreen: SplashScreenObject = SplashScreenObject(subtitle: "Track how good you have been with your medication", image: UIImage(named: "splashScreen2")!)
let thirdScreen: SplashScreenObject = SplashScreenObject(subtitle: "The better you are with your medication, the more points you'll earn!", image: UIImage(named: "splashScreen3")!)
splashScreenObjects.append(firstScreen)
splashScreenObjects.append(secondScreen)
splashScreenObjects.append(thirdScreen)
}
func configureScrollView() {
self.scrollView.layoutIfNeeded()
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.pagingEnabled = true
self.scrollView.delegate = self
let width = view.frame.size.width
for index in 0..<splashScreenObjects.count {
let subtitle = UILabel(frame: CGRectMake((width * CGFloat(index)) + 25, self.scrollView.frame.size.height-75, width-50, 75))
subtitle.text = splashScreenObjects[index].subtitle
subtitle.textAlignment = NSTextAlignment.Center
subtitle.textColor = UIColor.whiteColor()
subtitle.font = UIFont(name:"Ubuntu", size: 16)
subtitle.numberOfLines = 2
subtitle.backgroundColor = UIColor.clearColor()
self.scrollView.addSubview(subtitle)
self.subtitleViewArray.append(subtitle)
subtitle.alpha = 0
let mainImage = UIImageView(frame: CGRectMake((width * CGFloat(index)), 50, width, self.scrollView.frame.size.height-150))
mainImage.image = splashScreenObjects[index].image
mainImage.contentMode = UIViewContentMode.ScaleAspectFit
self.scrollView.addSubview(mainImage)
self.imageViewArray.append(mainImage)
mainImage.alpha = 0
}
self.scrollView.contentSize = CGSizeMake(width * CGFloat(splashScreenObjects.count), self.scrollView.frame.size.height-50)
animateViews(Int(0))
}
func configurePageControl() {
self.pageControl.numberOfPages = splashScreenObjects.count
self.pageControl.currentPage = 0
self.view.addSubview(pageControl)
pageControl.addTarget(self, action: #selector(SplashViewController.changePage(_:)), forControlEvents: UIControlEvents.ValueChanged)
}
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * self.view.frame.size.width
scrollView.setContentOffset(CGPointMake(x, 0), animated: true)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / self.view.frame.size.width)
pageControl.currentPage = Int(pageNumber)
animateViews(Int(pageNumber))
}
func animateViews(pageNumber: Int) {
UIView.animateWithDuration(0.5, animations: {
self.imageViewArray[pageNumber].alpha = 1.0
self.subtitleViewArray[pageNumber].alpha = 1.0
})
}
Here are my auto layout constraints for the UIScrollView:
Your leading and trailing spaces are both -20, which means that the scroll view is 40 points wider than its superview. Change these to 0.
You should replace
self.scrollView.layoutIfNeeded()
to
self.view.layoutIfNeeded()
because layoutIfNeeded layout caller subviews, not itself. So, scrollView, when you add subtitle and mainImage on it, has wrong frame.
There is a good chance that I am just using the wrong terminology for this, but I have been looking to see if there is an iOS UIView transition that splits a view (BLUE) and any subview controls to reveal another view (RED) and its controls. I have found a couple of posts that mention something similar from 2011 but nothing recent, so was wondering if anything new had been added now we are upto iOS 8. Any pointers would be much appreciated.
If you are trying to do such split transition, I have created a animation controller for view controller transition. If you look at the code, you will find that there can be two different ways to transit, opening from view from the middle or the toview comes and collapses on the top of the from view.
Here is a small gif of how the code below works;
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
let presenting: Bool
init(presenting: Bool) {
self.presenting = presenting
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 1.0
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
animateOutImagesWithContext(transitionContext)
//animateInImagesWithContext(transitionContext)
}
func snapshotView(view: UIView!) -> UIImage {
UIGraphicsBeginImageContext(view.bounds.size)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshotImage
}
func animateOutImagesWithContext(transitionContext:UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let fromView = fromViewController!.view
let toView = toViewController!.view
containerView.addSubview(toView)
let snapshotImage = snapshotView(fromView)
fromView.removeFromSuperview()
let imageViews = animatingOutImageViews(snapshotImage)
containerView.addSubview(imageViews.firstImageView)
containerView.addSubview(imageViews.secondImageView)
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
let firstImageView = imageViews.firstImageView
let secondImageView = imageViews.secondImageView
if self.presenting {
firstImageView.frame = CGRectOffset(firstImageView.frame, -CGRectGetWidth(firstImageView.frame), 0)
secondImageView.frame = CGRectOffset(secondImageView.frame, CGRectGetWidth(secondImageView.frame), 0)
} else {
firstImageView.frame = CGRectOffset(firstImageView.frame, 0, -CGRectGetHeight(firstImageView.frame))
secondImageView.frame = CGRectOffset(secondImageView.frame, 0, CGRectGetHeight(secondImageView.frame))
}
}) { (completed: Bool) -> Void in
imageViews.firstImageView.removeFromSuperview()
imageViews.secondImageView.removeFromSuperview()
transitionContext.completeTransition(true)
}
}
func animateInImagesWithContext(transitionContext:UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let fromView = fromViewController!.view
let toView = toViewController!.view
containerView.insertSubview(toView, belowSubview: fromView)
let snapshotImage = snapshotView(toView)
let imageViews = animatingInImageViews(snapshotImage)
containerView.addSubview(imageViews.firstImageView)
containerView.addSubview(imageViews.secondImageView)
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
let firstImageView = imageViews.firstImageView
let secondImageView = imageViews.secondImageView
if self.presenting {
firstImageView.frame = CGRectOffset(firstImageView.frame, 0, CGRectGetHeight(firstImageView.frame))
secondImageView.frame = CGRectOffset(secondImageView.frame, 0, -CGRectGetHeight(secondImageView.frame))
} else {
firstImageView.frame = CGRectOffset(firstImageView.frame, CGRectGetWidth(firstImageView.frame), 0)
secondImageView.frame = CGRectOffset(secondImageView.frame, -CGRectGetWidth(secondImageView.frame),0)
}
}) { (completed: Bool) -> Void in
fromView.removeFromSuperview()
imageViews.firstImageView.removeFromSuperview()
imageViews.secondImageView.removeFromSuperview()
transitionContext.completeTransition(true)
}
}
func animatingOutImageViews(snapshotImage: UIImage) -> (firstImageView: UIImageView!, secondImageView: UIImageView!)
{
let imageSize = snapshotImage.size
var firstPartFrame: CGRect
var secondPartFrame: CGRect
if presenting {
firstPartFrame = CGRectMake(0, 0, imageSize.width * 0.5, imageSize.height)
secondPartFrame = CGRectOffset(firstPartFrame, CGRectGetWidth(firstPartFrame), 0)
} else {
firstPartFrame = CGRectMake(0, 0, imageSize.width, imageSize.height * 0.5)
secondPartFrame = CGRectOffset(firstPartFrame, 0, CGRectGetHeight(firstPartFrame))
}
let firstImage = getImage(snapshotImage, insideRect: firstPartFrame)
let secondImage = getImage(snapshotImage, insideRect: secondPartFrame)
let firstImageView = UIImageView(frame: firstPartFrame)
firstImageView.image = firstImage
let secondImageView = UIImageView(frame: secondPartFrame)
secondImageView.image = secondImage
return (firstImageView, secondImageView)
}
func animatingInImageViews(snapshotImage: UIImage) -> (firstImageView: UIImageView!, secondImageView: UIImageView!)
{
let imageSize = snapshotImage.size
var firstPartFrame: CGRect
var secondPartFrame: CGRect
if presenting {
firstPartFrame = CGRectMake(0, 0, imageSize.width, imageSize.height * 0.5)
secondPartFrame = CGRectOffset(firstPartFrame, 0, CGRectGetHeight(firstPartFrame))
} else {
firstPartFrame = CGRectMake(0, 0, imageSize.width * 0.5, imageSize.height)
secondPartFrame = CGRectOffset(firstPartFrame, CGRectGetWidth(firstPartFrame), 0)
}
let firstImage = getImage(snapshotImage, insideRect: firstPartFrame)
let secondImage = getImage(snapshotImage, insideRect: secondPartFrame)
let firstImageView = UIImageView(image: firstImage)
let secondImageView = UIImageView(image: secondImage)
if presenting {
firstImageView.frame = CGRectOffset(firstPartFrame, 0, -CGRectGetHeight(firstPartFrame))
secondImageView.frame = CGRectOffset(secondPartFrame, 0, CGRectGetHeight(secondPartFrame))
} else {
firstImageView.frame = CGRectOffset(firstPartFrame, -CGRectGetWidth(firstPartFrame), 0)
secondImageView.frame = CGRectOffset(secondPartFrame, CGRectGetWidth(secondPartFrame), 0)
}
return (firstImageView, secondImageView)
}
func getImage(image: UIImage, insideRect rect:CGRect) -> UIImage {
let image = CGImageCreateWithImageInRect(image.CGImage, rect)!
return UIImage(CGImage: image)!
}
}
class SecondViewController: UIViewController, UIViewControllerTransitioningDelegate {
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(nibName: nil, bundle: nil)
self.transitioningDelegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
let view1 = UIView(frame: CGRectZero)
view1.backgroundColor = UIColor.purpleColor()
view1.setTranslatesAutoresizingMaskIntoConstraints(false)
let view2 = UIView(frame: CGRectZero)
view2.backgroundColor = UIColor.cyanColor()
view2.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(view1)
view.addSubview(view2)
let views = [
"view1": view1,
"view2": view2
]
let vFormat = "V:|[view1][view2(==view1)]|"
let hFormat = "H:|[view1]|"
let hConstraints = NSLayoutConstraint.constraintsWithVisualFormat(hFormat,
options: .allZeros,
metrics: nil,
views: views)
let vConstraints = NSLayoutConstraint.constraintsWithVisualFormat(vFormat,
options: .AlignAllLeft | .AlignAllRight,
metrics: nil,
views: views)
view.addConstraints(hConstraints)
view.addConstraints(vConstraints)
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: "tapped")
view.addGestureRecognizer(tapGestureRecognizer)
}
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(presenting: true)
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(presenting: false)
}
func tapped() {
let nextViewController = NextViewController()
dismissViewControllerAnimated(true, completion: nil)
}
}
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let view1 = UIView(frame: CGRectZero)
view1.backgroundColor = UIColor.redColor()
view1.setTranslatesAutoresizingMaskIntoConstraints(false)
let view2 = UIView(frame: CGRectZero)
view2.backgroundColor = UIColor.greenColor()
view2.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(view1)
view.addSubview(view2)
let views = [
"view1": view1,
"view2": view2
]
let hFormat = "H:|[view1][view2(==view1)]|"
let vFormat = "V:|[view1]|"
let hConstraints = NSLayoutConstraint.constraintsWithVisualFormat(hFormat,
options: .AlignAllTop | .AlignAllBottom,
metrics: nil,
views: views)
let vConstraints = NSLayoutConstraint.constraintsWithVisualFormat(vFormat,
options: .allZeros,
metrics: nil,
views: views)
view.addConstraints(hConstraints)
view.addConstraints(vConstraints)
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: "tapped")
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped() {
let secondViewController = SecondViewController()
presentViewController(secondViewController, animated: true, completion: nil)
}
}
The code might be little longer but has to be easy to understand. Tune up a little if you want.
There is no such built in transition in iOS. You have to make it yourself. One way to do this would be by using the UIViewControllerAnimatedTransitioning protocol, and doing a custom presentation. The animation steps that would be carried out by the custom animator object would be something like this,
1) create two half images of the blue view, and add them to the transitionContext's containerView (you can't split a UIView in half, so you need to use an image of it instead)
2) add the red controller's view to the transitionContext's containerView underneath the half images.
3) remove the blue view
4) slide the two half images off the screen by animating their constraint constant values.
You could use something like:
UIView *redView = [[UIView alloc]initWithFrame:CGRectMake(self.view.frame.size.width / 2, 0, 1, self.view.frame.size.height)];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
[UIView animateWithDuration:1 animations:^{
redView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
UIViewController2 *viewController = [UIViewController2 new];
[self presentViewController:viewController animated:NO completion:^{
[redView removeFromSuperview];
}];
}];
on button click.