iOS 11 - Cannot access UIView of UIBarButton - ios

I am trying to create a badge number on the top right corner of my UIBarButton. For iOS 9 and 10 it works fine but with iOS 11 I cannot access the view of the button which I normally do like this:
extension UIBarButtonItem {
func createBadge() {
guard let view = self.value(forKey: "view") as? UIView else {
return
}
}
}
I understand that using key values is not a good idea for cases like this, as the key can change. But is there an alternative here, using public APIs so that the solution works for iOS 11 too?
Thanks.

You can create the UIBarButtonItem from a custom view. Add the badge to the custom view

I'd recommend to use a custom view and embed it in your UIToolbar / UINavigationBar instead of a UIBarbuttonItem
The custom view could contain a UIButton and your badge view.
Via storyboard, simply drag and drop a UIView to the slot in your bar and add a UIButton as subview. Depending on your implementation of a badge, you could just create a UILabel and position it via constraints relative to your UIButton.
Create IBOutlets for those and set them up programmatically.

let button = UIButton.init(frame: CGRect.init(x: 0, y: 0, width: 25, height: 25))
button.addTarget(self.target, action: self.action!, for: UIControlEvents.touchUpInside)
button.target(forAction: self.action!, withSender: self.target)
button.setImage(self.image, for: .normal)
self.customView = button
guard let view = self.value(forKey: "view") as? UIView else {
return
}

Related

Push ViewController subviews beneath NavigationBar when translucent is True, Programmatically

This question asked to be implemented in Swift 4, iOS 11
Is there any way to make every subview of ViewController's view to be pushed down when it is under UINavigationBar?
If navigation bar is NOT TRANSLUCENT the subview is under it. This is what I want.
Desired Result
But when navigation bar is TRANSLUCENT the subview is lying under it. I dont want it. I want the subview is pushed down just be like if navigation bar is not translucent.
Undesired Result
I create the view programmatically :
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.red
let navBar = (self.parent as? UINavigationController)?.navigationBar
navBar?.isTranslucent = true
}
func makeChildView() {
let myframe = CGRect(x: 0, y: 10, width: self.view.frame.width, height:
self.view.frame.height * 0.4)
let view = UIView(frame: myframe)
view.backgroundColor = UIColor.green
self.view.addSubview(view)
}
Using Autolayout
I am able to solve this problem using autolayout. But I just want to know how to achieve this result without autolayout if possible. Is there any other approach?
Swift 3.x
navBar?.isTranslucent = true
self.automaticallyAdjustsScrollViewInsets = false
Add this line & you are good to go.

Swift - Adding touch to UIImageView

