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
}
}
Related
Long-pressing images or links in a WKWebView on iOS 11 and 12 initiates a Drag & Drop session (the user can drag the image or the link). How can I disable that?
I did find a solution that involves method swizzling but it's also possible to disable drag and drop in a WKWebView without any swizzling.
Note: See special notes for iOS 12.2+ below
WKContentView — a private subview of WKWebView's WKScrollView — has an interactions property, just like any other UIView in iOS 11+. That interactions property contains both a UIDragInteraction and a UIDropInteraction. Simply setting enabled to false on the UIDragInteraction does the trick.
We don't want to access any private APIs and make the code as solid as possible.
Assuming your WKWebView is called webView:
if (#available(iOS 11.0, *)) {
// Step 1: Find the WKScrollView - it's a subclass of UIScrollView
UIView *webScrollView = nil;
for (UIView *subview in webView.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
webScrollView = subview;
break;
}
}
if (webScrollView) {
// Step 2: Find the WKContentView
UIView *contentView = nil;
// We don't want to trigger any private API usage warnings, so instead of checking
// for the subview's type, we simply look for the one that has two "interactions" (drag and drop)
for (UIView *subview in webScrollView.subviews) {
if ([subview.interactions count] > 1) {
contentView = subview;
break;
}
}
if (contentView) {
// Step 3: Find and disable the drag interaction
for (id<UIInteraction> interaction in contentView.interactions) {
if ([interaction isKindOfClass:[UIDragInteraction class]]) {
((UIDragInteraction *) interaction).enabled = NO;
break;
}
}
}
}
}
That's it!
Special note for iOS 12.2+
The above code still works on iOS 12.2, but it is important when to call it. On iOS 12.1 and below you could call this code right after creating the WKWebView. That's not possible anymore. The WKContentView's interactions array is empty when it's first created. It is only populated after the WKWebView is added to a view hierarchy that is attached to a UIWindow - simply adding it to a superview that is not yet part of the visible view hierarchy is not enough. In a view controller viewDidAppear would most likely be a safe place to call it from.
How did I find this out?
I searched through the WebKit source and found this: https://github.com/WebKit/webkit/blob/65619d485251a3ffd87b48ab29b342956f3dcdc7/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm#L4953
That's the method that creates and adds the UIDragInteraction
It turns out that this method (setupDataInteractionDelegates) actually exists on WKContentView
So I set a symbolic breakpoint on -[WKContentView setupDataInteractionDelegates]
The breakpoint was hit
I used lldb to print the backtrace using the bt command
This was the output:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 50.1
* frame #0: 0x00000001115b726c WebKit`-[WKContentView(WKInteraction) setupDataInteractionDelegates]
frame #1: 0x00000001115a8852 WebKit`-[WKContentView(WKInteraction) setupInteraction] + 1026
frame #2: 0x00000001115a5155 WebKit`-[WKContentView didMoveToWindow] + 79
So clearly the creation and addition of the UIDragInteraction is triggered by the view moving to (being added to) a window.
This works great!
Thanks #basha for the swift version.
I did the same but with some compactMaps to reduce the depth of the if-statements and guards to get rid of the force unwraps.
private func disableDragAndDropInteraction() {
var webScrollView: UIView? = nil
var contentView: UIView? = nil
if #available(iOS 11.0, *) {
guard let noDragWebView = webView else { return }
webScrollView = noDragWebView.subviews.compactMap { $0 as? UIScrollView }.first
contentView = webScrollView?.subviews.first(where: { $0.interactions.count > 1 })
guard let dragInteraction = (contentView?.interactions.compactMap { $0 as? UIDragInteraction }.first) else { return }
contentView?.removeInteraction(dragInteraction)
}
}
Based on Johannes FahrenKrug's Post, with some changes.
private func disableDragAndDropInteraction() {
var webScrollView: UIView? = nil
var contentView: UIView? = nil
if #available(iOS 11.0, *) {
if (webView != nil) {
for subView in webView!.subviews {
if (subView is UIScrollView) {
webScrollView = subView
break
}
}
if (webScrollView != nil) {
for subView in webScrollView!.subviews {
if subView.interactions.count > 1 {
contentView = subView
break
}
}
if (contentView != nil) {
for interaction in contentView!.interactions {
if interaction is UIDragInteraction {
contentView!.removeInteraction(interaction)
}
}
}
}
} else {
// Fallback on earlier versions
}
}
}
Use the CSS property webkit-touch-callout on the img and a elements. Also set their draggable attribute to false by changing the HTML or injecting it using -[WKWebView evaluateJavaScript(...)].
img, a {
-webkit-touch-callout: none;
}
<img draggable="false">
Avoids all the fragile subview diving and manipulation.
(Recap of my answer to the similar Disable link drag on WKWebView .)
My approach was to subclass WKWebView and search the view hierarchy for subviews that contain drag or drop interactions, recursively. Once a view is found, which most likely will be a WKContentView, the interactions are removed. The benefit of this method is to not rely on any subview order / view hierarchy, which could change between OS releases.
override func didMoveToWindow() {
super.didMoveToWindow()
disableDragAndDrop()
}
func disableDragAndDrop() {
func findInteractionView(in subviews: [UIView]) -> UIView? {
for subview in subviews {
for interaction in subview.interactions {
if interaction is UIDragInteraction {
return subview
}
}
return findInteractionView(in: subview.subviews)
}
return nil
}
if let interactionView = findInteractionView(in: subviews) {
for interaction in interactionView.interactions {
if interaction is UIDragInteraction || interaction is UIDropInteraction {
interactionView.removeInteraction(interaction)
}
}
}
}
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 switched my project over to new beta versions of iOS 10 and XCode 8. In all three areas of my app where I use:
imageView.layer.cornerRadius = imageView.frame.size.width/2
imageView.clipsToBounds = true
The associated images are not displaying at all. I have attempted cleaning the project as well as the build folder, restarting the device, trying on various simulators, re-adding the imageView, programmatically setting the associated UIImage instead of choosing one from the assets.
Removing the clipsToBounds line shows the rectangular image regardless of whether masksToBounds is true or false. How can I make a circular image in XCode8 / iOS10 ?
Edit: The project is Swift 2.x and not yet updated to Swift 3.0 syntax.
I had the same problem and calling layoutIfNeeded before all the rounded corner / clipsToBounds stuff fixed the issue. iOS 10 GM with xcode 8 GM causes views to disappear due to roundedCorners & clipsToBounds
This problem also happened to me.
I moved these code imageView.layer.cornerRadius = imageView.frame.size.width/2 from - (void)awakeFromNib to - (void)drawRect:(CGRect)rect and this problem went away.
My imageView is sized by autolayout. I think that height and width are not decided when awaking from nib on iOS 10.
This sounds like it could be due to a new bug since iOS 10 and Xcode 8 where views are initialised at size 1000x1000 and when trying to set corner radius to half your frame, it is setting it to 500 instead. This is documented further in this question here: Since Xcode 8 and iOS10, views are not sized properly on viewDidLayoutSubviews. My reason for thinking this is the fix to the 1000x1000 issue it to call layout subviews before doing anything that requires sizes for something that has been constructed on the interface builder.
I had the same problem. Not showing on the phone, but perfect on Storyboard and UI debugger. It was also working when I was putting the project "Opens with Xcode 7" instead of 8 but wasn't a satisfying solution. So I digged and found out the problem. The trouble was with a class looking like this :
#IBDesignable public class MyButton: UIButton {
#IBInspectable var styleName: String = "" {
didSet {
switch styleName {
case "RoundedLight":
tintColor = UIColor.lightPinkColor()
layer.borderColor = UIColor.whiteColor().CGColor
layer.borderWidth = 1
layer.masksToBounds = true
default:
break
}
}
}
override public func layoutSubviews() {
super.layoutSubviews()
switch styleName {
case "RoundedLight":
layer.cornerRadius = frame.height / 2
default:
break
}
}
}
I changed it to this and it works now :
#IBDesignable public class MyButton: UIButton {
#IBInspectable var styleName: String = "" {
didSet {
layoutIfNeeded()
}
}
override public func layoutSubviews() {
super.layoutSubviews()
switch styleName {
case "RoundedLight":
tintColor = UIColor.lightPinkColor()
layer.borderColor = UIColor.whiteColor().CGColor
layer.borderWidth = 1
layer.cornerRadius = frame.height / 2
layer.masksToBounds = true
default:
break
}
}
}
Note that none of the above worked for me, and I mean :
Calling layoutIfNeeded in the layoutSubviews()
Calling layoutIfNeeded in the awakeFromNib of my button (or my cell containing the button)
Calling layoutIfNeeded in the layoutSubview of my cell
Calling contentView.layoutIfNeeded() in the awakeFromNib of my cell
Calling view.layoutIfNeeded() in my view controller
I had ImageView that is fully circular in shape. Below is code :
//MARK:- Misc functions
func setProfileImage() {
imgProfile.layer.masksToBounds = false
imgProfile.layer.cornerRadius = imgProfile.frame.size.height/2
imgProfile.clipsToBounds = true
imgProfile.contentMode = UIViewContentMode.ScaleToFill
}
This works fine in iOS 9. However, in iOS 10 Xcode 8 the ImageView disappears.
After debugging found that ClipsToBound is culprit.
So placed the code in viewDidLayoutSubviews() resolved the issue.
override func viewDidLayoutSubviews() {
setProfileImage()
}
You can also use self.view.layoutIfNeeded()
It might be a layout issue. Setting clipToBounds to false would show the image even if its size is zero. Can you print the frame of your images?
If you set the cornerRadius and clipToBounds properties in viewDidLoad, try doing it in viewDidLayoutSubviews().
You could also try to call self.view.layoutIfNeeded().
Using obj-c, moving the syntax from viewDidLoad to viewDidLayoutSubviews worked for me.
I just implemented corner rounding like this
#implementation RoundImageView
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
self.layer.masksToBounds = YES;
self.layer.cornerRadius = MIN(self.bounds.size.height, self.bounds.size.width)/2;
[self addObserver:self
forKeyPath:#"bounds"
options:NSKeyValueObservingOptionNew
context:(__bridge void * _Nullable)(self)];
}
return self;
}
-(void)dealloc
{
[self removeObserver:self
forKeyPath:#"bounds"
context:(__bridge void * _Nullable)(self)];
}
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context
{
if(context == (__bridge void * _Nullable)(self) && object == self && [keyPath isEqualToString:#"bounds"])
{
self.layer.cornerRadius = MIN(self.bounds.size.height, self.bounds.size.width)/2;
}
}
#end
so I always have properly rounded corners.
And I hadn't issues on upgrading to iOS10 and XCode8
I think the problem happens because we set the round corner and clip subviews as #Pranoy C said.
I solved the problems by using layoutIfNeeded for the my tableview cell showing a user's profile picture. I want to ensure the image is round corner
Code is as follow:
- (void)awakeFromNib {
[super awakeFromNib];
[self layoutIfNeeded];
[self.inputIndicator_imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.profile_pic_edit_imageView.layer.cornerRadius = self.profile_pic_edit_imageView.frame.size.width/2;
self.profile_pic_edit_imageView.layer.borderWidth = 1.0;
self.profile_pic_edit_imageView.layer.borderColor = GRAY_COLOR_SUPER_LIGHT.CGColor;
self.profile_pic_edit_imageView.clipsToBounds = YES;}
You should call layoutSubviews() on the main view before call imageView.frame.size.width
[self.view layoutSubviews];
imageView.layer.cornerRadius = imageView.frame.size.width/2
imageView.clipsToBounds = true
I need to implement a dismissive keyboard (swiping down to dismiss) like the one in the stock messages app on iOS.
I have this code to get the keyboard view:
func keyboardWillShowWithNotification(notification:NSNotification) {
let keyboardView = accessoryView.superview
}
And I connected the UIPanGestureRecognizer of the tableView to detect when I need to start moving the keyboard down.
func handleTableViewPan(gr:UIPanGestureRecognizer) {
let location = panGestureRecognizer.locationInView(self.view)
let offset = ... //calculated correctly
keyboardView.frame.origin.y = originalKeyboardFrame.origin.y + offset
}
The method worked fine with iOS 8 but with iOS 9 it seems like the keyboard is hold in place a little different so I can't move it.
Maybe someone encountered the same problem and can help me.
Thank you.
In iOS 9 there is a new window for keyboard named UIRemoteKeyboardWindow, so when you are using accessoryView.superview you will get a wrong view.
To get correct view try to find it from window hierarchy directly:
(objective-c code)
-(UIView*)getKeyboardInputView {
if([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
for(UIWindow* window in [[UIApplication sharedApplication] windows])
if([window isKindOfClass:NSClassFromString(#"UIRemoteKeyboardWindow")])
for(UIView* subView in window.subviews)
if([subView isKindOfClass:NSClassFromString(#"UIInputSetHostView")])
for(UIView* subsubView in subView.subviews)
if([subsubView isKindOfClass:NSClassFromString(#"UIInputSetHostView")])
return subsubView;
} else {
return accessoryView.superview;
}
return nil;
}
P.S. Taken from DAKeyboardControl https://github.com/danielamitay/DAKeyboardControl/pull/98
Disclaimer: I've been working too late. But, I'm determined to get through this one tonight.
I have an app where I support different color themes. The dark cell backgrounds have been problematic.
I've been poking around trying to find a formidable way to draw the accessory disclosure icon in uitableviewcells with black backgrounds.
I decided to try overriding setAccessoryType to inherit the functionality for my 50+ views:
-(void) addWhiteDisclosureImage {
UIImageView *disclosureView = (UIImageView*) [self.contentView viewWithTag:kDisclosureReplacementImageTag];
if(!disclosureView) {
[super setAccessoryType:UITableViewCellAccessoryNone];
disclosureView = [[UIImageView alloc] initWithImage:self.whiteDisclosureImage];
disclosureView.tag = kDisclosureReplacementImageTag;
disclosureView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin;
DebugLog(#"%f, %f", self.frame.size.width, self.frame.size.height);
[self.contentView addSubview:disclosureView];
[self.contentView bringSubviewToFront:disclosureView];
[disclosureView release];
}
}
- (void)setAccessoryType:(UITableViewCellAccessoryType)accessoryType {
if(accessoryType == UITableViewCellAccessoryDisclosureIndicator) {
if ([self.viewController isKindOfClass:[ViewControllerBase class]]) {
ViewControllerBase *view = (ViewControllerBase*) self.viewController;
if(view.colorTheme && view.colorTheme.controlBackgroundColor) {
if([ViewColors colorAverage:view.colorTheme.controlBackgroundColor] < 0.2) { //substitute white disclosure indicator
[self addWhiteDisclosureImage];
return;
} else { //not dark enough
[self removeWhiteDisclosureImage];
[super setAccessoryType:accessoryType];
return;
}
} else { //no colorTheme.backgroundColor
[self removeWhiteDisclosureImage];
[super setAccessoryType:accessoryType];
return;
}
} else { //viewController is not type ViewControllerBase
[self removeWhiteDisclosureImage];
[super setAccessoryType:accessoryType];
return;
}
}
UIView *disclosureView = [self.contentView viewWithTag:kDisclosureReplacementImageTag];
if(disclosureView)
[disclosureView removeFromSuperview];
[super setAccessoryType:accessoryType];
}
This override is typically called in cellForRowAtIndexPath.
It seemed like a good option until I drill down and come back. For some cells, the cell frame will be a great deal larger than the first time through. This consistently happens to the same cell in a list of 6 that I've been testing against. There's clearly something unique about this cell: it's frame.size.
Here is the size of the cell that I log for the first tableview load (in some cases every load/reload):
320.000000, 44.000000
This is the difference in what I get for some (not all) of the cells after call to reloadData:
759.000000, 44.000000
Does anyone know why this might happen?
Update: the suspect cell's custom accessory disclosure view almost acts like it's autoresizing flag is set to none. I confirmed this by setting all to none. I say almost because I see it line up where it should be after reloadData. A split second later it moves clear over to the left (where they all end up when I opt for no autoresizing).
Don't mess around with subviews and calculating frames.
Just replace the accessoryView with the new imageView. Let iOS do the work.