"0" is not convertible to BackgroundView in swift - swift

I have this class method and i'm calling this method in my view controller
class func initWithNewFrame(var frame : CGRect?)
{
if let newFrame = frame
{
var backgroundView : BackgroundView = BackgroundView(frame: newFrame)
}
}
I,m calling as
var frameNew : CGRect = CGRectMake(self.view.frame.origin.x,
self.view.frame.origin.y,
self.view.frame.size.width,
self.view.frame.size.height)
self.gradientView = BackgroundView.initWithNewFrame(frameNew)
and i got this error "0" is not convertible to BackgroundView in swift
,
Please solve this and thanks in advance.

Reason:
The error comes from here: self.gradientView = BackgroundView.initWithNewFrame(frameNew)
You assign self.gradientView with return value of initWithNewFrame, but in your code, initWithNewFrame has no return value.
In Swift, no return value means return an empty tuple. So you got "“0” is not convertible to BackgroundView" error, because you want to assign a BackgroundView with an empty tuple.
Maybe this is your expected method:
class func initWithNewFrame(var frame : CGRect?) -> BackgroundView?
{
var backgroundView : BackgroundView? = nil
if let newFrame = frame
{
backgroundView = BackgroundView(frame: newFrame)
}
return backgroundView
}
Update:
if you want to make a custom initializer for class BackgroundView, try this:
convenience init(var frame : CGRect?) {
if let newFrame = frame
{
self.init(newFrame )
}
// Do custom thing
}

Related

Change Google's map SDK current location button possition [duplicate]

I am using google maps in my project i want to show my location button
a bit up how should i do that using swift
You can change the position of the MapControls by set the padding for your map view .
let mapView = GMSMapView()
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.padding = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 50)
It will change the padding of the my location control. From bottom 50px and from right 50px
Refer the below screenshot which added the padding of 50px from bottom and right.
Update for swift 4 : just change the position of my location button only
for object in self.mapView.subviews{
if(object.theClassName == "GMSUISettingsPaddingView"){
for view in object.subviews{
if(view.theClassName == "GMSUISettingsView"){
for btn in view.subviews{
if(btn.theClassName == "GMSx_QTMButton"){
var frame = btn.frame
frame.origin.y = frame.origin.y - 100
btn.frame = frame
}
}
}
}
}
}
public extension NSObject {
public var theClassName: String {
return NSStringFromClass(type(of: self))
}
}
Like Subramanian, this works very well :
mapView.padding = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 0)
This is perfect if you have a navigation bar in your app.
Here's my function for Google Maps SDK 5.1.0, since the button's class was renamed to 'GMSx_MDCFloatingButton' in case someone needs an updated approach.
Also depending on your implementation you might want to call this on viewDidAppear().
func moveLocationButton(){
for object in self.googleMapsView!.subviews{
if(object.thisClassName == "GMSUISettingsPaddingView"){
for view in object.subviews{
if(view.thisClassName == "GMSUISettingsView"){
for btn in view.subviews{
if(btn.theClassName == "GMSx_MDCFloatingButton"){
var frame = btn.frame
frame.origin.x = frame.origin.x - 50
frame.origin.y = frame.origin.y - 150
btn.frame = frame
}
}
}
}
}
}
}
public extension NSObject {
var thisClassName: String {
return NSStringFromClass(type(of: self))
}
}
I would recommend to change button position by correcting constraint instead of changing frame.origin. I got it worked by changing frame.origin placing it into viewDidAppear only. Neither viewWillApper nor viewWillLayoutSubviews were correct methods to call from. However I managed it work perfectly calling constraint changing from viewDidLoad. And I used snapkit, but it is not a big deal to get it done natively
private func changeCurrentLocationButtonPosition(of mapView: GMSMapView) {
for object in mapView.subviews where object.theClassName == "GMSUISettingsPaddingView" {
for view in object.subviews where view.theClassName == "GMSUISettingsView" {
for btn in view.subviews where btn.theClassName == "GMSx_MDCFloatingButton" {
btn.snp.makeConstraints({ maker in
maker.left.equalToSuperview().offset(15)
})
btn.snp.makeConstraints({ maker in
maker.bottom.equalToSuperview().offset(-30)
})
return
}
}
}
}
Use this function and call this at viewDidLayoutSubviews()
func changeLocationBtnFrame(){
self.mapView.settings.myLocationButton = true
for obj in self.mapView.subviews{
if String(describing: obj.classForCoder) == "GMSUISettingsPaddingView"{
for viw in obj.subviews{
if String(describing: viw.classForCoder) == "GMSUISettingsView"{
for btn in viw.subviews{
if String(describing: btn.classForCoder) == "GMSx_MDCFloatingButton"{
var frame = btn.frame
btn.frame = CGRect(x: 316, y: 0, width: 56, height: 56)
//USE you want frame this line
break
}
}
break
}
}
break
}
}
}

swift- Assigning the value to the custom button from subview

