I want to rotate a bar button item - Swift - swift

I encountered with an issue. I want to display 3 buttons, that are used for filtering my tableView. I found an appropriate images from “SF symbols.” Except one, which is the same I used for filtering(bigger to smaller) but is rotated on 180 degrees. I don’t know how to rotate bar button item, so I decided add custom button to bar button item.
There’s a problem occur – button is very small.
After adding button to bar button item:
I tried to use image configuration, but it’s not very good because it looks different and not with the same spacing.
After configuring button's image:
In the debug view hierarchy I found that symbol config is “Medium”, meanwhile other bar button items have “Body, Large”. But I haven’t found anything how to change it.
I have few questions:
Is there a way to flip bar button item without adding custom button in it?
If the way in the first question impossible, is this real to configure image “dynamically” the same way as other are displayed?
My code:
class viewController: UIViewController {
#IBOutlet var youngToOldfilterButton: UIBarButtonItem!
enter code here
override func viewDidLoad() {
let filterButton = UIButton(type: .custom)
var image = UIImage(systemName: "line.3.horizontal.decrease.circle", withConfiguration: UIImage.SymbolConfiguration(pointSize: 22))
filterButton.setImage(image, for: .normal)
filterButton.sizeToFit()
filterButton.addTarget(self, action: #selector(action), for: .touchUpInside)
//rotation transform
filterButton.transform = CGAffineTransform(rotationAngle: 180 * .pi/180)
youngToOldfilterButton.customView = filterButton
}
#objc func action {
...
}
}

symbol config is “Medium”, meanwhile other bar button items have “Body, Large”. But I haven’t found anything how to change it.
That way is to use a symbol configuration. Your problem is you are using the wrong configuration:
SymbolConfiguration(pointSize: 22))
Does that say Body, Large? No. You want this:
SymbolConfiguration(textStyle: .body, scale: .large)
https://developer.apple.com/documentation/uikit/uiimage/symbolconfiguration/3294246-init
However, the very best solution would likely be to design your own custom symbol image based directly on the "decreasing" image. This takes some time, but it isn't difficult, and you obviously care a lot about the exact thickness and position of the bars, so it might be worth it. Watch https://developer.apple.com/videos/play/wwdc2021/10250 for info.

Related

Why can't I edit the button shape?