I'm following a tutorial online and I'm trying to add touch detection to an UIImageView. I realize that this can be done with a gesture recognizer but I have a different problem and I don't know how to ask for it.
Basically, I added an overlay UIButton initialized from the frame of imageView. However, I have to move the frame down 64 px because of the navigation bar. My imageView is added form the storyboard but UIButton is added programmatically.
Is there a way to do this without adding 64 px manually? This is no biggies but since the frame of the imageView when printing is (10, 10, 355, 450) and the frame of UIButton is (10, 74, 355, 450), it kinda ticks me off a little bit.
Let me clarify, I'm wondering why view controller doesn't take into account the size of the navigation bar when adding subview programmatically? It seems to handle this just fine when adding subview from Storyboard.
Without adding 64px:
overlay = UIButton(frame: imageView.frame)
overlay.backgroundColor = UIColor.red
overlay.addTarget(self, action: #selector(imageViewTapped), for: .touchUpInside)
view.addSubview(overlay)
With 64px added - fits perfectly
overlay = UIButton(frame: imageView.frame.offsetBy(dx: 0, dy: 64))
overlay.backgroundColor = UIColor.red
overlay.addTarget(self, action: #selector(imageViewTapped), for: .touchUpInside)
view.addSubview(overlay)
You can add button on image view.
Try This :
let overlay = UIButton(frame: imageView.bounds)
imageView.isUserInteractionEnabled = true
overlay.backgroundColor = UIColor.red.withAlphaComponent(0.3)
overlay.addTarget(self, action: #selector(imageViewTapped), for: .touchUpInside)
imageView.addSubview(overlay)
You can use UITapGestureRecognizer for the same as given below.
let bg = UIImageView(image: UIImage(named: "Home"))
bg.frame = self.view.frame
self.view.addSubview(bg)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(SecondViewController.onTapScreen))
bg.isUserInteractionEnabled = true
bg.addGestureRecognizer(tapRecognizer)
Handle tap event as given below
func onTapScreen() {
// Handle touch here.
}
By this way you don't need to use button also so it can avoid multiple controls.
Set your UIViewController's navigationbar to Opaque Navigation Bar. It will start you view below navigation bar.

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.

Custom SearchBar iOS

I am trying to create a Custom SearchBar so that I can
Left Align the search image
Change the corners of textbook to be a little more rounded.
After the image is left aligned, the placeholder text can also be left aligned
Change color of the search text
However, even after subclassing UISearchBar I am unable to achieve what I want and facing following problems:
When debugging, I can see UISearchBar in the view & searchField inside it, but upon iteration on self.subViews and checking if the element is UITextField, I don't get anything. Re-checked and I always get one subView which has different memory address than that of searchbar in debug mode
var customSearchBar:UISearchBar?
var searchField:UITextField?
var button:UIButton?
override func layoutSubviews() {
for subView in self.subviews {
if (subView.isKindOfClass(UITextField)) {
searchField = (subView as! UITextField)
break
}
}
if ((searchField) != nil) {
searchField?.textColor = UIColor.redColor() //Testing if code works?
}
super.layoutSubviews()
}
I don't want to use undocumented ways and have almost tried all the ways I could find here, without any success
Kindly help me. I am using Xcode 7 and iOS 8
1.You can try this code:
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
searchBar.backgroundImage = UIImage()//Without this you can't change background color
searchBar.backgroundColor = UIColor.greenColor()
searchBar.showsCancelButton = true
searchBar.returnKeyType = .Done
searchBar.placeholder = "Test text"
var txtSearchField = searchBar.valueForKey("_searchField")
txtSearchField?.layer.cornerRadius = 15
txtSearchField?.layer.borderWidth = 1.5
txtSearchField?.layer.borderColor = UIColor.orangeColor().CGColor
let image = txtSearchField?.subviews?[1]
image?.frame = CGRect(x: 0, y: 7.5, width: 13, height: 13)
let lbl = txtSearchField?.subviews.last as! UILabel
lbl.textColor = UIColor.whiteColor()
lbl.backgroundColor = UIColor.blueColor()
lbl.frame = CGRect(x: 13, y: 1, width: 127.5, height: 25)
Result from Playground:
Also you can look at this and this open source solutions.
To understand logic of how UISearchBar is build you can have a look on their UISearchBar.h file with all private apis here. As you can see, it has UISearchBarTextField as a subview.
Last year in iOS 7 project I used this code to access UITextField of UISearchBar:
UITextField *searchBarTextField;
for (UIView *subview in self.searchBar.subviews) {
for (UIView *subsubview in subView.subviews){
if ([subsubview isKindOfClass:[UITextField class]]) {
searchBarTextField = (UITextField *)subsubview;
break;
}
}
}
However, I would not recommend to use this logic, as if Apple changes private API method you UI and layout might be broken. You can try to use loop method to loop threw all subviews of subviews of UISearchBar and find needed text field, but again, it might be removed any time.
In the future, if you have any questions on how to access any subview on Apple build UI - you can debugger view hierarchy by pressing this button on debug panel:
If you want to make it custom - why not creating you own one? UIView + UITextField + UIButton + some logic = UISearchBar.

remove gap between buttons in navigation item rightbuttonitems

I'm using self.navigationItem.rightBarButtonItems to setup my navigation bar items. However, for the bar buttons, i'm using a custom view(button). I observe there is spacing between buttons. How can I remove this?
i know its too late, but i solved it using following method of UIBarButtonItem
use
[barbuttonitem setImageInsets:UIEdgeInsetsMake(0, -30, 0, -70)];
I solved this by using storybord interface.I know you are using custom Bar,but this answer will useful for those who use stroybord.
1.Select the Bar item.
2.Select the Size Inspector.
Here you can find image Inset,using top,bottom AND left , right you can change the position of Bar Item.
You can't remove it. You can work around it by creating a bar button item with a custom view, where that custom view has you custom buttons all added as subviews. In this way you can directly control the exact positioning.
Here is an example that how you can resolve this issue:
Create an extension of UIBarButton
extension UIBarButtonItem
{
/** Create custom right bar button for reduce space between right bar buttons */
func initRightButton(let imageNamed:String, let target:UIViewController, let selector:Selector) -> UIBarButtonItem {
let frame = CGRectMake(0, 0, 30, 30)
//Create imageView
let imageView = UIImageView(frame:frame)
imageView.image = UIImage(named: imageNamed)
//Create Button
let button = UIButton(frame: frame)
button.addTarget(target, action: selector, forControlEvents: .TouchUpInside)
//Create View and add imageView and Button
let view = UIView(frame: frame)
view.addSubview(imageView)
view.addSubview(button)
return UIBarButtonItem(customView: view)
}
}
In your class controller use the method customizeNavigationBar
func customizeNavigationBar() {
//Create custom right bar button chat for reduce space between right bar buttons
let barButton1 = UIBarButtonItem().initRightButton("customImageNamed1", target: self, selector: customSelector)
let barButton2 = UIBarButtonItem().initRightButton("customImageNamed2", target: self, selector: customSelector)
self.navigationItem.rightBarButtonItems = [barButton1,barButton2]
}

Resources