I have some buttons on the view. I don't know how many, I know that it can be minimum of 2 or maximum of 4. I want to check each button's title text and add to array true or false depending on value of title.
is it possible to do ?
Try below thing..
for view in self.view.subviews {
if view.isKindOfClass(UIButton){
// Add you logic over here.
// you can check the tag of button as well.
}
}
Yes, it is possible to iterate through all objects inside the view. To see all the subviews present in your view, you can do following:
for view in self.view.subviews as! [UIView] {
if let btn = view as? UIButton {
if btn.title == "whateverYourCriteriaIs" {
//your code
}
else {
//do something else
}
}
}
Related
I want to add a banner to the navigation bar, but by increasing the height of it. I want to copy the design and behaviour of an artist page in the Apple Music app:
It behaves just like a normal Large Title would, except for that it has been moved down, it has a sticky UIImageView behind it and it returns its background when the user scrolls down far enough. You can fire up Apple Music, search for an artist and go to their page to try it out yourself.
I've tried a bunch of things like setting the frame on the UINavigationBarLargeTitleView, and the code from this answer: https://stackoverflow.com/a/49326161/5544222
I already got a hold of the UINavigationBarLargeTitleView and its UILabel using the following code:
func setLargeTitleHeight() {
if let largeTitleView = self.getLargeTitleView() {
if let largeTitleLabel = self.getLargeTitleLabel(largeTitleView: largeTitleView) {
// Set largeTitleView height.
}
}
}
func getLargeTitleView() -> UIView? {
for subview in self.navigationBar.subviews {
if NSStringFromClass(subview.classForCoder).contains("UINavigationBarLargeTitleView") {
return subview
}
}
return nil
}
func getLargeTitleLabel(largeTitleView: UIView) -> UILabel? {
for subview in largeTitleView.subviews {
if subview.isMember(of: UILabel.self) {
return (subview as! UILabel)
}
}
return nil
}
Initially put a view with image and label and play button. Then clear the navigation bar it will show the image below it by
self.navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController!.navigationBar.shadowImage = UIImage()
self.navigationController!.navigationBar.isTranslucent = true
later when you scroll up you have to handle it manually and again show the
navigation with title.
I have 5 buttons within a UIStackView, and I want to find out which index is being selected, and later compare those indexes. My code right now gives me an Array.Index. I've tried both subviews and arrangedSubviews. Is there anyway I can turn this into an Integer? I can't figure it out. Thanks!!
if let selectedIndex = stackview.subviews.index(of: sender) {
}
// UPDATE
I kinda got what I wanted with:
let int = stackview.subviews.distance(from: stackview.subviews.startIndex, to: selectedIndex)
I'm still not sure if this is the most efficient way, but it does the job for now.
index(of:) return Int.
Also you should find your button in the arrangedSubviews, not in the subviews
Assuming your stack view contains only buttons, and each button is connected to this #IBAction, this should work:
#IBAction func didTap(_ sender: Any) {
// make sure the sender is a button
guard let btn = sender as? UIButton else { return }
// make sure the button's superview is a stack view
guard let stack = btn.superview as? UIStackView else { return }
// get the array of arranged subviews
let theArray = stack.arrangedSubviews
get the "index" of the tapped button
if let idx = theArray.index(of: btn) {
print(idx)
} else {
print("Should never fail...")
}
}
I would add a tag to each of your buttons (button.tag = index) then check the tag of your sender.
So then you can wire up each of your buttons to the same function with a sender parameter, then check if sender.tag == index.
In Swift 3 (XCode 8.3.3) I have a control in a UIStackView. I have an array of UIImageViews, and loop through the array to populate the stack view at run time:
for voiceIcon in voiceIcons {
let voiceView = UIImageView(image: voiceIcon)
addArrangedSubview(voiceView)
}
These icons will sometimes become disabled (replaced with a new image), so in order to update the control, I have a function to remove all the icons so that I can re-add the appropriate ones (if there's a better way, I'm listening!):
private func resetIconsView() {
for subUIView in self.subviews as [UIView] {
removeArrangedSubview(subUIView)
subUIView.removeFromSuperview()
print("Removing")
}
}
I've also tried
for subUIView in self.subviews as! [UIImageView] { ... }
I get the debug line "Removing" for each of the icons, but they still remain in the control and the UI. I'm new to Swift, so I'm likely not understanding something, what approach should I take?
Try code below:
for view in arrangedSubviews {
view.removeFromSuperview()
}
I am assuming your UIStackView only contains some UIImageView. You can iterate through all the arranged subviews of your stack view and update your image of that imageView. A sample implementation could look like below:
func changeImage() {
for view in self.arrangedSubviews {
if let imgView = view as? UIImageView {
imgView.image = UIImage(named: "taka_icon.png")
}
}
}
I did it with an extension. You have also to remove Constraints if existing. Else this can cause some trouble.
USAGE: myStackView.removeAllArrangedSubviews()
public extension UIStackView {
func removeAllArrangedSubviews() {
let removedSubviews = arrangedSubviews.reduce([]) { (allSubviews, subview) -> [UIView] in
self.removeArrangedSubview(subview)
return allSubviews + [subview]
}
// Deactivate all constraints
NSLayoutConstraint.deactivate(removedSubviews.flatMap({ $0.constraints }))
// Remove the views from self
removedSubviews.forEach({ $0.removeFromSuperview() })
}
}
In my app I'm using KYDrawerController library from here: https://github.com/ykyouhei/KYDrawerController
It works as expected, but I want to add an UIButton on the menu view controller (which is on top when opened), however it's clipped by view bounds. Best way I can explain this is by showing you a screenshot:
And here's how it should look like:
Button now has negative right constraint margin so it's position is correct, but how can I disable clipping?
In the menu view controller, which can you see on the foreground, I've added this code:
self.navigationController?.navigationBar.clipsToBounds = false
self.navigationController?.view.clipsToBounds = false
self.view.clipsToBounds = false
let elDrawer = self.navigationController?.parent as! KYDrawerController
elDrawer.view.clipsToBounds = false
elDrawer.displayingViewController?.view.clipsToBounds = false
elDrawer.drawerViewController?.view.clipsToBounds = false
elDrawer.displayingViewController?.view.clipsToBounds = false
elDrawer.mainViewController.view.clipsToBounds = false
elDrawer.inputViewController?.view.clipsToBounds = false
elDrawer.splitViewController?.view.clipsToBounds = false
As you can see I've tried all possible ways to disable clipping, yet it's still clipped. How can I achieve this?
edit:
Added hierachy view:
I've also tried to run following test:
var view = arrowButton as UIView?
repeat {
view = view?.superview
if let sview = view {
if(sview.clipsToBounds){
print("View \(view) clips to bounds")
break
}
else{
print("View \(view) does not clip to bounds")
}
}
} while (view != nil)
And it prints:
View Optional(>) does not clip to
bounds
So looks like nothing is clipping yet it's clipped.
edit2:
debug view hierarchy:
Yay, I've found the solution:
self.navigationController?.view.subviews[0].clipsToBounds = false
old code is not needed.
UINavigationTransitionView (it's Apple private class) was the one responsible with clipToBounds turned on.
We can't really tell what's going on because you don't show us the view hierarchy.
I suggest you write test code that starts at the button, walking up the superview hierarchy looking for a superview who's' clipsToBounds is true. Something like this:
var view = button as UIView?
repeat {
view = view.superview
if view?.superview.clipsToBounds ?? false == true {
print("View \(view) clips to bounds")
break
} while view != nil
Then you'll know which view is clipping, and you can fix it witout the "shotgun" approach you're using.
EDIT:
I'm not sure why you are getting such strange debug messages. I suggest adding unique tag numbers to all the button's superviews, and then using code like this:
override func viewDidAppear(_ animated: Bool) {
var view = button as UIView?
repeat {
view = view?.superview
if let view = view {
let tag = view.tag
let description = (tag == 0) ? view.debugDescription : "view w tag \(tag)"
if(view.clipsToBounds){
print("View \(description) clips to bounds")
break
}
else{
print("View \(description) does not clip to bounds")
}
}
} while (view != nil)
}
Post ALL the output from that debug code so we can see the entire view hierarchy you're dealing with.
How would I loop through all UIButtons in my view in Swift? I would want to set all the titles to "", but my for-loop in Swift is giving an error.
for btns in self.view as [UIButton] {
// set the title to ""
}
This code should work:
for view in self.view.subviews as [UIView] {
if let btn = view as? UIButton {
btn.setTitleForAllStates("")
}
}
You need to iterate through the subViews array.
Shortened and updated for Swift 3 & 4
for case let button as UIButton in self.view.subviews {
button.setTitleForAllStates("")
}
Looping over subview works, but it's sometimes a little ugly, and has other issues.
If you need to loop over some specific buttons, for example to add corner radius or change tint or background color, you can use an array of IBOutlets and then loop over that.
var buttons = [SkipBtn, AllowBtn]
for button in buttons as! [UIButton] {
button.layer.cornerRadius = 5
}
Swift 4:
let subviewButtons = self.view.subviews.filter({$0.isKind(of: UIButton.self)})
for button in subviewButtons {
//do something
}
To add some context for a common use case, suppose the buttons were in a scroll view and you wanted to highlight the tapped button and de-highlight the other buttons. In this situation, you would direct all buttons to one action method:
#objc private func buttonAction(_ button: UIButton) {
for case let b as UIButton in view.scrollView.subviews {
if b == button {
b.setTitleColor(UIColor.green, for: []) // highlight
} else {
b.setTitleColor(UIColor.black, for: []) // de-highlight
}
}
}
This code seems to be quite useful for iterating over any object within a view, just change UIButton for any other subview type such as UIView or UIImageView, etc.
let filteredSubviews = self.view.subviews.filter({
$0.isKindOfClass(UIButton)})
for view in filteredSubviews {
//Do something
}
Used some of the offered questions out there and created my own. I believe is the most efficient when you want to programmatically set up the title of various UIButtons(in my case I am building a quiz)
By randomising my array list and with just a for loop I printing the item at index to the button title
for view in self.viewForButtons.subviews{
if view.isKindOfClass(UIButton)
{
let button : UIButton = view as! UIButton
button.setTitle("item[i]", forState: .Normal)
}
}
If you have UIView's within self.view then you need to loop through the subviews while searching for UIButton. Using the accepted answer, I made this little function to do so:
Swift 4 + :
func findButton(`in` view: UIView){
for view in view.subviews as [UIView] {
if let button = view as? UIButton {
// Do something with 'button'
}else{
// Loop through subview looking for buttons
findButton(in: view)
}
}
}
Usage:
override func viewDidLoad() {
findButton(in: self.view)
}
Hope this helps!
Here's a short way in Swift if you know the subview only has buttons:
myView.subviews.map {
($0 as? UIButton)!.enabled = false
}