Hi I'm trying to assign the particular subview in custom buttons in for loop it is working fine in objective c, but I'm facing problem in swift code.
objective c code:
HButton *view = nil; // hbutton class object
NSArray *subviews = [self.superview subviews]; // storing the subviews in subviews array
NSInteger i = 0; // for index
for (view in subviews)
{
if ([view isKindOfClass:[HButton class]])
{
HButton *check=[subviews objectAtIndex:i];
if (self.tag == check.tag)
{
self.titleLabel.text =#"hel";
self.backgroundColor =[UIColor orangeColor];
}
i++;
}
Swift Code:
var view1 :BTNCustom! = nil
var subviews : NSArray = self.superview.subviews
var ival : Int = 0;
for view1 : AnyObject in subviews // I'm getting the error in this line
{
if view1.isKindOfClass(BTNCustom)
{
var check : BTNCustom = subviews.objectAtIndex(ival) as BTNCustom
if (self.tag == check.tag)
{
self.titleLabel.text = "hel";
self.backgroundColor = UIColor.orangeColor()
}
ival++;
}
}
Can you be more clear what you are actually trying to do? The following is the closest thing I can imagine to what you are trying to do:
for view in superview.subviews {
if view.isKindOfClass(BTNCustom) && tag == view.tag {
titleLabel.text = "hel"
backgroundColor = UIColor.orangeColor()
}
}
However, it really looks like you are abusing tags and not really understanding the point of a type system.
Specific problems with your code:
1) var view1 :BTNCustom! = nil you are assigning nil to something that you are saying you want to be automatically unwrapped, why not just var view1: BTNCustom!, they should be the same but the latter is much more palatable.
2) You are trying to use view1 as your loop variable of a different type when you have already declared it to be BTNCustom. Why? Are you really using variable names like this? Do you have a limit on the number of characters you are allowed to use?
3) What is the point of using an extra integer to pull views out of the subview array when in fact that view is exactly the one you are already have a reference to? To be clear, in your Obj-C code, both view and check will always be identical whenever you actually use check.
Just trying to solved your error may this will help you!
for index in 1...subviews
{
let view1:AnyObject = subViews[index] as AnyObject
if view1.isKindOfClass(BTNCustom)
{
var check : BTNCustom = subviews.objectAtIndex(ival) as BTNCustom
if (self.tag == check.tag)
{
self.titleLabel.text = "hel";
self.backgroundColor = UIColor.orangeColor()
}
ival++;
}
}

Is it possible to change UITabBarItem badge color

