Custom tab bar like attached screen shot in swift-Storyboard - ios

Hi every one I am stuck somewhere. Need your help. I wanna implement custom tab bar
What I have done so far.
I added tab bar controller but unable to give constraints.
Also added tab bar in view controller but failed to add actions to
navigate to other controllers.

Whether its possible to modify the tab bar to look like this, i'm not sure. I can confirm its not possible to do it via storyboard.
Storyboards are very powerful tools, but only give limited ability to customise controls, through the options along the right hand side. For a tababr, all you can do is set the background color, tab selectedColor etc. To do something like this, at a minimum you would need to run some code inside the tabBarController to modify the UIViews to create that special button in the middle and then round the edges etc.
For example, I previously needed to create a special, bigger middle button, with the tabbar's top left and right corners rounded. Heres a piece of that code as a rough ida:
import UIKit
class HomeTabBarController: UITabBarController {
private let middleButton = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let tabItems = tabBar.items else { return }
tabItems[0].titlePositionAdjustment = UIOffset(horizontal: -25, vertical: 0)
tabItems[1].titlePositionAdjustment = UIOffset(horizontal: 25, vertical: 0)
middleButton.setBackgroundImage(UIImage(named: "middle-tab-button"), for: .normal)
middleButton.setBackgroundImage(UIImage(named: "middle-tab-button")?.maskWithColor(color: .lightGray), for: .highlighted)
middleButton.tintColor = UIColor.black
middleButton.center = CGPoint(x: tabBar.frame.width/2, y: 25)
self.tabBar.addSubview(middleButton)
self.view.backgroundColor = UIColor(named: "background")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.tabBar.roundCorners(corners: [.topLeft, .topRight], radius: 28)
}
}
You might be able to use this as a start and add to it. The other alternative being to completely build a custom control from scratch, and make all the interactions work the same.
But this would be a lot easier if the designer behind this would agree to stick to a more iOS-y style control. It takes a significant amount of time and effort to build and maintain these things. Often the slightly different appearance of the inbuilt control barely changes anything at all, making it not worth it.

Related

Troubles with my navBarItem

i have a navigation Bar Item. When the app loads, i have this code so the navbar has the height that i want...
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let height: CGFloat = 38
let bounds = self.navigationController!.navigationBar.bounds
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height + height)
}
The problem is that when i press the homeButton and then i return to the app, the navbar changes the height, and i want the height to be like the first time you run the app
It seems like when i first run the app, the height of the navbar takes the value that i want, but then, when i close and reopen the app, it takes the original value (the default height value of the navbar).
What should i do? Should i implement another method?? Thanks!!!
I would recommend use autolayout constraint properly in your storyboard file. And then you do not have to set the height programmatically. But if you would like to manipulate programmatically then move your code in ViewDidload instead of ViewDidAppear method.
let height: CGFloat = 38
let bounds = self.navigationController!.navigationBar.bounds
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height + height)
In Storyboard select your Navaigation Item > Prompt Field add space to increase the height of Navigation bar
And have your code in either viewDidAppear(As you have today) or viewWillAppear
viewWillAppear or viewDidAppear methods are not designed to work like that. If your application goes to inactive and become active again while a UIViewController on the foreground it won't call any of those methods.
You can track that from applicationDidBecomeActive method in AppDelegate.
But if you want to know that in a specific view controller you can use the notification named UIApplicationDidBecomeActive
I think you should subclass the UINavigationController, but keep the navigationbar it self is the best, if you wat to add something on the bar, you can add a uitoolbar on the ViewController's view, and toolBar.frame = (0,64,screenWidth, 30), then delete the hair line of navigationbar , and it will look like the navigationbar been extended

Create cover flow using UIScrollView

