Swift 4 - Button over Tab Bar Item - ios

I am trying to position a custom button over one of the item of my Tab bar.
func setupMiddleButton() {
let numberOfItems = CGFloat(tabBar.items!.count)
let tabBarItemSize = CGSize(width: tabBar.frame.width / numberOfItems, height: tabBar.frame.height)
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: tabBarItemSize.width, height: self.tabBar.frame.size.height))
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.white
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
My issue is that with the previous code the button is not perfectly over the bar item (see picture):
Any suggestion? I really don't know how else to try.
Thank you!

I notice that this screenshot is of an iPhone X Simulator, which has a different layout at the bottom of the screen.
Your code works well on any other iPhone. In iOS 11 they introduced what's called the "Safe area". When you calculate the size and origin for your button, you will have to take that into account.
When you calculate the origin.y for your buttonFrame, you have to subtract the height for the safe-area at the bottom, like this:
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height - self.view.safeAreaInsets.bottom
This won't solve your problem though, as your code probably runs in viewDidLoad, which happens before the view knows it's supposed be displayed on an iPhone X with a safe area.
You can override viewDidLayoutSubviews for this, and set the correct frame for your button each time that is called.
This will fix your issue:
class CustomTabBarViewController: UITabBarController {
let menuButton = UIButton(frame: CGRect.zero)
override func viewDidLoad() {
super.viewDidLoad()
setupMiddleButton()
}
func setupMiddleButton() {
let numberOfItems = CGFloat(tabBar.items!.count)
let tabBarItemSize = CGSize(width: tabBar.frame.width / numberOfItems, height: tabBar.frame.height)
menuButton.frame = CGRect(x: 0, y: 0, width: tabBarItemSize.width, height: tabBar.frame.size.height)
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height - self.view.safeAreaInsets.bottom
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.green
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
menuButton.frame.origin.y = self.view.bounds.height - menuButton.frame.height - self.view.safeAreaInsets.bottom
}
}
I know it's tempting to simply call setupMiddleButton from inside viewDidLayoutSubviews, but do not do that. viewDidLayoutSubviews should not be used to create buttons etc., it should only be used to move them accordingly to the rest of the view. You might want to set the entire frame of menuButton inside viewDidLayoutSubviews rather than only the origin.y like I did, especially if you need to support rotation/landscape-mode. In this very simple example, updating origin.y is enough.

Related

Xcode horizontal UIScrollView

I have 4 buttons in a UIViewController in XCode. I already added code to them so I would prefer to keep the buttons. I want to have the buttons side by side in the view where there is only one button on the screen at a time and the edge of the other buttons on the edge of the screen. Where you can just barely see them. I wanted to know how would I be able to accomplish this? If you need more information than this just let me know?
var scrollView: UIScrollView!
private func addButton() {
for index in 0...2{
let button = UIButton()
let x = scrollView.frame.size.width * CGFloat(index)
button.frame = CGRect(x: x, y: 0, width: scrollView.frame.width, height: self.view.frame.height)
button.backgroundColor = UIImage(named: ColorArr[index])
scrollView.contentSize.width = scrollView.frame.size.width * CGFloat(index + 1)
scrollView.addSubview(button)
}
}
You can call this function in viewDidLoad() and i took color array to differentiate buttons

Custom tab button position error on iphone x

I add custom button to my tabbar with following this answer.
var menuButtonFrame: CGRect = menuButton.frame
//menuButtonFrame.origin.y = tabBar.frame.y + tabBar.frame.height - menuButtonFrame.height
menuButtonFrame.origin.y = view.bounds.height - menuButtonFrame.height
menuButtonFrame.origin.x = self.view.bounds.width / 2 - menuButtonFrame.size.width / 2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = Colors.mainColor
menuButton.layer.cornerRadius = menuButtonFrame.height/2
self.view.addSubview(menuButton)
when i tested on simulator it works good. but when i tested on Iphone X simulator it displays under tabbar.
I tried to align buttons origin.y value to tabbar bottom (commented line) but it didn't work again.
Should i use iphone x specific alignment or is there more clean solution for this.
Don't add centerButton in UIView. Add in tabBar
func addMenuButton() {
let menuWidth: CGFloat = 64 //let height & width same as 64
let menuButton = UIButton(frame: CGRect(x: (tabBar.frame.width / 2) - (menuWidth / 2), y: 0, width: menuWidth, height: menuWidth))
menuButton.backgroundColor = .red
menuButton.layer.cornerRadius = menuWidth/2
tabBar.addSubview(menuButton) //Add in tabBar instead of UIView
}

