I am using JSQMessagesViewController for my chat application. When there is no internet activity I would like to hide the inputToolbar
I tried this, but that does not work:
self.inputToolbar.frame.size = CGSize(width: 0,height: 0)
When I set this, then for less than a second it's gone:
self.inputToolbar.preferredDefaultHeight = 0
Any idea how to do this?
Maybe disabling the inputToolbar could also be good enough.
I found a better solution which doesn't have any side effects.
You can make the actions in a descendant class of JSQMessagesViewController.
1. Make this method of base class available for you:
#interface JSQMessagesViewController ()
- (void)jsq_setCollectionViewInsetsTopValue:(CGFloat)top
bottomValue:(CGFloat)bottom;
#end
2. Override parent realization of method (called when size changed):
- (void)jsq_updateCollectionViewInsets {
CGFloat topInset = self.topLayoutGuide.length + self.topContentAdditionalInset;
CGFloat bottomInset = 0.0;
[self jsq_setCollectionViewInsetsTopValue:topInset bottomValue:bottomInset];
}
3. Write the method to hide input toolbar forever:
- (void)hideInputToolbar {
self.inputToolbar.hidden = YES;
[self jsq_updateCollectionViewInsets];
}
4. Enjoy!
Instead of removing from superview and having to add back as a subview, why not just use:
[self.inputToolbar setHidden:YES];
It turned out that this will work:
override func viewDidLoad() {
super.viewDidLoad()
self.inputToolbar.removeFromSuperview()
}
Related
I am trying to find a way to change the tint color of the backBarButtonItem based on the scroll position. In the example below, the button should be yellow by default, but then at a certain threshold it should change to red.
Although using breakpoints I can see the code triggering in each block, but unfortunately backBarButtonItem never changes to red and always remains yellow. Any suggestions on why this might be the case? I'm assuming that you might not be able to change the back button in the navigation bar once it's already set.
CGFloat totalHeight = CGRectGetMaxY(self.frame);
CGFloat barHeight = CGRectGetHeight(self.frame);
CGFloat offsetHeight = (self.scrollview.contentOffset.y - self.scrollViewMinimumOffset) + totalHeight;
offsetHeight = MAX(offsetHeight, 0.0f);
offsetHeight = MIN(offsetHeight, totalHeight);
if (offsetHeight > barHeight * 1.0f) {
[self.backBarButtonItem setTintColor:[UIColor redColor]];
} else {
[self.backBarButtonItem setTintColor:[UIColor yellowColor]];
}
Let me provide the following example that can help you figure out or gain some ideas to better address the issue.
So in the storyboard (can be done programmatically), I have the following scenario:
That backBarButtonItem is actually 1stVC button in the NavigationBar.
In order to change the color of backBarButtonItem, you may implement the following code (or take a look):
import UIKit
class ViewController2: UIViewController {
var counter = 0 //any conditions you want to play with
override func viewDidLoad() {
super.viewDidLoad()
var color: UIColor = UIColor.purple //or yellow, by default
if(counter == 0){
color = UIColor.red
}
self.navigationController?.navigationBar.tintColor = color
}
}
It is done in the viewDidLoad() method of ViewController2 so that it can get configured as soon as this ViewController is opened.
Here, I just used counter variable as a simple example to create some condition based on which the color of backBarButtonItem should be changed. In your case, you have another condition.
So this is the output:
I have tried EVERYTHING to get this to work. I setup a custom class like so.
override func layoutSubviews() {
super.layoutSubviews()
clearBackgroundColor() // function in the question
}
private func clearBackgroundColor() {
guard let UISearchBarBackground: AnyClass = NSClassFromString("UISearchBarBackground") else { return }
for view in self.subviews {
for subview in view.subviews {
if subview.isKind(of: UISearchBarBackground) {
subview.alpha = 0
}
}
}
}
I set backgroundColor, barTintColor to .clear. Style to minimal. Im losing my mind. I set breakpoints to make sure we are finding the search bar background. Ive tried subview.removeFromSuperview() as well. Nothing. I think Im going insane. Am I missing something?
This is on iOS 10 and am using storyboard. Any help would be greatly appreciated.
I had to do this in a client's app a while ago. Here's what worked for me:
I had a UISearchBar subclass:
#property (nonatomic, strong) UITextField* textField;
I called the following from init:
self.textField = [self findViewOfClass:[UITextField class] inView:self];
self.translucent = NO;
self.barTintColor = ...;
self.textField.backgroundColor = ...;
- (id)findViewOfClass:(Class)class inView:(UIView*)view
{
if ([view isKindOfClass:class])
{
return view;
}
else
{
for (UIView* subview in view.subviews)
{
id foundView = [self findViewOfClass:class inView:subview];
if (foundView != nil)
{
return foundView;
}
}
}
return nil;
}
The essential part is finding the UITextField. (I did a similar thing to allow me to custom style the cancel button.) I vaguely remember that disabling translucent was really needed; easy to try.
That should be it. Let me know if this works for you.
I only have Obj-C code, but this is easy to convert.
I finally ignored previous answers from all the posts about this subject and did my own Debug View Hierarchy. I spotted a ImageView that serves as the background which I guess is now called "_UISearchBarSearchFieldBackgroundView". This helped me find a single function that fixes the problem at least for iOS 9+.
searchBar.setSearchFieldBackgroundImage(UIImage(), for: .normal)
One thing to note is that this isn't the only way to fix this problem. However, I used it because it requires no looping and because the image is empty the additional view is never added giving the same end result as other methods.
One thing to note is that this may only work for iOS 9+. So, your milage may vary. I tested with iOS 10 with a Deployment Target of 9.3.
I am building an iOS Today widget, and while testing for iOS 10, I see a "Show More" / "Show Less" button on the top right of the widget header. How can I remove this button? I am using Objective-C.
In iOS 10, as far as I know, the show more option is new and we cannot remove it, but we can modify it as needed.
The following code will allow you to automatically size the Today widget. Just change the table or collection view or whatever you used in your project.
static CGFloat padding = 25.0;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
// This will remove extra separators from tableview
self.articleTableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
// Add the iOS 10 Show More ability
[self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeExpanded];
}
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
if (activeDisplayMode == NCWidgetDisplayModeCompact){
// Changed to compact mode
self.preferredContentSize = maxSize;
}
else{
// Changed to expanded mode
self.preferredContentSize = CGSizeMake(self.articleTableView.contentSize.width, self.articleTableView.contentSize.height + padding);
}
}
In viewDidLoad you can set the largest available display mode.
[self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeCompact];
This will remove the Show More/Less button, but it may not be what you want. The maximum allowed size for the compact view is fairly small.
You can implement:
-(void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize
to update your preferredContentSize. The maxSize parameter will be the maximum allowed size for the activeDisplayMode.
-[NCWidgetProviding widgetActiveDisplayModeDidChange:withMaximumSize:]
Is probably what you're looking for, I would reference this
Sadly you cannot hide it and should conform to the
widgetActiveDisplayModeDidChange:withMaximumSize:
widgets that doesn't show this control were not build for iOS10
I know the original post mentions using objective-c
but in the event anyone needs the swift answer, here it is
override func viewDidLoad()
{
super.viewDidLoad()
self.extensionContext?.widgetLargestAvailableDisplayMode = .compact
}
When set to compact, the app will only support compact mode i.e. show less/show show buttons/functionality will be gone.
here's some documentation for more info
Placing this line of code inside the widgetActiveDisplayModeDidChange delegate method solved my problem.
[self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeExpanded];
If you wanna hide the show more/ show less option replace NCWidgetDisplayModeExpanded with NCWidgetDisplayModeCompact.
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode
withMaximumSize:(CGSize)maxSize {
[self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeExpanded];
}
Recently at work, I've been tasked with putting together an app that creates all of its views programmatically rather than via a storyboard. For the most part this worked well, aside from more back-and-forth between running the app and tweaking the views. As a fun side project I wanted to build a "debug" kit to assist those who're in the same boat.
What has really helped me with alignment/styling is a 3rd party library for css-like styling (http://classy.as), which allows me to see the boundaries of each layer/view after adding the following snippet to the stylesheet:
^UIView {
layer: #{
borderWidth: 1;
}
}
I'd want something similar, but with a label attached to the top-left corner naming the component's class name. Effectively, what I'd want is an auto-generated and auto-attached label on each UIView, preferably one that can be toggled via a variable (i.e. debugLabel). I tried to achieve that via the following code:
private var debugLabelsEnabled: Bool = false
extension UIView {
#IBInspectable var debugLabel: Bool! {
get {
return objc_getAssociatedObject(self, &debugLabelsEnabled) as? Bool
}
set(value) {
objc_setAssociatedObject(self, &debugLabelsEnabled, value, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN))
let label = UILabel(frame: CGRect(origin: self.frame.origin, size: CGSize(width: 100, height: 15)))
label.text = _stdlib_getDemangledTypeName(self)
label.font = UIFont(name: "Courier", size: 9)
self.addSubview(label)
}
}
}
I then tried to test the above snippet via Classy, by setting debugLabel on ^UIView as well. That had no effect. However, individually setting myView.debugLabel = true does work. Unfortunately, I can't expect the developer to manually set this property on each view. I want something along the lines of Classy's logic, where I could toggle that label for all of the elements in view (and those that may appear later).
What I'm basically trying to do is modify the initialization logic for all UIViews to do something extra without having to subclass (because I want all existing elements inheriting from UIView to automatically execute this logic). Could someone help me out? Is there a sane way to achieve that? One way I was thinking of going up to root view controller and then recursively iterating through all of its subviews, but that won't handle views that are not currently shown, like Classy does. I don't mind switching to Obj-C for this if needed, I would, however, prefer to keep the rest of my logic in Swift.
As far as I know there are only 2 useful init of UIView so you can easily achieve what you want by using convenience init.
class FancyView : UIView {
var foo = 0
}
extension UIView {
convenience init(frame : CGRect?, addDebugThings: Bool) {
if frame != nil {
self.init(frame: frame!)
} else {
self.init()
}
if addDebugThings {
addLabel()
}
}
private func addLabel() {
println("add label")
}
}
let view = UIView(frame: nil, addDebugThings: true)
let fancy = FancyView(frame: CGRectMake(0.0, 35.0, 350.0, 350.0), addDebugThings: false)
Edit:
As of you pointed me that it wont work with UIView subclasses the other method I can suggest is method swizzling, but that'd require to migrate to Obj-C, unfortunately.
This is an example of how one can easily do it.
UIView+Border.h:
#import <Foundation/Foundation.h>
#interface UIView(Border)
#end
UIView+Border.m:
#import "UIView+Border.h"
#import <QuartzCore/QuartzCore.h>
#import <objc/runtime.h>
#implementation UIView(Border)
- (id)swizzled_initWithFrame:(CGRect)frame
{
// You might think it is an endless recursion but it is not.
id result = [self swizzled_initWithFrame:frame];
// Safe guard: do we have an UIView (or something that has a layer)?
if ([result respondsToSelector:#selector(layer)]) {
// Get layer for this view.
CALayer *layer = [result layer];
// Set border on layer.
layer.borderWidth = 2;
layer.borderColor = [[UIColor redColor] CGColor];
}
// Return the modified view.
return result;
}
- (id)swizzled_initWithCoder:(NSCoder *)aDecoder
{
// You might think it is an endless recursion but it is not.
id result = [self swizzled_initWithCoder:aDecoder];
// Safe guard: do we have an UIView (or something that has a layer)?
if ([result respondsToSelector:#selector(layer)]) {
// Get layer for this view.
CALayer *layer = [result layer];
// Set border on layer.
layer.borderWidth = 2;
layer.borderColor = [[UIColor blueColor] CGColor];
}
// Return the modified view.
return result;
}
+ (void)load
{
// The "+ load" method is called once, very early in the application life-cycle.
// It's called even before the "main" function is called. Beware: there's no
// autorelease pool at this point, so avoid Objective-C calls.
Method original, swizzle;
// Get the "- (id)initWithFrame:" method.
original = class_getInstanceMethod(self, #selector(initWithFrame:));
// Get the "- (id)swizzled_initWithFrame:" method.
swizzle = class_getInstanceMethod(self, #selector(swizzled_initWithFrame:));
// Swap their implementations.
method_exchangeImplementations(original, swizzle);
// Get the "- (id)initWithCoder:" method.
original = class_getInstanceMethod(self, #selector(initWithCoder:));
// Get the "- (id)swizzled_initWithCoder:" method.
swizzle = class_getInstanceMethod(self, #selector(swizzled_initWithCoder:));
// Swap their implementations.
method_exchangeImplementations(original, swizzle);
}
#end
So I just installed Xcode 6GM and fiddled with my iOS7 app on simulator running iOS8.
I have a UITableView that's in editing mode and there's now a circle on the left side of the cell which doesn't appear when running on iOS7.
I glanced at the documentation for iOS8, but I don't see any new constants and I'm using UITableViewCellEditingStyleNone and UITableViewCellSelectionStyleNone.
That circle disappears when tableView.editing = NO, also allowsMultipleSelectionDuringEditing = YES.
If anyone can tell me what's going on that'd be great :)
EDIT: compiling from XCode6GM onto my iPhone running iOS7.1 gives me the circle too. I suspect a bug with XCode6GM?
Here is a screenshot with the circles:
I just had this annoying issue while migrating my app to iOS8.
Here is the workaround I found ... add something like this in your UITableViewCell subclass:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
for( UIView* subview in self.subviews )
if( [NSStringFromClass(subview.class) isEqualToString:#"UITableViewCellEditControl"] )
subview.hidden = YES;
}
I hope this will be documented / fixed soon ...
I think I have a better solution, add this code to your custom uitableviewcell:
- (void)addSubview:(UIView *)view {
[super addSubview:view];
if( [NSStringFromClass(view.class) isEqualToString:#"UITableViewCellEditControl"] ) {
view.hidden = YES
}
}
Here's a Swift solution combining the two answers:
override func addSubview(view: UIView) {
super.addSubview(view)
if view.isKindOfClass(NSClassFromString("UITableViewCellEditControl")!) {
view.hidden = true
}
}
Here is the Swift3 version:
override func addSubview(_ view: UIView) {
super.addSubview(view)
if view.classAsString() == "UITableViewCellEditControl" {
view.isHidden = true
}
}