I am trying to create a cover flow image slide something like this :
I have to say I don't want to use others framework like iCarousel I need to write my own code. here is my codes but it only shows me one image per page , I was wondering how can I change my code to add previous and next image like the image ?
override func viewDidLoad() {
super.viewDidLoad()
pageControl.numberOfPages = imageArray.count
scrollView.frame = self.view.frame
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(pageControl.numberOfPages) , height: 0)
scrollView.delegate = self
for i in 0..<imageArray.count {
ashvanImage = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width - 50 , height: self.view.frame.height - 200))
ashvanImage.center = scrollView.center
ashvanImage.contentMode = .scaleAspectFit
ashvanImage.image = UIImage(named: "\(i).jpg")
scrollView.addSubview(ashvanImage)
createPageWith(images: ashvanImage, page: i)
}
}
func createPageWith(images:UIImageView, page:Int) {
let newView = UIView(frame: CGRect(x: scrollView.frame.size.width * CGFloat(page), y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height))
newView.addSubview(images)
scrollView.addSubview(newView)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let page = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(page)
}
As others have said, if you don't want to use iCarousel, which has CoverFlow built in, you should use a UICollectionView. It is built for what you want.
Certainly you could do this starting from a UIScrollView, but it would probably take an experienced developer several weeks of full-time work to get a clean design working and debugged, and the result would look and feel a whole lot like a collection view when you were done, only not as flexible or as maintainable. Plus you'd need pretty advanced knowledge of Core Animation, which is pretty specialized and not very well documented.
EDIT:
A Google search on "UICollectionView CoverFlow Swift" found this framework on Github:
https://github.com/sumitlni/LNICoverFlowLayout
If you're determined to do this yourself, you could at least look over that framework as a road map for what you'd need to do.
You are set scrollview's width equal to the whole view, make it less like 85% or 90% or according to your requirement.
Which will solve half of your problem.
By looking at the design i think you want the view to animate.
So first, turn off scrolling for the scrollview.
Then add 2 gesture's on the scrollview, one for right swipe and another for left swipe.
Then scroll the view 1 page at a time according to the gesture and animate the view's size by changing its x position by 1 page and decreasing its size and increasing the incoming one's.
You can use the default method UIScrollView.animate too.

How to display UITableViewController as a swipe up gesture in front of another ViewController?