How can I properly place a UIButton on top of a loaded SFSafariViewController?

I have read many articles about the SFSafariViewController and I believe that it offers splendid functionality in iOS apps. However, when I load my SFSafariViewController, I intentionally hide the navigation bar because I want one custom fixed button in the upper left corner to dismiss the view controller.
override func viewDidAppear(_ animated: Bool) {
let safariViewController = PSSafariViewController(url: URL(string: blogUrl)!, entersReaderIfAvailable: true)
present(safariViewController, animated: false) {
var frame = safariViewController.view.frame
let OffsetY: CGFloat = 44
frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
frame.size = CGSize(width: frame.width, height: frame.height + OffsetY)
safariViewController.view.frame = frame
let btn: UIButton = UIButton(frame: CGRect(x: 100, y: 400, width: 100, height: 50))
btn.backgroundColor = UIColor.green
btn.setTitle("Click Me", for: .normal)
btn.addTarget(self, action: #selector(PSBlogViewController.buttonAction), for: UIControlEvents.touchUpInside)
btn.tag = 1 // change tag property
btn.isOpaque = true
safariViewController.view.addSubview(btn)
safariViewController.view.bringSubview(toFront: btn)
print(btn.description)
}
}
As you can see, I alter the frame so that the bar at the top is not visible. That code runs fine. But when I try to add a UIButton, it appears briefly and then is covered when I run the app. It's a simple blog reader app that uses the SFSafariViewController. Maybe Apple doesn't want developers running around messing with this, but any solutions or workarounds to make the button stay visible are greatly appreciated!
Here's the info about the button: 0x7f950b618db0; frame = (100 400; 100 50); tag = 1; layer = CALayer: 0x60000023ab60
Why don't you use one of the other WebView classes to get the extra functionalities you desire?
5.1.1 (iv) SafariViewContoller must be used to visibly present information to users; the controller may not be hidden or obscured by
other views or layers. Additionally, an app may not use
SafariViewController to track users without their knowledge and
consent.
It is definitely true that Apple won't want the SFSafariViewController to be obscured. However, I did figure out that when I present it, in the completion block I can add a button and what was causing trouble was that I had to increase the zPosition of the layer of the button's view like so:
present(safariViewController, animated: false) {
var frame = safariViewController.view.frame
let OffsetY: CGFloat = 44
frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
frame.size = CGSize(width: frame.width, height: frame.height + OffsetY)
safariViewController.view.frame = frame
self.btn = UIButton(frame: CGRect(x: 5, y: 50, width: 50, height: 50))
self.btn.layer.cornerRadius = 25
self.btn.backgroundColor = UIColor(red: 0, green: 170/255, blue: 240/255, alpha: 0.5)
self.btn.setTitle("←", for: .normal)
self.btn.addTarget(self, action: #selector(safariViewController.buttonAction(sender:)), for: .touchUpInside)
self.btn.tag = 1 // change tag property
self.btn.isOpaque = true
safariViewController.view.addSubview(self.btn)
safariViewController.view.bringSubview(toFront: self.btn)
self.btn.layer.zPosition = safariViewController.view.layer.zPosition + 1
for subview in safariViewController.view.subviews {
subview.isUserInteractionEnabled = false
}
self.btn.isEnabled = true
self.btn.isUserInteractionEnabled = true
//print(self.btn.description)
}

center element when pressed in a ScrollView

i really how to do a thing wich is probably easy..
I've a ScrollView with some button in it.
That's how i create all my button in the scrollview.
var buttonList = [UIButton]()
func createButton() {
let imageArray = fillImageArray()
var lastButtonWidth: CGFloat = 0
for index in 0..<6 {
let frame1 = CGRect(x: ((self.view.frame.size.width / 2) - 27.5) + CGFloat(index * 70), y: 0, width: 55, height: 55 )
let button = UIButton(frame: frame1)
button.setImage(imageArray[index], forState: .Normal)
button.tag = index
button.addTarget(parentViewController, action: #selector(ViewController.buttonClicked(_:)), forControlEvents: .TouchUpInside)
self.scrollView.addSubview(button)
lastButtonWidth = frame1.origin.x
buttonList.append(button)
}
self.scrollView.contentSize = CGSizeMake(lastButtonWidth + 55, 0)
}
I want when i press one of my button to center him and positioning correctly the other buttons.
example :
If i press on 5 i want this result :
the button 5 is moved to the center.
Now what I would suggest using is the scroll view method scrollView.scrollRectToVisible(CGRect, animated: Bool). This will move the scroll view to make a certain part of your content visible.
To create the CGRect you could do something like this:
let scrollWidth = scrollView.frame.width
let scrollHeight = scrollView.frame.height
let desiredXCoor = button.frame.origin.x - ((scrollWidth / 2) - (button.frame.width / 2) )
let rect = CGRect(x: desiredXCoor, y: 0, width: scrollWidth, height: scrollHeight)
scrollView.scrollRectToVisible(rect, animated: true)
My math may be a bit off, but the essence is that you use the size of the scrollView and the UIButton to create a CGRect for the scroll view to move to. This means when a button is clicked, you could use an implementation like this:
func myMethod() {
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
}
func buttonClicked(sender: UIButton){
let scrollWidth = scrollView.frame.width
let scrollHeight = scrollView.frame.height
let desiredXCoor = sender.frame.origin.x - ((scrollWidth / 2) - (sender.frame.width / 2) )
let rect = CGRect(x: desiredXCoor, y: 0, width: scrollWidth, height: scrollHeight)
scrollView.scrollRectToVisible(rect, animated: true)
}
If adjusted properly to your project, this should allow you to do what you have outlined.
Hope this would help you.
In button action method write below code.
func buttonClicked(sender:UIButton) {
for view in self.scrollView.subviews {
if view.isKindOfClass(UIButton) && view.tag == sender.tag {
let xCenter = max(0, (view.center.x - self.scrollView.frame.width/2))
self.scrollView.setContentOffset(CGPointMake(xCenter, self.scrollView.contentOffset.y), animated: true)
break;
}
}
}
I suppose you want the button touched to be placed in the center,so there is a easy solution, you can use the button's center.x - scrollview.width / 2 as the offsetX to construct the contentOffset and take two boundary situation into consideration:offsetx < 0 and offsetx + scrollview.width > scroll.contentSize.width
Ok i just found by myself.
let centerScrollView = scrollView.frame.size.height * 2
UIView.animateWithDuration(0.2, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.scrollView.contentOffset = CGPoint(x: sender.frame.origin.x - centerScrollView, y: sender.frame.origin.y)
}, completion: nil)
and it's perfectly center. Thx everyone.

How to embed stack view in scroll view programmatically

I have tried embedding it, but my stack view is dynamic and my app is also changing orientations time to time. I have segment control at the end of the view.
I have also tried googling it but had no luck. thanks in advance.
So far I have done:
In view did load:
mainStackView.axis = UILayoutConstraintAxis.Vertical
mainStackView.spacing = 3
scrollView.frame = self.view.bounds
scrollView.addSubview(mainStackView)
view.addSubview(scrollView)
In view did layout:
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
let top = topLayoutGuide.length
let bottom = bottomLayoutGuide.length
self.mainStackView.frame = CGRect(x: 0, y: top, width: view.frame.width, height: view.frame.height - top - bottom).insetBy(dx: 10, dy: 10)
dispatch_async(dispatch_get_main_queue())
{
self.scrollView.frame = self.view.bounds
self.scrollView.contentSize = CGSize(width: self.view.bounds.width, height: self.segmentedControl.frame.origin.y + self.segmentedControl.frame.height + 50)
}
print(scrollView.contentSize)
}
You need to set the height constraint of segment control.
For Example:
segmentedControl.heightAnchor.constraintEqualToConstant(50).active = true
More over, you can Add Empty bottom view to avoid stack view's must fill mechanism. This will show you desired view output.
var bottomView = UIView(frame: CGRectZero)
stackView.addArrangedSubview(bottomView)

Resources