Hello I am a very new and inexperienced developer and I am trying to border around a button. I'm using the Storyboard and when I used ViewController.swift, I can't make the button a weak var. It is only allowing me to edit the actions. Please help, thank you!
When I usually do this it allows me to choose if I insert "Action, Outlet or Outlet Collection."
Your screenshot shows "Mates Scene" but your class name is ViewController. It also looks like "Mates Scene" has an embed segue to a ViewController, so it looks like you might be trying to add an IBOutlet to a child view controller from one of the parent view controller's views (the "Home Button").
If so, you can't do that. You may have meant to add "Home Button" to the class of your "Mates" view controller instead.
In recent versions of iOS Buttons don't have any shape by default. They are displayed as clickable text.
Make sure you are trying to connect your outlet and action from IB to the right target view controller, as suggested by Tyler.
Once you've got the outlet and action links working you can add code to change the appearance.
If you want to make your button a rounded rectangle, say, you can do that with code that manipulates the buttons' layer settings (corner radius, borderWidth, borderColor, and backgroundColor are pretty common properties to edit.)
Here is code that turns a button into a rounded rectangle withe a 1-pixel blue outline and a yellow background color:
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if let button = button {
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.blue.cgColor
button.layer.backgroundColor = UIColor.yellow.cgColor
}
}
Edit:
That creates a button that looks like this:
(Note that if you use a border you may need to add some padding so that the border doesn't "crowd" the button title as in my example.)
Drag and release, then from the little menu that appears select outlet rather than action. Then, to set borders do something like:
yourButton.layer.borderWidth = 10
yourButton.layer.borderColor = UIColor.red.cgColor

Xcode: Reset App and all variables and classes

I have an App that creates 21 buttons on the storyboard and then these buttons allow the user to choose players.The buttons are created programatically and have a class that sets their format.
//run 2 loops to dsip;ay the buttons (21 of them)
for j in 0...2 {
for i in 0...6 {
//use the CLASS KSPIckButton to format the buttons
let buttonOne:UIButton = KSPickButton(frame: CGRect(x: (j + 1) * 35 + (j * 80), y: (i + 5) * 35 + buttonSet, width: 110, height: 30))
//Add the button to the storyboard
self.view.addSubview(buttonOne)
buttonOne.addTarget(self,
action: #selector(playerButtons),
for: .touchUpInside)
//assign the tag to the button
buttonOne.tag = playerNo
//Give the buttons the players names
buttonOne.setTitle(allPlayers[playerNo], for: .normal)
THE PROBLEM:
The user could pick 10 names and then decide to reset.....
I TRIED:
I ran viewDidLoad() from an action Button, and it would load another 21 buttons on top of the 21 already there, which in theory works, but as the backgroundColor of my buttons is clear the old ones underneath show.
THEORY:
I am guessing that I should not be just loading layers and layers and layers of buttons anyway?
But I cannot find anywhere an option to programatically reset the app as if just loaded, or clear the existing buttons so I can create some more.
QUESTION:
Can someone point me in the right direction for an App reset or similar process?
many thanks in advance
Kev
Create a reset method and first of all remove all potential pick buttons from the view.
func reset() {
let ksPickButtons = view.subviews.filter{$0 is KSPickButton}
ksPickButtons.forEach{$0.removeFromSuperview()}
// code to create the buttons
}
Or shorter
func reset() {
view.subviews.filter{$0 is KSPickButton}
.forEach{$0.removeFromSuperview()}
// code to create the buttons
}
Then move the other code from viewDidLoad into reset and call the method from viewDidLoad as well as from any other place.
Side note:
You must not call yourself any delegate method containing will, did and should. Those methods are exclusively called by the framework.
You should either only add the buttons on the storyboard and create IBOutlets to reach them in code, or don't add any buttons in the storyboard and create and add them only with code. I'm not sure what you are trying to accomplish so unfortunately I can't give you a whole lot of advice regarding this, aside from that it's (generally) a good idea to either stick to only storyboards or only programatically creating UI (and myself I prefer the latter).
Either way, to directly answer your question, the only way to really 'reset' a viewcontroller is by creating a new viewcontroller and replacing the old one with the new one (so there is not really any type of reset on an existing viewcontroller). If you don't want to create a new viewcontroller then the only thing you can do is manually clean everything up yourself, I'll give you an example of how to clean up your buttons:
func removeAllPickButtons() {
let buttons = view.subviews.compactMap({ $0 as? KSPickButton })
buttons.forEach({ $0.removeFromSuperview() })
}
This method filters all the views that are directly in your viewcontroller (so only 1 layer deep) to only KSPickButtons (compactMap), and then loops through all of them to remove them from their superview (forEach)

Custom TabBarController with active circle

After reading a few articles about custom UITabBarControllers, I am left more confused than before I even started doing the research in the first place.
My goal is to create a custom TabBar with 3 important properties:
No text, just icons
The active icon is marked by a circle filled with a color behind it, and therefore needs a different icon color
Here's what I am trying to achieve:
I've been able to remove the text and center the icon by following another StackOverflow answer (Remove tab bar item text, show only image), although the solution seems like a hack to me.
How would I go about creating a circle behind the item and change the active item's color?
Also, would someone mind explaining the difference between the XCode inspector sections "Tab Bar Item" and "Bar Item", which appear directly under each other?
The first step is simple: leaving the title property of the UITabbarItem empty should hide the label.
Your second step can actually be broken down into two steps: changing the color of the icon and adding a circle behind it.
The first step here is simple again: you can set a different icon to use for the currently selected ViewController (I use Storyboards, this process is pretty straightforward). What you'd do is add a white version of the icon to be shown when that menu option is selected.
The final step is displaying the circle. To do this, we'll need the following information:
Which item is currently selected?
What is the position of the icon on the screen?
The first of these two is pretty easy to find out, but the second poses a problem: the icons in a UITabBar aren't spaced around the screen equally, so we can't just divide the width of the tabbar by the amount of items in it, and then take half of that to find the center of the icons. Instead, we will subclass UITabBarController.
Note: the tabBar property of a UITabBarController does have a .selectionIndicatorImage property. You can assign an image to this and it will be shown behind your icon. However, you can't easily control the placement of this image, and that is why we still resort to subclassing UITabBarController.
class CircledTabBarController: UITabBarController {
var circle: UIView?
override func viewDidLoad() {
super.viewDidLoad()
let numberOfItems = CGFloat(tabBar.items!.count)
let tabBarItemSize = CGSize(width: (tabBar.frame.width / numberOfItems) - 20, height: tabBar.frame.height)
circle = UIView(frame: CGRect(x: 0, y: 0, width: tabBarItemSize.height, height: tabBarItemSize.height))
circle?.backgroundColor = .darkGray
circle?.layer.cornerRadius = circle!.frame.width/2
circle?.alpha = 0
tabBar.addSubview(circle!)
tabBar.sendSubview(toBack: circle!)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let index = -(tabBar.items?.index(of: tabBar.selectedItem!)?.distance(to: 0))!
let frame = frameForTabAtIndex(index: index)
circle?.center.x = frame.origin.x + frame.width/2
circle?.alpha = 1
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
let index = -(tabBar.items?.index(of: item)?.distance(to: 0))!
let frame = frameForTabAtIndex(index: index)
self.circle?.center.x = frame.origin.x + frame.width/2
}
func frameForTabAtIndex(index: Int) -> CGRect {
var frames = tabBar.subviews.compactMap { (view:UIView) -> CGRect? in
if let view = view as? UIControl {
for item in view.subviews {
if let image = item as? UIImageView {
return image.superview!.convert(image.frame, to: tabBar)
}
}
return view.frame
}
return nil
}
frames.sort { $0.origin.x < $1.origin.x }
if frames.count > index {
return frames[index]
}
return frames.last ?? CGRect.zero
}
}
Now use this subclass of UITabBarController instead of the base class.
So why this approach over simply changing the icon to a circled one? Because you can do many different things with this. I wrote an article about animating the UITabBarController in a similar manner, and if you like, you can easily use above implementation to add animation to yours too.
The easiest and actually cleanest way to do it is to design your icons and import them as images to the .xcassets folder. Then you can just set the different icons for the different states for each of the viewControllers with:
ViewController.tabBarItem = UITabBarItem(title: "", image: yourImage.withRenderingMode(.alwaysOriginal), selectedImage: yourImage)
your selected image will be the one with the circle and the image will be without. It is way easier than manipulating the images in xcode and it is also less expensive since the compiler only has to render the images and doesn't have to manipulate them.
About the other question UIBarItem is
An abstract superclass for items that can be added to a bar that appears at the bottom of the screen.
UITabBarItem is a subclass of UIBarItem to provide extra funtionality.

Programmatic beginRefreshing() on iOS11 has problems with largeTitles mode

We have found what seems to be a bug in UIKit but wanted to post here to see if anyone else has this problem or found a solution.
We're trying to use the new iOS11 large titles and hoisted search bar/refreshcontrol. We seemed to have found a problem where the root viewController of the navigation stack shows a minor display issue (problem A) but once another viewcontroller is pushed onto the navigation stack, the display goes nuts (problem B):
Things to note:
The problem is worse on the 2nd VC in the stack rather than the 1st
The refreshControl is not the green color the code sets it to the 1st time you see it on each sceen
The refreshControl slides down as you pull to refresh, it shouldn't do this
This odd behavior seems to only be a problem when we programmatically do a "pull to refresh" in viewDidLoad so that the user can see that the data is loading when they enter the screen. If we remove the lines that invoke refreshControl?.beginRefreshing() the display is clean. I've recreated this problem in a sample vanilla app. This is the entirety of the viewcontroller that shows the problem:
import UIKit
class ViewController: UITableViewController {
var tableHeaderSearchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationItem.largeTitleDisplayMode = .always
tableHeaderSearchController = UISearchController(searchResultsController: UITableViewController())
navigationItem.searchController = tableHeaderSearchController
refreshControl?.tintColor = UIColor.green
refreshControl?.backgroundColor = UIColor.clear
refreshControl?.attributedTitle = NSAttributedString(string: "Loading Stuff...", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17)])
refreshControl?.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
// Commenting out these 2 lines makes it work fine but you can't see the initial refresh spinner
refreshControl?.beginRefreshing()
refreshPulled()
}
#objc func refreshPulled() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [refreshControl] in
refreshControl?.endRefreshing()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here's the storyboard. It's just a vanilla tableviewcontroller wrapped in a navigationController. 3 static cells, the 2nd one traverses to another instance of the same controller type.
Any ideas would be greatly appreciated. We'd really like to adopt the new look but this stuff is making it very hard to do so.
First, it is absolutely crucial that the table view extend up underneath the navigation bar and that is iOS 11 offset behavior be correct:
self.edgesForExtendedLayout = .all
self.tableView.contentInsetAdjustmentBehavior = .always
Second, scrolling to show the refresh control when you refresh manually is up to you, and calculating the amount is not at all simple:
self.refreshControl!.sizeToFit()
let top = self.tableView.adjustedContentInset.top
let y = self.refreshControl!.frame.maxY + top
self.tableView.setContentOffset(CGPoint(0, -y), animated:true)
self.refreshControl!.beginRefreshing()
The bar still stays too big during the refresh, but I don't see what can be done about that. Basically Apple has implemented large titles and shown the refresh control in the nav bar without thinking through the effects or dealing with the resulting bugs.

Swift - bar button item image / set size and width

Feel like I've been wasting a lot of time on this one and would love some help. I'm trying to use a custom menu icon (20px x 20px) in my barButtonItem. The problem it that is scales to fill the bar button item and is distorted.
Normally this shouldn't be too complicated. But I'm using tabbed view controllers and not a navigation controller. The navigation bar I have dropped into the view controller is just an outlet.
Normally I could do something like this:
let image = UIImage(named: "menu_white")
let frame = CGRectMake(0, 0, image!.size.width, image!.size.height)
let button = UIButton()
button.frame = frame
button.setImage(image, forState: .Normal)
let rightMenuButton = UIBarButtonItem(customView: button)
self.navigationItem.setRightBarButtonItem(rightMenuButton, animated: true)
But this doesn't work because self.navigationItem doesn't actually refer to anything in the view controller.
I need to somehow get the image into the outlet programmatically but setRightBarButtonItem is not a method of UINavigationBar.
#IBOutlet weak var navBar: UINavigationBar!
Would really appreciate some help if anyone's got some ideas.
The way a "loose" navigation bar works is that you configure a UINavigationItem and set an array containing that to be the navigation bar's items, as in this example code (from my book):
let ni = UINavigationItem(title: "Tinker")
let b = UIBarButtonItem(title: "Evers", style: .Plain, target: self, action: "pushNext:")
ni.rightBarButtonItem = b
self.navbar.items = [ni]
So, you could presumably do something like that.
The self.navigationItem thing is merely a way of causing that to be done automatically in the special situation where you're using a UINavigationController. But you say that you're not doing that. (Of course, you could do that; there's no law that says you have to use a navigation controller to do any actual navigation. I often use a navigation controller just to get the navigation bar, because it's a good place to put things like little menu buttons.)