I want to change background color of UITabBarItem badge but can't find any resource on how to make it.
UITabBarItem has this available since iOS 10.
var badgeColor: UIColor? { get set }
It's also available via appearence.
if #available(iOS 10, *) {
UITabBarItem.appearance().badgeColor = .green
}
reference docs:
https://developer.apple.com/reference/uikit/uitabbaritem/1648567-badgecolor
Changing the badge-color is now natively supported in iOS 10 and later using the badgeColor property inside your UITabBarItem. See the apple docs for more infos on the property.
Example:
Swift 3: myTab.badgeColor = UIColor.blue
Objective-C: [myTab setBadgeColor:[UIColor blueColor]];
I wrote this piece of code for my app, but I have only tested it in iOS 7.
for (UIView* tabBarButton in self.tabBar.subviews) {
for (UIView* badgeView in tabBarButton.subviews) {
NSString* className = NSStringFromClass([badgeView class]);
// looking for _UIBadgeView
if ([className rangeOfString:#"BadgeView"].location != NSNotFound) {
for (UIView* badgeSubview in badgeView.subviews) {
NSString* className = NSStringFromClass([badgeSubview class]);
// looking for _UIBadgeBackground
if ([className rangeOfString:#"BadgeBackground"].location != NSNotFound) {
#try {
[badgeSubview setValue:[UIImage imageNamed:#"YourCustomImage.png"] forKey:#"image"];
}
#catch (NSException *exception) {}
}
if ([badgeSubview isKindOfClass:[UILabel class]]) {
((UILabel *)badgeSubview).textColor = [UIColor greenColor];
}
}
}
}
}
You're only able to update the badge background with an image, not a color. I have also exposed the badge label if you wanted to update that in some way.
Its important to note that this code must be called after setting the tabBarItem.badgeValue!
EDIT: 4/14/14
The above code will work in iOS 7 when called anywhere. To get it working in iOS 7.1 call it in the view controllers -viewWillLayoutSubviews.
EDIT: 12/22/14
Here's an updated snippet which I'm currently using. I put the code in a category extension for simplicity.
- (void)badgeViews:(void (^)(UIView* badgeView, UILabel* badgeLabel, UIView* badgeBackground))block {
if (block) {
for (UIView* tabBarButton in self.subviews) {
for (UIView* badgeView in tabBarButton.subviews) {
NSString* className = NSStringFromClass([badgeView class]);
if ([className rangeOfString:#"BadgeView"].location != NSNotFound) {
UILabel* badgeLabel;
UIView* badgeBackground;
for (UIView* badgeSubview in badgeView.subviews) {
NSString* className = NSStringFromClass([badgeSubview class]);
if ([badgeSubview isKindOfClass:[UILabel class]]) {
badgeLabel = (UILabel *)badgeSubview;
} else if ([className rangeOfString:#"BadgeBackground"].location != NSNotFound) {
badgeBackground = badgeSubview;
}
}
block(badgeView, badgeLabel, badgeBackground);
}
}
}
}
}
Then when you're ready to call it, it'll look like this.
[self.tabBar badgeViews:^(UIView *badgeView, UILabel *badgeLabel, UIView *badgeBackground) {
}];
EDIT: 11/16/15
It's been brought to my attention that some people need a little more clarity on what's happening in this code. The for loops are searching for a few views which are not publicly accessible. By checking if the views class name contains a part of the expected name, it's ensuring to reach the intended view while not setting off any possible red flags by Apple. Once everything has been located, a block is executed with easy access to these views.
It's noteworthy that the possibility exists for this code to stop working in a future iOS update. For example these internal views could one day acquire different class names. However the chances of that are next to none since even internally Apple rarely refactors classes to this nature. But even if they were to, it would be something along the title of UITabBarBadgeView, which would still reach the expected point in code. Being that iOS9 is well out the door and this code is still working as intended, you can expect this problem to never arise.
I have the same problem and solved it by creating a little category that replace the BadgeView with an UILabel that you can customize easily.
https://github.com/enryold/UITabBarItem-CustomBadge/
For people using Swift, I managed to improve on TimWhiting answer in order to have the badge view working on any screen size and any orientation.
extension UITabBarController {
func setBadges(badgeValues: [Int]) {
for view in self.tabBar.subviews {
if view is CustomTabBadge {
view.removeFromSuperview()
}
}
for index in 0...badgeValues.count-1 {
if badgeValues[index] != 0 {
addBadge(index, value: badgeValues[index], color:UIColor(paletteItem: .Accent), font: UIFont(name: Constants.ThemeApp.regularFontName, size: 11)!)
}
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
let badgeView = CustomTabBadge()
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.whiteColor()
badgeView.textAlignment = .Center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = color
badgeView.tag = index
tabBar.addSubview(badgeView)
self.positionBadges()
}
override public func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.positionBadges()
}
// Positioning
func positionBadges() {
var tabbarButtons = self.tabBar.subviews.filter { (view: UIView) -> Bool in
return view.userInteractionEnabled // only UITabBarButton are userInteractionEnabled
}
tabbarButtons = tabbarButtons.sort({ $0.frame.origin.x < $1.frame.origin.x })
for view in self.tabBar.subviews {
if view is CustomTabBadge {
let badgeView = view as! CustomTabBadge
self.positionBadge(badgeView, items:tabbarButtons, index: badgeView.tag)
}
}
}
func positionBadge(badgeView: UIView, items: [UIView], index: Int) {
let itemView = items[index]
let center = itemView.center
let xOffset: CGFloat = 12
let yOffset: CGFloat = -14
badgeView.frame.size = CGSizeMake(17, 17)
badgeView.center = CGPointMake(center.x + xOffset, center.y + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width/2
tabBar.bringSubviewToFront(badgeView)
}
}
class CustomTabBadge: UILabel {}
No you can't change the color but you can use your own badges instead. Add this extension at the file scope and you can customise the badges however you like. Just call self.tabBarController!.setBadges([1,0,2]) in any of your root view controllers.
To be clear that is for a tab bar with three items, with the badge values going from left to right.
extension UITabBarController {
func setBadges(badgeValues:[Int]){
var labelExistsForIndex = [Bool]()
for value in badgeValues {
labelExistsForIndex.append(false)
}
for view in self.tabBar.subviews {
if view.isKindOfClass(PGTabBadge) {
let badgeView = view as! PGTabBadge
let index = badgeView.tag
if badgeValues[index]==0 {
badgeView.removeFromSuperview()
}
labelExistsForIndex[index]=true
badgeView.text = String(badgeValues[index])
}
}
for var i=0;i<labelExistsForIndex.count;i++ {
if labelExistsForIndex[i] == false {
if badgeValues[i] > 0 {
addBadge(i, value: badgeValues[i], color:UIColor(red: 4/255, green: 110/255, blue: 188/255, alpha: 1), font: UIFont(name: "Helvetica-Light", size: 11)!)
}
}
}
}
func addBadge(index:Int,value:Int, color:UIColor, font:UIFont){
let itemPosition = CGFloat(index+1)
let itemWidth:CGFloat = tabBar.frame.width / CGFloat(tabBar.items!.count)
let bgColor = color
let xOffset:CGFloat = 12
let yOffset:CGFloat = -9
var badgeView = PGTabBadge()
badgeView.frame.size=CGSizeMake(17, 17)
badgeView.center=CGPointMake((itemWidth * itemPosition)-(itemWidth/2)+xOffset, 20+yOffset)
badgeView.layer.cornerRadius=badgeView.bounds.width/2
badgeView.clipsToBounds=true
badgeView.textColor=UIColor.whiteColor()
badgeView.textAlignment = .Center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = bgColor
badgeView.tag=index
tabBar.addSubview(badgeView)
}
}
class PGTabBadge: UILabel {
}
Swift 3 Here is an updated version of #Kirualex's answer (who improved on #TimWhiting's answer) for Swift 3.
extension UITabBarController {
func setBadges(badgeValues: [Int]) {
for view in self.tabBar.subviews {
if view is CustomTabBadge {
view.removeFromSuperview()
}
}
for index in 0...badgeValues.count-1 {
if badgeValues[index] != 0 {
addBadge(index: index, value: badgeValues[index], color: UIColor.blue, font: UIFont(name: "Helvetica-Light", size: 11)!)
}
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
let badgeView = CustomTabBadge()
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.white
badgeView.textAlignment = .center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = color
badgeView.tag = index
tabBar.addSubview(badgeView)
self.positionBadges()
}
override open func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.positionBadges()
}
// Positioning
func positionBadges() {
var tabbarButtons = self.tabBar.subviews.filter { (view: UIView) -> Bool in
return view.isUserInteractionEnabled // only UITabBarButton are userInteractionEnabled
}
tabbarButtons = tabbarButtons.sorted(by: { $0.frame.origin.x < $1.frame.origin.x })
for view in self.tabBar.subviews {
if view is CustomTabBadge {
let badgeView = view as! CustomTabBadge
self.positionBadge(badgeView: badgeView, items:tabbarButtons, index: badgeView.tag)
}
}
}
func positionBadge(badgeView: UIView, items: [UIView], index: Int) {
let itemView = items[index]
let center = itemView.center
let xOffset: CGFloat = 12
let yOffset: CGFloat = -14
badgeView.frame.size = CGSize(width: 17, height: 17)
badgeView.center = CGPoint(x: center.x + xOffset, y: center.y + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width/2
tabBar.bringSubview(toFront: badgeView)
}
}
class CustomTabBadge: UILabel {}
It appears that no. You may only set the value.
From Apple's documentation badge is:
Text that is displayed in the upper-right corner of the item with a
surrounding red oval.
You need to specify tab item at index to change badge color, #available in iOS 10 ,
if #available(iOS 10.0, *)
{
self.kAppTabBarController.tabBar.items![1].badgeColor = YOUR_COLOR
}
You can now do it in the storyboard too, by selecting your tab bar item and going to the attributes inspector.
Since iOS 15 has different approach, what worked in my case:
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
let barAppearance = UITabBarItemAppearance()
barAppearance.normal.badgeBackgroundColor = .green
barAppearance.normal.badgeTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.red]
appearance.stackedLayoutAppearance = barAppearance
tabBar.standardAppearance = appearance
YES, But the only possible solution is to create a custom Tabbar and creating your custom tabbar badge icon. You will find many article/code for creating custom tabbar.
// change TabBar BadgeView background Color
-(void)changeTabBarBadgeViewBgColor:(UITabBar*)tabBar {
for (UIView* tabBarButton in tabBar.subviews) {
for (UIView* badgeView in tabBarButton.subviews) {
NSString* className = NSStringFromClass([badgeView class]);
// looking for _UIBadgeView
if ([className rangeOfString:#"BadgeView"].location != NSNotFound) {
for (UIView* badgeSubview in badgeView.subviews) {
NSString* className = NSStringFromClass([badgeSubview class]);
// looking for _UIBadgeBackground
if ([className rangeOfString:#"BadgeBackground"].location != NSNotFound) {
#try {
[badgeSubview setValue:nil forKey:#"image"];
[badgeSubview setBackgroundColor:[UIColor blueColor]];
badgeSubview.clipsToBounds = YES;
badgeSubview.layer.cornerRadius = badgeSubview.frame.size.height/2;
}
#catch (NSException *exception) {}
}
if ([badgeSubview isKindOfClass:[UILabel class]]) {
((UILabel *)badgeSubview).textColor = [UIColor greenColor];
}
}
}
}
}
}
Hm...it's very easy.
[[self tabBarItem] setBadgeColor:[UIColor greenColor]];
Add below lines of code in UITabBarController :
class RootTabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
if #available(iOS 13.0, *) {
let appearance = tabBar.standardAppearance.copy()
setTabBarItemBadgeAppearance(appearance.stackedLayoutAppearance)
setTabBarItemBadgeAppearance(appearance.inlineLayoutAppearance)
setTabBarItemBadgeAppearance(appearance.compactInlineLayoutAppearance)
tabBar.standardAppearance = appearance
if #available(iOS 15.0, *) {
tabBar.scrollEdgeAppearance = appearance
}
}
// Do any additional setup after loading the view.
}
#available(iOS 13.0, *)
private func setTabBarItemBadgeAppearance(_ itemAppearance: UITabBarItemAppearance) {
itemAppearance.normal.badgeBackgroundColor = UIColor.colorBlue207DFF
}
}
Since iOS 15 / Xcode 13, you have to set stackedLayoutAppearance property to change badge color on UITabBarItem. Change just ".blue" with you own color:
if #available(iOS 15.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.stackedLayoutAppearance.normal.badgeBackgroundColor = .blue
UITabBar.appearance().standardAppearance = appearance
UITabBar.appearance().scrollEdgeAppearance = appearance
}
Tested on Xcode 14.1 / iOS 16.
Take a look here # UITabbarItem-CustomBadge.
A complete demonstration is following
it takes only two line of code, if you want to use the default implementation
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//supplying the animation parameter
[UITabBarItem setDefaultAnimationProvider:[[DefaultTabbarBadgeAnimation alloc] init]];
[UITabBarItem setDefaultConfigurationProvider:[[DefaultSystemLikeBadgeConfiguration alloc] init]];
//rest of your code goes following...
return YES;
}

How to make a UIScrollView auto scroll when a UITextField becomes a first responder

I've seen posts around here that suggest that UIScrollViews should automatically scroll if a subview UITextField becomes the first responder; however, I can't figure out how to get this to work.
What I have is a UIViewController that has a UIScrollView and within the UIScrollView there are multiple textfields.
I know how to do this manually if necessary; however, from what I've been reading, it seems possible to have it autoscroll. Help please.
I hope this example will help you
You can scroll to any point by this code.
scrollView.contentOffset = CGPointMake(0,0);
So if you have textfield, it must have some x,y position on view, so you can use
CGPoint point = textfield.frame.origin ;
scrollView.contentOffset = point
This should do the trick,
But if you don't know when to call this code, so you should learn UITextFieldDelegate methods
Implement this method in your code
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// Place Scroll Code here
}
I hope you know how to use delegate methods.
I know this question has already been answered, but I thought I would share the code combination that I used from #Adeel and #Basil answer, as it seems to work perfectly for me on iOS 9.
-(void)textFieldDidBeginEditing:(UITextField *)textField {
// Scroll to the text field so that it is
// not hidden by the keyboard during editing.
[scroll setContentOffset:CGPointMake(0, (textField.superview.frame.origin.y + (textField.frame.origin.y))) animated:YES];
}
-(void)textFieldDidEndEditing:(UITextField *)textField {
// Remove any content offset from the scroll
// view otherwise the scroll view will look odd.
[scroll setContentOffset:CGPointMake(0, 0) animated:YES];
}
I also used the animated method, it makes for a much smoother transition.
Here is the Swift 4 update to #Supertecnoboff's answer. It worked great for me.
func textFieldDidBeginEditing(_ textField: UITextField) {
scroll.setContentOffset(CGPoint(x: 0, y: (textField.superview?.frame.origin.y)!), animated: true)
}
func textFieldDidEndEditing(_ textField: UITextField) {
scroll.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}
Make sure to extend UITextFieldDelegate and set the textfields' delegate to self.
There is nothing you have to do manually. It is the default behavior. There are two possibilities as to why you are not seeing the behavior
The most likely reason is that the keyboard is covering your UITextField. See below for solution
The other possibility is that you have another UIScrollView somewhere in the view hierarchy between the UITextField and the UIScrollView that you want to auto scroll. This is less likely but can still cause problems.
For #1, you want to implement something similar to Apple's recommendations for Moving Content That Is Located Under the Keyboard. Note that the code provided by Apple does not account for rotation. For improvements on their code, check out this blog post's implementation of the keyboardDidShow method that properly translates the keyboard's frame using the window.
- (void)textFieldDidBeginEditing:(UITextField *)textField {
CGRect rect = [textField bounds];
rect = [textField convertRect:rect toView:self.scrollView];
rect.origin.x = 0 ;
rect.origin.y -= 60 ;
rect.size.height = 400;
[self.scrollView scrollRectToVisible:rect animated:YES];
}
You can use this function for autoScroll of UITextField
on UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UITextField *)textField {
[self autoScrolTextField:textField onScrollView:self.scrollView];
}
- (void) autoScrolTextField: (UITextField *) textField onScrollView: (UIScrollView *) scrollView {
float slidePoint = 0.0f;
float keyBoard_Y_Origin = self.view.bounds.size.height - 216.0f;
float textFieldButtomPoint = textField.superview.frame.origin.y + (textField.frame.origin.y + textField.frame.size.height);
if (keyBoard_Y_Origin < textFieldButtomPoint - scrollView.contentOffset.y) {
slidePoint = textFieldButtomPoint - keyBoard_Y_Origin + 10.0f;
CGPoint point = CGPointMake(0.0f, slidePoint);
scrollView.contentOffset = point;
}
EDIT:
Im now using IQKeyboardManager
Kudos to the developer of this, you need to try this.
Solution
extension UIScrollView {
func scrollVerticallyToFirstResponderSubview(keyboardFrameHight: CGFloat) {
guard let firstResponderSubview = findFirstResponderSubview() else { return }
scrollVertically(toFirstResponder: firstResponderSubview,
keyboardFrameHight: keyboardFrameHight, animated: true)
}
private func scrollVertically(toFirstResponder view: UIView,
keyboardFrameHight: CGFloat, animated: Bool) {
let scrollViewVisibleRectHeight = frame.height - keyboardFrameHight
let maxY = contentSize.height - scrollViewVisibleRectHeight
if contentOffset.y >= maxY { return }
var point = view.convert(view.bounds.origin, to: self)
point.x = 0
point.y -= scrollViewVisibleRectHeight/2
if point.y > maxY {
point.y = maxY
} else if point.y < 0 {
point.y = 0
}
setContentOffset(point, animated: true)
}
}
extension UIView {
func findFirstResponderSubview() -> UIView? { getAllSubviews().first { $0.isFirstResponder } }
func getAllSubviews<T: UIView>() -> [T] { UIView.getAllSubviews(from: self) as [T] }
class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] {
parenView.subviews.flatMap { subView -> [T] in
var result = getAllSubviews(from: subView) as [T]
if let view = subView as? T { result.append(view) }
return result
}
}
}
Full Sample
Do not forget to paste the Solution code here
import UIKit
class ViewController: UIViewController {
private weak var scrollView: UIScrollView!
private lazy var keyboard = KeyboardNotifications(notifications: [.willHide, .willShow], delegate: self)
override func viewDidLoad() {
super.viewDidLoad()
let scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.contentSize = CGSize(width: view.frame.width, height: 1000)
scrollView.isScrollEnabled = true
scrollView.indicatorStyle = .default
scrollView.backgroundColor = .yellow
scrollView.keyboardDismissMode = .interactive
self.scrollView = scrollView
addTextField(y: 20)
addTextField(y: 300)
addTextField(y: 600)
addTextField(y: 950)
}
private func addTextField(y: CGFloat) {
let textField = UITextField()
textField.borderStyle = .line
scrollView.addSubview(textField)
textField.translatesAutoresizingMaskIntoConstraints = false
textField.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: y).isActive = true
textField.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 44).isActive = true
textField.widthAnchor.constraint(equalToConstant: 120).isActive = true
textField.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
keyboard.isEnabled = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
keyboard.isEnabled = false
}
}
extension ViewController: KeyboardNotificationsDelegate {
func keyboardWillShow(notification: NSNotification) {
guard let userInfo = notification.userInfo as? [String: Any],
let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
scrollView.contentInset.bottom = keyboardFrame.height
scrollView.scrollVerticallyToFirstResponderSubview(keyboardFrameHight: keyboardFrame.height)
}
func keyboardWillHide(notification: NSNotification) {
scrollView.contentInset.bottom = 0
}
}
/// Solution
extension UIScrollView {
func scrollVerticallyToFirstResponderSubview(keyboardFrameHight: CGFloat) {
guard let firstResponderSubview = findFirstResponderSubview() else { return }
scrollVertically(toFirstResponder: firstResponderSubview,
keyboardFrameHight: keyboardFrameHight, animated: true)
}
private func scrollVertically(toFirstResponder view: UIView,
keyboardFrameHight: CGFloat, animated: Bool) {
let scrollViewVisibleRectHeight = frame.height - keyboardFrameHight
let maxY = contentSize.height - scrollViewVisibleRectHeight
if contentOffset.y >= maxY { return }
var point = view.convert(view.bounds.origin, to: self)
point.x = 0
point.y -= scrollViewVisibleRectHeight/2
if point.y > maxY {
point.y = maxY
} else if point.y < 0 {
point.y = 0
}
setContentOffset(point, animated: true)
}
}
extension UIView {
func findFirstResponderSubview() -> UIView? { getAllSubviews().first { $0.isFirstResponder } }
func getAllSubviews<T: UIView>() -> [T] { UIView.getAllSubviews(from: self) as [T] }
class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] {
parenView.subviews.flatMap { subView -> [T] in
var result = getAllSubviews(from: subView) as [T]
if let view = subView as? T { result.append(view) }
return result
}
}
}
// https://stackoverflow.com/a/42600092/4488252
import Foundation
protocol KeyboardNotificationsDelegate: class {
func keyboardWillShow(notification: NSNotification)
func keyboardWillHide(notification: NSNotification)
func keyboardDidShow(notification: NSNotification)
func keyboardDidHide(notification: NSNotification)
}
extension KeyboardNotificationsDelegate {
func keyboardWillShow(notification: NSNotification) {}
func keyboardWillHide(notification: NSNotification) {}
func keyboardDidShow(notification: NSNotification) {}
func keyboardDidHide(notification: NSNotification) {}
}
class KeyboardNotifications {
fileprivate var _isEnabled: Bool
fileprivate var notifications: [NotificationType]
fileprivate weak var delegate: KeyboardNotificationsDelegate?
fileprivate(set) lazy var isKeyboardShown: Bool = false
init(notifications: [NotificationType], delegate: KeyboardNotificationsDelegate) {
_isEnabled = false
self.notifications = notifications
self.delegate = delegate
}
deinit { if isEnabled { isEnabled = false } }
}
// MARK: - enums
extension KeyboardNotifications {
enum NotificationType {
case willShow, willHide, didShow, didHide
var selector: Selector {
switch self {
case .willShow: return #selector(keyboardWillShow(notification:))
case .willHide: return #selector(keyboardWillHide(notification:))
case .didShow: return #selector(keyboardDidShow(notification:))
case .didHide: return #selector(keyboardDidHide(notification:))
}
}
var notificationName: NSNotification.Name {
switch self {
case .willShow: return UIResponder.keyboardWillShowNotification
case .willHide: return UIResponder.keyboardWillHideNotification
case .didShow: return UIResponder.keyboardDidShowNotification
case .didHide: return UIResponder.keyboardDidHideNotification
}
}
}
}
// MARK: - isEnabled
extension KeyboardNotifications {
private func addObserver(type: NotificationType) {
NotificationCenter.default.addObserver(self, selector: type.selector, name: type.notificationName, object: nil)
}
var isEnabled: Bool {
set {
if newValue {
for notificaton in notifications { addObserver(type: notificaton) }
} else {
NotificationCenter.default.removeObserver(self)
}
_isEnabled = newValue
}
get { _isEnabled }
}
}
// MARK: - Notification functions
extension KeyboardNotifications {
#objc func keyboardWillShow(notification: NSNotification) {
delegate?.keyboardWillShow(notification: notification)
isKeyboardShown = true
}
#objc func keyboardWillHide(notification: NSNotification) {
delegate?.keyboardWillHide(notification: notification)
isKeyboardShown = false
}
#objc func keyboardDidShow(notification: NSNotification) {
isKeyboardShown = true
delegate?.keyboardDidShow(notification: notification)
}
#objc func keyboardDidHide(notification: NSNotification) {
isKeyboardShown = false
delegate?.keyboardDidHide(notification: notification)
}
}
If you have multiple textfields say Textfield1, Textfield2, Textfield3 and you want to scroll the scrollview along the y-axis when textfield2 becomes first responder:
if([Textfield2 isFirstResponder])
{
scrollView.contentOffset = CGPointMake(0,yourY);
}
As Michael McGuire mentioned in his point #2 above, the system's default behavior misbehaves when the scroll view contains another scroll view between the text field and the scroll view. I've found that the misbehavior also occurs when there's a scroll view merely next to the text field (both embedded in the scroll view that needs to be adjusted to bring the text field into view when the text field wants to start editing. This is on iOS 12.1.
But my solution is different from the above. In my top-level scroll view, which is sub-classed so I can add properties and override methods, I override scrollRectToVisible:animated:. It simply calls its [super scrollRectToVisible:animated:] unless there's a property set that tells it to adjust the rect passed in, which is the frame of the text field. When the property is non-nil, it is a reference to the UITextField in question, and the rect is adjusted so that the scroll view goes further than the system thought it would. So I put this in the UIScrollView's sub-classed header file:
#property (nullable) UITextField *textFieldToBringIntoView;
(with appropriate #synthesize textFieldToBringIntoView; in the implementation. Then I added this override method to the implementation:
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)how
{
if (textFieldToBringIntoView) {
// Do whatever mucking with `rect`'s origin needed to make it visible
// based on context or its spatial relationship with the other
// view that the system is getting confused by.
textFieldToBringIntoView = nil; // Go back to normal
}
[super scrollRectToVisible:rect animated:how];
}
In the delegate method for the UITextField for when it's about to begin editing, just set textFieldToBringIntoView to the textField in question:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
// Ensure it scrolls into view so that keyboard doesn't obscure it
// The system is about to call |scrollRectIntoView:| for the scrolling
// superview, but the system doesn't get things right in certain cases.
UIScrollView *parent = (UIScrollView *)textField.superview;
// (or figure out the parent UIScrollView some other way)
// Tell the override to do something special just once
// based on this text field's position in its parent's scroll view.
parent.textFieldToBringIntoView = textField;
// The override function will set this back to nil
return(YES);
}
It seems to work. And if Apple fixes their bug, it seems like it might still work (fingers crossed).
Building off of Vasily Bodnarchuk's answer I created a gist with a simple protocol that you can implement and it'll do it all for you.
All you need to do is call registerAsTextDisplacer()
I created a BaseViewController in my project and made that implement it
https://gist.github.com/CameronPorter95/cb68767f5f8052fdc70293c167e9430e
Other solutions I saw, let you set the offset to the origin of the textField but this makes the scroller view go beyond it bounds.
I did this adjustment to the offset instead to not go beyond the bottom nor the top offsets.
Set the keyboardHeightConstraint to the bottom of the page.
When the keyboard shows, update its constraint's constant to negative the keyboard height.
Then scroll to the responderField as we will show below.
#IBOutlet var keyboardHeightConstraint: NSLayoutConstraint?
var responderField: String?
#objc func keyboardNotification(notification: NSNotification) {
guard let keyboardValue = notification.userInfo [UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardHeight = keyboardValue.cgRectValue.height
keyboardHeightConstraint?.constant = -keyboardHeight
scroll(field: responderField!)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
responderField = textField
}
Now we want to make sure we do not scroll greater than the bottom offset nor less than the top offset.
At the same time, we want to calculate the offset of the field's maxY value.
To do that, we subtract the scrollView.bounds.size.height from the maxY value.
let targetOffset = field.frame.maxY - scrollView.bounds.size.height
I found it nicer to scroll an extra distance of the keyboard height, but you could neglect that if you want to scroll right below the field.
let targetOffset = keyboardHeight + field.frame.maxY - scrollView.bounds.size.height
Remember to add the scrollView.contentInset.bottom if you have the tab bar visible.
func scroll(field: UITextField) {
guard let keyboardConstraintsConstant = keyboardHeightConstraint?.constant else { return }
let keyboardHeight = -keyboardConstraintsConstant
view.layoutIfNeeded()
let bottomOffset = scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom
let topOffset = -scrollView.safeAreaInsets.top
let targetOffset = keyboardHeight + field.frame.maxY + scrollView.contentInset.bottom - scrollView.bounds.size.height
let adjustedOffset = targetOffset > bottomOffset ? bottomOffset : (targetOffset < topOffset ? topOffset : targetOffset)
scrollView.setContentOffset(CGPoint(x: 0, y: adjustedOffset), animated: true)
}
If you have scrollView and tableView with invalidating intrinsicContentSize as the subview, you can disable tableView scrolling in storyboard or set tableView.isScrollEnabled to false in code.