I am trying to get something like this to work. This is the Uber App. Where a user can swipe another view up, in front of a background view.
The background view is fairly simple, it has been done already. The view which will be swiped on top will be a UITableView. I want the user to be able to see just a little top part first when the app launches, then upon swiping a little it should stop in the middle and then after fully swiping up should take it all the way to the top, replacing the Background view.
Frameworks I have looked at are pullable view for iOS. But it is way too old and doesn't get any nice animations across. I have also looked at SWRevealViewController but I can't figure out how to swipe up from below.
I have also tried to use a button so when a user clicks on it, the table view controller appears modally, covering vertical, but that is not what I want. It needs to recognize a gesture.
Any help is greatly appreciated. Thanks.
I'm aware that the question is almost 2 and a half years old, but just in case someone finds this through a search engine:
I'd say that your best bet is to use UIViewPropertyAnimator. There's a great article about it here: http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/
EDIT:
I managed to get a simple prototype working with UIViewPropertyAnimator, here's a GIF of it:
Here's the project on Github: https://github.com/Luigi123/UIViewPropertyAnimatorExample
Basically I have two UIViewControllers, the main one called ViewController and the secondary one called BottomSheetViewController. The secondary view has an UIPanGestureRecognizer to make it draggable, and inside the recognizer's callback function I do 3 things (after actually moving it):
① calculate how much percent of the screen have been dragged,
② trigger the animations in the secondary view itself,
③ notify the main view about the drag action so it can trigger it's animations. In this case I use a Notification, passing the percentage inside notification.userInfo.
I'm not sure how to convey ①, so as an example if the screen is 500 pixels tall and the user dragged the secondary view up to the 100th pixel, I calculate that the user dragged it 20% of the way up. This percentage is exactly what I need to pass into the fractionComplete property inside the UIViewPropertyAnimator instances.
⚠️ One thing to note is that I couldn't make it work with an actual navigation bar, so I used a "normal" view with a label in it's place.
I tried making the code smaller by removing some utility functions like checking if the user interaction is finished, but that means that the user can stop dragging in the middle of the screen and the app wouldn't react at all, so I really suggest you see the entire code in the github repo. But the good news is that the entire code that executes the animations fits in about 100 lines of code.
With that in mind, here's the code for the main screen, ViewController:
import UIKit
import MapKit
import NotificationCenter
class ViewController: UIViewController {
#IBOutlet weak var someView: UIView!
#IBOutlet weak var blackView: UIView!
var animator: UIViewPropertyAnimator?
func createBottomView() {
guard let sub = storyboard!.instantiateViewController(withIdentifier: "BottomSheetViewController") as? BottomSheetViewController else { return }
self.addChild(sub)
self.view.addSubview(sub.view)
sub.didMove(toParent: self)
sub.view.frame = CGRect(x: 0, y: view.frame.maxY - 100, width: view.frame.width, height: view.frame.height)
}
func subViewGotPanned(_ percentage: Int) {
guard let propAnimator = animator else {
animator = UIViewPropertyAnimator(duration: 3, curve: .linear, animations: {
self.blackView.alpha = 1
self.someView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8).concatenating(CGAffineTransform(translationX: 0, y: -20))
})
animator?.startAnimation()
animator?.pauseAnimation()
return
}
propAnimator.fractionComplete = CGFloat(percentage) / 100
}
func receiveNotification(_ notification: Notification) {
guard let percentage = notification.userInfo?["percentage"] as? Int else { return }
subViewGotPanned(percentage)
}
override func viewDidLoad() {
super.viewDidLoad()
createBottomView()
let name = NSNotification.Name(rawValue: "BottomViewMoved")
NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil, using: receiveNotification(_:))
}
}
And the code for the secondary view (BottomSheetViewController):
import UIKit
import NotificationCenter
class BottomSheetViewController: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet weak var navBarView: UIView!
var panGestureRecognizer: UIPanGestureRecognizer?
var animator: UIViewPropertyAnimator?
override func viewDidLoad() {
gotPanned(0)
super.viewDidLoad()
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(respondToPanGesture))
view.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.delegate = self
panGestureRecognizer = gestureRecognizer
}
func gotPanned(_ percentage: Int) {
if animator == nil {
animator = UIViewPropertyAnimator(duration: 1, curve: .linear, animations: {
let scaleTransform = CGAffineTransform(scaleX: 1, y: 5).concatenating(CGAffineTransform(translationX: 0, y: 240))
self.navBarView.transform = scaleTransform
self.navBarView.alpha = 0
})
animator?.isReversed = true
animator?.startAnimation()
animator?.pauseAnimation()
}
animator?.fractionComplete = CGFloat(percentage) / 100
}
// MARK: methods to make the view draggable
#objc func respondToPanGesture(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
moveToY(self.view.frame.minY + translation.y)
recognizer.setTranslation(.zero, in: self.view)
}
private func moveToY(_ position: CGFloat) {
view.frame = CGRect(x: 0, y: position, width: view.frame.width, height: view.frame.height)
let maxHeight = view.frame.height - 100
let percentage = Int(100 - ((position * 100) / maxHeight))
gotPanned(percentage)
let name = NSNotification.Name(rawValue: "BottomViewMoved")
NotificationCenter.default.post(name: name, object: nil, userInfo: ["percentage": percentage])
}
}
EDIT: So, some time has passed and now there is a really awesome library called Pulley. It does exactly what I wanted it to do, and its a breeze to setup!
Original answer:
Thanks to both Rikh and Tj3n for giving me hints. I managed to do something very basic, it doesn't have nice animations like Uber but it gets the job done.
With the following code, you can swipe any UIViewController. I use a UIPanGestureRecognizer on my image, which will stay on top of the dragged view at all times. Basically, you use that image and it recognizes where it gets dragged, and it sets the view's frame according to the user's input.
First go to your storyboard and add an identifier for the UIViewController that will be dragged.
Then in the MainViewController, use the following code:
class MainViewController: UIViewController {
// This image will be dragged up or down.
#IBOutlet var imageView: UIImageView!
// Gesture recognizer, will be added to image below.
var swipedOnImage = UIPanGestureRecognizer()
// This is the view controller that will be dragged with the image. In my case it's a UITableViewController.
var vc = UIViewController()
override func viewDidLoad() {
super.viewDidLoad()
// I'm using a storyboard.
let sb = UIStoryboard(name: "Main", bundle: nil)
// I have identified the view inside my storyboard.
vc = sb.instantiateViewController(withIdentifier: "TableVC")
// These values can be played around with, depending on how much you want the view to show up when it starts.
vc.view.frame = CGRect(x: 0, y: self.view.frame.height, width: self.view.frame.width, height: -300)
self.addChildViewController(vc)
self.view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
swipedOnImage = UIPanGestureRecognizer(target: self, action: #selector(self.swipedOnViewAction))
imageView.addGestureRecognizer(swipedOnImage)
imageView.isUserInteractionEnabled = true
}
// This function handles resizing of the tableview.
func swipedOnViewAction() {
let yLocationTouched = swipedOnImage.location(in: self.view).y
imageView.frame.origin.y = yLocationTouched
// These values can be played around with if required.
vc.view.frame = CGRect(x: 0, y: yLocationTouched, width: UIScreen.main.bounds.width, height: (UIScreen.main.bounds.height) - (yLocationTouched))
vc.view.frame.origin.y = yLocationTouched + 50
}
Final Product
Now, It is possible that my answer might not be the most efficient way of going at this, but I am new to iOS so this is the best I could come up with for the time being.
You can embed that table view inside a custom scroll view that will only handle touch when touch that table view part (override hittest), then drag it up (disable tableview scroll), till the upper part then disable scroll view and enable tableview scroll again
Or, you can just add the swipe gesture into your tableview and change it's frame along and disable swipe when it reach the top
Experiment with those and eventually you will achieve the effect you wanted
As Tj3n pointed out, you could use a UISwipeGesture to display the UITableView. So using constraints (instead of frames) heres how you could go about doing that:
Go to your UIViewController inside your Story board on which you wish to display the UITableView. Drag and drop the UITableView and add a leading, trailing and height to the UITableView. Now add a vertical constraint between the UIViewController and UITableView so that the UITableView appears below the UIViewController(Play around with this vertical value until you can display the top part of the UITableView to suit your need). Create outlets for the vertical spacing constraint and height constraint (in case you need to set a specific height that you can figure out at run time). On the swipe up just animatedly set the vertical constraint to be equal to the negative value of the height sort of like:
topSpaceToViewControllerConstraint.constant = -mainTableViewHeightConstraint.constant
UIView.animate(withDuration: 0.3) {
view.layoutIfNeeded()
};
Alternatively
If you want to be able to bring the UITableView up depending on the pan amount (i.e depending on how much the user has moved across the screen or how fast) you should use a UIPanGestureRecognizer instead and try and set frames instead of autoLayout for the UITableView (as I'm not a big fan of calling view.layoutIfNeeded repeatedly. I read somewhere that it is an expensive operation, would appreciate it if someone would confirm or correct this).
func handlePan(sender: UIPanGestureRecognizer) {
if sender.state == .Changed {
//update y origin value here based on the pan amount
}
}
Alternatively using UITableViewController
Doing what you wish to perform is also possible using a UITableViewController if you wish to but it involves a lot of faking and effort by creating a custom UINavigationControllerDelegate mainly to create a custom animation that will use UIPercentDrivenInteractiveTransition to pull the new UITableViewController up using a UIPanGestureRecognizer if you want it depending on the pan amount. Otherwise you can simply add a UISwipeGestureRecognizer to present the UITableViewController but you will still have to again create a custom animation to "fake" the effect you want.

iPad Input accessory View on a ViewController displayed as a form

Ive been searching for awhile on this issue and I cant seem to find an answer. Im looking to use a Input accessory view on a view controller that is displayed on the iPad as a form sheet. I currently have the ALTextInputBar implemented that works well but my problem is that the accessory view is displayed full screen width. Id like to apply the input accessory to the displayed modal VC only. Like facebook has implemented in their iPad app below.
So my commentsVC code has the following methods
var messageInput = ALTextInputBar()
let keyboardObserver = ALKeyboardObservingView()
let leftButton = UIButton(frame: CGRectMake(0, 0, 44, 44))
let rightButton = UIButton(frame: CGRectMake(0, 0, 44, 44))
override var inputAccessoryView: UIView? {
get {
return messageInput
}
}
override func canBecomeFirstResponder() -> Bool {
return true
}
I then configure the textInputbar in its own method setting the left and right buttons etc. This all works perfect, Its just Id like to have the same approach as the image above.
One approach I have tried but it doesnt work too well is setting the tablefooterview of the commentsVc to equal messageInput. Although this is very buggy and not the right approach imo.

How to display UIView over keyboard in iOS

I want to create a simple view over keyboard, when users tap "Attach" button in inputAccessoryView.
Something like this:
Is there an easy way to do it? Or i should create my custom keyboard?
You can add that new subview to your application window.
func attach(sender : UIButton)
{
// Calculate and replace the frame according to your keyboard frame
var customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height-300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.redColor()
customView.layer.zPosition = CGFloat(MAXFLOAT)
var windowCount = UIApplication.sharedApplication().windows.count
UIApplication.sharedApplication().windows[windowCount-1].addSubview(customView);
}
Swift 4 version:
let customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height - 300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.red
customView.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
UIApplication.shared.windows.last?.addSubview(customView)
The trick is to add the customView as a top subview to the UIWindow that holds the keyboard - and it happens to be the last window in UIApplication.shared.windows.
Swift 4.0
let customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height-300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.red
customView.layer.zPosition = CGFloat(MAXFLOAT)
let windowCount = UIApplication.shared.windows.count
UIApplication.shared.windows[windowCount-1].addSubview(customView)
As Tamás Sengel said, Apple's guidelines does not support adding a view over the keyboard. The recommended way to add a view over keyboard in Swift 4 & 5 is:
1) Add view with your "Next" button in your storyboard as external view and connect in your class (see Explain Image), in my case:
IBOutlet private weak var toolBar: UIView!
2) For the textfield you want to add your custom view over keyboard, add it as accessory view in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
phoneNumberTextField.inputAccessoryView = toolBar
}
3) Add action for "Next" button:
#IBAction func nextButtonPressed(_ sender: Any) {
descriptionTextView.becomeFirstResponder()
// or -> phoneNumberTextField.resignFirstResponder()
}
Explain Image:
Method 2: Result with image
In TableView Controller - add stricked view at bottom
Please follow this great link to handle safe area for screens like iPhone X if you want to use this method(2). Article: InputAccessoryView and iPhone X
override var inputAccessoryView: UIView? {
return toolBar
}
override var canBecomeFirstResponder: Bool {
return true
}
Do you have find some effective method to solve this problem? In iOS9,you put your customView on the top of the windows:
UIApplication.sharedApplication().windows[windowCount-1].addSubview(customView);
But if the keyboard dismisses, the top Windows will be removed, so your customView will be removed.
Looking forward for your help!
Thank you for your help!
You can definitely add the view to your application’s window, and you can also add another window entirely. You can set its frame and level. The level could be UIWindowLevelAlert.
While this can be possible with accessing the topmost window, I would avoid doing this, as it clearly interferes with Apple's guidelines.
What I would do is dismissing the keyboard and replacing its frame with a view with same dimensions.
The keyboard's frame can be accessed from keyboard notifications listed here, their userInfo contain a key that can be accessed with UIKeyboardFrameEndUserInfoKey.

Resources