Can UITableView Section Headers be Changed when They are 'Floating'?

On iOS devices the section headers in UITableView's have a nice behavior where they stick or 'float' to the top of the screen as you scroll through a section. The section headers in my particular case are loaded from other XIB files.
Is it possible to change the section headers depending on whether or not they are currently floating? Specifically I'd like to add a small shadow to appear under the header only while it's stuck to the top of the view.
Thanks!
Here's the function I created to update whether each header has a shadow or not. All the section headers in this case are a UIView subclass ListHeader. They're retained and returned by the viewForHeaderInSection function.
- (void) updateHeaderShadows {
int i=0;
int sectionHeight = 0;
int totalHeight = 0;
UIView * sectionHeader;
while (i<[self numberOfSectionsInTableView:self.tableView]) {
sectionHeight = [self.tableView rectForSection:i].size.height;
sectionHeader = [self tableView:self.tableView viewForHeaderInSection:i];
if ([sectionHeader respondsToSelector:#selector(shadow)]) {
if (sectionHeader.frame.origin.y == totalHeight || sectionHeader.frame.origin.y == totalHeight + sectionHeight - sectionHeader.frame.size.height) {
[((ListHeader *) sectionHeader).shadow setHidden:YES];
} else {
[((ListHeader *) sectionHeader).shadow setHidden:NO];
}
}
totalHeight += sectionHeight;
i++;
}
}
I haven't tested it yet, but I don't see any reason why it wouldn't be possible.
Just make sure you set the right bounds (because your shadow needs to be on top of your view, not above it).
You can use the following approach:
Use scrollView:didScroll: to get notified about scroll-events.
In this method, check whether you need to add your shadow-view to your (floating) header-view.
If so, add it. (just [view addSubview:shadowView].) Something like CGRectMake(0.f, yourDefaultHeaderHeight, 320.f, yourShadowHeight) should be the frame of your shadowView.
Now, update the bounds of view, so it can show your shadowView: CGRectMake(0.f, 0.f - yourShadowHeight, 320.f, yourDefaultHeaderHeight + 2 * yourShadowHeight).
When you find out that your header isn't floating anymore (by using scrollView:didScroll:), remove the shadow-view.
Your headerViews bounds should be 0.f - yourShadowHeight because if you use just 0.f, it'll blur (I don't know why ...).
You would have to have your own UIView in the header. Then you would need a reference to it. Then hook into scrollViewWillBeginDragging: with your UIScrollViewDelegate. In that function, add the shadow to the custom view.
Hook into scrollViewDidEndDragging:willDecelerate: and remove the shadow in this function.
#Anthony Mattox answer for Swift
protocol SectionHeaderWithShadowProtocol where Self: UIView {
var shadow: Bool { get set }
}
class SectionHeaderView: UITableViewHeaderFooterView, SectionHeaderWithShadowProtocol {
#IBOutlet weak var shadowView: UIView!
var shadow: Bool = false {
didSet {
shadowView.isHidden = shadow
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateHeaderShadows()
}
func updateHeaderShadows() {
var i = 0
var sectionHeight: CGFloat = 0
var totalHeight: CGFloat = 0
while i < numberOfSections() {
sectionHeight = tableView.rect(forSection: i).size.height
if let sectionHeader = tableView.headerView(forSection: i) as? SectionHeaderWithShadowProtocol {
if sectionHeader.frame.origin.y == totalHeight || sectionHeader.frame.origin.y == totalHeight + sectionHeight - sectionHeader.frame.size.height {
sectionHeader.shadow = false
} else {
sectionHeader.shadow = true
}
}
totalHeight += sectionHeight
i += 1
}
}

Categories