I am trying to add two UIScrollView's into one UIView, Both the scrollView's show up properly, the problem that I am having is how to determine which scrollView is scrolled because based on that I have to populate the images. This is what I am doing:
I have a ViewController with UIScrollViewDelegate.
In the loadView method of my ViewConroller, I do the following:
CGRect scrollViewFrame1;
CGPoint scrollViewPoint1;
scrollViewPoint1.x = 0;
scrollViewPoint1.y = 57;
CGSize scrollViewSize1;
scrollViewSize1.width = 320;
scrollViewSize1.height = 154;
scrollViewFrame1.size = scrollViewSize1;
scrollViewFrame1.origin = scrollViewPoint1;
CGRect scrollViewFrame2;
CGPoint scrollViewPoint2;
scrollViewPoint2.x = 0;
scrollViewPoint2.y = 258;
CGSize scrollViewSize2;
scrollViewSize2.width = 320;
scrollViewSize2.height = 154;
scrollViewFrame2.size = scrollViewSize2;
scrollViewFrame2.origin = scrollViewPoint2;
scrollView1 = [[UIScrollView alloc] initWithFrame:scrollViewFrame1];
scrollView2 = [[UIScrollView alloc] initWithFrame:scrollViewFrame2];
And then:
scrollView1.delegate = self;
scrollView2.delegate = self;
And then:
[self.view addSubView:scrollView1];
[self.view addSubView:scrollView2];
I have one scrollViewDidScroll: method, how do I determine which scrollView this method got invoked by, because based on that i need to populate different images for my scrollView.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//Code to populate the next or previous images for scrollView
// If it was one i am able to show the images
}
Thanks for the help.
Delegate methods send with it the object that sent the message (the UIScrollView in this case). So, all you have to do is check that against your instance variables of scrollView1 and scrollView2.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == scrollView1) {
//do stuff with scrollView1
} else if (scrollView == scrollView2) {
//do stuff with scrollView2
}
}
FYI
You can also set a tag for both of the scrollView to differentiate
scrollView1.tag=10;
scrollView2.tag=11;
[self.view addSubView:scrollView1];
[self.view addSubView:scrollView2];
In your delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView1.tag==10) {
//do stuff with scrollView1
} else if (scrollView2.tag==11) {
//do stuff with scrollView2
}
}
Related
I used UITableviewcellEditingstyleDelete to show button for user click on it to show delete button (it's the same way you can see on email app, when user click edit and then click on button to show the delete button). It's work fine in ios6 but when I build my app on device which have ios 7, the delete button is disappear, but when you tap in the delete button's area it's also can delete. The prolem is user cannot see the delete button (The button which have red color provide by OS).
My code is:
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Detemine if it's in editing mode
if (self.editing)
{
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleNone;
}
please help me to find the solution, I'm not know much with iOS7 enviroment.Thanks!
Thanks all for your advice, and this solution can solved it, I make it into custom cell
UIImageView* backgroundImage;
//Variable for animation delete button
// Keep the ContactView in normal state
BOOL bFirstEditingCell;
BOOL bShownRedMinus;
BOOL bShownDeleteButton;
CGRect frameOfContactViewInNormal;
CGPoint centerOfCellInNormal;
UITableViewCellAccessoryType accessoryTypeInNormal;
//
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super initWithCoder:decoder])
{
super.backgroundColor = [UIColor clearColor];
self.selectionStyle = UITableViewCellSelectionStyleNone;
backgroundImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"row_gradient" ]];
self.backgroundView = backgroundImage;
bShownDeleteButton = false;
bShownRedMinus = false;
bFirstEditingCell = true;
accessoryTypeInNormal = UITableViewCellAccessoryNone;
}
return self;
}
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
if (!isOS7()) {
return;
}
for (UIView *subview in self.subviews) {
//Keep normal value of contact view and cell
if (bFirstEditingCell ) {
frameOfContactViewInNormal = self->contactView.frame;
frameOfContactViewInNormal.size.width = kContactViewWidth;
centerOfCellInNormal = subview.center;
bFirstEditingCell = false;
}
if (state == UITableViewCellStateDefaultMask) {
self.backgroundView = backgroundImage;
subview.center = centerOfCellInNormal;
//Set for position of speed dial image
CGRect f = frameOfContactViewInNormal;
if (bShownRedMinus) {
f.size.width -= kRedMinusButtonWidth;
}
self->contactView.frame = f;
bShownDeleteButton = false;
self.accessoryType = accessoryTypeInNormal;
}
else if (state == UITableViewCellStateShowingDeleteConfirmationMask)
{
float sectionIndexWidth = 0.0;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
sectionIndexWidth = 30.0;
}
else {
sectionIndexWidth = 15.0;
}
CGPoint center = centerOfCellInNormal;
subview.center = CGPointMake(center.x - sectionIndexWidth, center.y);
self.backgroundView = nil;
//Set width of contact name
UIView* view = [subview.subviews objectAtIndex: 0];
CGRect f = view.frame;
f.origin.x = (kDeleteButtonWidth + sectionIndexWidth);
view.frame = f;
f = frameOfContactViewInNormal;
f.size.width = self.frame.size.width - (kDeleteButtonWidth + sectionIndexWidth);
self->contactView.frame = f;
bShownDeleteButton = true;
bShownRedMinus = false;
accessoryTypeInNormal = self.accessoryType;
self.accessoryType = UITableViewCellAccessoryNone;
}
else if (state == UITableViewCellStateShowingEditControlMask) {
CGRect f = frameOfContactViewInNormal;
f.size.width -= 5;
self->contactView.frame = f;
bShownRedMinus = true;
}
else if (state == 3) { //State for clicking red minus button
CGRect f = frameOfContactViewInNormal;
f.size.width += kRedMinusButtonWidth;
self->contactView.frame = f;
self.backgroundView = nil;
}
}
}
It looks like one of the Bug from iOS 7. For some reason the backgroundView is moved by iOS over the delete button. You can work-around this by sub classing your backgroundView and implementing the setFrame function of your derived view like this :
UITableViewCell delete button gets covered up.
It may also happen when the accesoryView is specified and the editingAccessoryView is nil. Detailed explanation of this issue and solution is mentioned here :
UITableViewCell content overlaps delete button when in editing mode in iOS7.
You do not need to check if the tableView is being edited...just implement:
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleDelete;
}
Best way to remove this problem is that add an image in cell and set it in Backside.
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bgImg.png"]];
imageView.frame = CGRectMake(0, 0, 320, yourCustomCell.frame.size.height);
[yourCustomCell addSubview:imageView];
[yourCustomCell sendSubviewToBack:imageView];
If your text would overlap the delete button then implement Autolayout. It'll manage it in better way.
One more case can be generate that is cellSelectionStyle would highlight with default color. You can set highlight color as follows
yourCustomCell.selectionStyle = UITableViewCellSelectionStyleNone;
Set your table cell's selection style to UITableViewCellSelectionStyleNone. This will remove the blue background highlighting or other. Then, to make the text label or contentview highlighting work the way you want, use this method in yourCustomCell.m class.
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
if (highlighted)
self.contentView.backgroundColor = [UIColor greenColor];
else
self.contentView.backgroundColor = [UIColor clearColor];
}
I hope it will help you to understand.
Simpler Workaround
Assuming an iOS7 only app, with a technique similar to that linked in the post by Vin above, I believe the approach here: https://stackoverflow.com/a/19416870/535054 is going to be cleaner.
In this approach, you don't need to subclass your backgroundView, which could be different for different cells.
Place the code in the answer I've linked to above, in the root of your custom table cell hierarchy, and all of your table cells (that inherit from it), get the fix whenever they use the backgroundView or selectedBackgroundView properties.
Copy the solution from this gist: https://gist.github.com/idStar/7018104
An easy way to solve this problem is to make the delete confirmation button view as a front view. It can be done by implementing the delegate method layoutSubviews in customtableviewcell.m file.
In my case I solved it by just adding the following code into customtableviewcell.m file. It may be little different according to how the views are placed in your cell. But surely it will give you an idea about how to solve the problem.
- (void)layoutSubviews // called when a interface orientation occur ie. in our case when the '-' button clicks
{
[super layoutSubviews];
for (UIView *subview in self.subviews) { // Loop through all the main views of cell
// Check the view is DeleteConfirmationView or not, if yes bring it into the front
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationView"]) {
[self bringSubviewToFront:subview];// code for bring the view into the front of all subviews
}
}
}
How would I go about disabling a UIButton if the UIScrollView has scrolled more than a certain amount?
this is what I've been trying. Perhaps it's the wrong scrollViewDidScroll: delegate method.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_scrollView.contentOffset.y >= 100) {
mapLaunchButton.enabled = NO;
}
}
thanks for any help
Simple! You'll need to create a variable to store the starting position of the scroll view though. It should be a CGPoint. Set it to the scroll view's content offset in scrollViewWillBeginDragging: (where the scroll view starts moving) and then do comparison in scrollViewDidScroll similarly to how you were doing it before.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
startingPoint = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y >= startingPoint.y + 100.0f) {
mapLaunchButton.enabled = NO;
}
}
Keep in mind you may need to modify the values I've provided slightly depending on the starting position of the scroll view, and the direction in which you'd like to monitor the changes.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_scrollView.contentOffset.y >= 100) {
mapLaunchButton.enabled = NO;
}
else {
mapLaunchButton.enabled = YES;
}
}
The code is OK, but you have to add the delegate for the scrollView
- (void)viewDidLoad {
[super viewDidLoad];
// do whatever
...
// Add the delegate for the scrollview
[_scrollView setDelegate:self];
}
A UIScrollView contains several UIView objects; how can I tell if a point on the screen not generated by touches is within a specific subview of the scrollview? so far atempts to determine if the point is in the subview always return the first subview in the subviews array of the parent scrollview, ie the coordinates are relative to the scrollview, not the window.
Here's what I tried (edited)
-(UIView *)viewVisibleInScrollView
{
CGPoint point = CGPointMake(512, 384);
for (UIView *myView in theScrollView.subviews)
{
if(CGRectContainsPoint([myView frame], point))
{
NSLog(#"In View");
return myView;
}
}
return nil;
}
It looks like you're point is relative to the window, and you want it relative to the current view. convertPoint:fromView: should help with this.
There are probably errors here, but it should look something like this:
-(UIView *)viewVisibleInScrollView
{
CGPoint point = CGPointMake(512, 384);
CGPoint relativePoint = [theScrollView convertPoint:point fromView:nil]; // Using nil converts from the window coordinates.
for (UIView *myView in theScrollView.subviews)
{
if(CGRectContainsPoint([myView frame], relativePoint))
{
NSLog(#"In View");
return myView;
}
}
return nil;
}
Here's what I do:
#implementation UIScrollView (FOO)
- (id)foo_subviewAtPoint:(CGPoint)point
{
point.x += self.contentOffset.x;
for (UIView *subview in self.subviews) {
if (CGRectContainsPoint(subview.frame, point)) {
return subview;
}
}
return nil;
}
#end
And here's how I use it:
CGPoint center = [scrollView convertPoint:scrollView.center fromView:scrollView.superview];
UIView *view = [scrollView foo_subviewAtPoint:center];
Some things to note:
The point passed to foo_subviewAtPoint: is expressed in the coordinate system of the scroll view. This is why in the code above I have to convert center to that coordinate system from that of its superview.
I'm using iOS 6.1+ with layout constraints. I've never tested this code with anything else, so YMMV.
I'm using a UITextView to roughly replicate the SMS text box above the keyboard. I'm using a UITextView instead of a field so that it can expand with multiple lines.
The problem is that, in my UITextView, the correction suggestions pop up below the text, causing them to be partially obscured by the keyboard.
In the SMS app, the suggestions pop up above the text. The placement does not appear to be a property of UITextView, or UITextInputTraits.
Any idea how to replicate this behavior? Thanks!
The problem is that the Keyboard is implemented as a separate UIWindow, rather than as a view within the main UIWindow, so layout with it is tricky. Here are some pointers in the right direction:
Hunt through the application's -windows property to find the private UITextEffectsWindow window and figure out its frame. This is the keyboard
Hunt through the TextView's subviews to find the private UIAutocorrectInlinePrompt view. This is the autocorrect bubble.
Move that subview into a separate wrapper view (added to the TextView) and then move that wrapper view so it's above the above-mentioned keyboard window.
You'll notice two mentions of "private" above. That carries all the relevant caveats. I have no idea why Apple has allowed the problem to persist when even their apps have had to work around it.
By doing the search for the UIAutocorrectInlinePrompt in an overridden or swizzled layoutSubViews it is possible to alter the layout of the correction so that it appears above. You can do this without calling any private APIs by looking for the subs views of particular classes positioned in a way you'd expect them. This example works out which view is which, checks to see that the correction is not already above the text and moves the correction above, and draws it on the window so that it is not bounded by the UITextView itself. Obviously if apple change the underlying implementation then this will fail to move correction. Add this to your overriden or swizzled layoutSubViews implementation.
- (void) moveSpellingCorrection {
for (UIView *view in self.subviews)
{
if ([[[view class] description] isEqualToString:#"UIAutocorrectInlinePrompt"])
{
UIView *correctionShadowView = nil; // [view correctionShadowView];
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] isEqualToString:#"UIAutocorrectShadowView"])
{
correctionShadowView = subview;
break;
}
}
if (correctionShadowView)
{
UIView *typedTextView = nil; //[view typedTextView];
UIView *correctionView = nil; //[view correctionView];
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] isEqualToString:#"UIAutocorrectTextView"])
{
if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
{
correctionView = subview;
}
else
{
typedTextView = subview;
}
}
}
if (correctionView && typedTextView)
{
CGRect textRect = [typedTextView frame];
CGRect correctionRect = [correctionView frame];
if (textRect.origin.y < correctionRect.origin.y)
{
CGAffineTransform moveUp = CGAffineTransformMakeTranslation(0,-50.0);
[correctionView setTransform: moveUp];
[correctionShadowView setTransform: moveUp];
CGRect windowPos = [self convertRect: view.frame toView: nil ];
[view removeFromSuperview];
[self.window addSubview: view];
view.frame = windowPos;
}
}
}
}
}
}
Actually doing
textview.scrollEnabled = NO;
will set the bubble on top of the text... the caveat is that you lose scrolling, in my case it wasn't a problem due to havinng a textfield only for input purposes with character limit
Actually, the keyboard simply uses the result of -[UITextInput textInputView] to determine where to put the correction view (and to ask if your view supports correction). So all you need to do is this:
- (UIView *)textInputView {
for (UIWindow *window in [UIApplication sharedApplication].windows) {
if ([window isKindOfClass:NSClassFromString(#"UITextEffectsWindow")] &&
window != self.window) {
return window;
}
}
// Fallback just in case the UITextEffectsWindow has not yet been created.
return self;
}
Note that you'll likely also need to update -[UITextInput firstRectForRange:] to use the coordinate system of the window / device, so you can do this:
- (CGRect)firstRectForRange:(CoreTextTokenTextRange *)range {
CGRect firstRect = [self firstRectForRangeInternal:range];
return [self convertRect:firstRect toView:[self textInputView]];
}
(In the above context, self is a class that implements UITextInput).
If the bottom of your UITextView clears the keyboard, you should be able to just resize your UITextView to be tall enough to see the corrections. The corrections themselves don't display outside of the UITextView's frame.
If you want to mimic what you are getting in the SMS app (corrections above), you'll probably have to roll your own.
Putting the below method, adjustAutocorrectPromptView in layoutSubviews worked for me in portrait and landscape. I have a category that provides the bottom and top methods on view but you get the idea.
NSArray * subviewsWithDescription(UIView *view, NSString *description)
{
return [view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"class.description == '%#'", description]]];
}
- (void) adjustAutocorrectPromptView;
{
UIView *autocorrectPromptView = [subviewsWithDescription(self, #"UIAutocorrectInlinePrompt") lastObject];
if (! autocorrectPromptView)
{
return;
}
UIView *correctionShadowView = [subviewsWithDescription(autocorrectPromptView, #"UIAutocorrectShadowView") lastObject];
if (! correctionShadowView)
{
return;
}
UIView *typedTextView = nil; //[view typedTextView];
UIView *correctionView = nil; //[view correctionView];
for (UIView *subview in subviewsWithDescription(autocorrectPromptView, #"UIAutocorrectTextView"))
{
if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
{
correctionView = subview;
}
else
{
typedTextView = subview;
}
}
if (correctionView && typedTextView)
{
if (typedTextView.top < correctionView.top)
{
correctionView.bottom = typedTextView.top;
correctionShadowView.center = correctionView.center;
}
}
}
Make sure your view controller delegate is listening to the notification when the keyboard pops up so that you resize your UITextView so that the keyboard doesn't obscure the UITextView. Then your correction won't be obscured by the keyboard. See:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/12641-uitextview-scroll-while-editing.html
Here is a copy of the code from that page in case the original link is broken:
// the amount of vertical shift upwards keep the Notes text view visible as the keyboard appears
#define kOFFSET_FOR_KEYBOARD 140.0
// the duration of the animation for the view shift
#define kVerticalOffsetAnimationDuration 0.50
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
}
- (IBAction)backgroundClick:(id)sender
{
[latitudeField resignFirstResponder];
[longitudeField resignFirstResponder];
[notesField resignFirstResponder];
if (viewShifted)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:kVerticalOffsetAnimationDuration];
CGRect rect = self.view.frame;
rect.origin.y += kOFFSET_FOR_KEYBOARD;
rect.size.height -= kOFFSET_FOR_KEYBOARD;
self.view.frame = rect;
[UIView commitAnimations];
viewShifted = FALSE;
}
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
if (!viewShifted) { // don't shift if it's already shifted
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:kVerticalOffsetAnimationDuration];
CGRect rect = self.view.frame;
rect.origin.y -= kOFFSET_FOR_KEYBOARD;
rect.size.height += kOFFSET_FOR_KEYBOARD;
self.view.frame = rect;
[UIView commitAnimations];
viewShifted = TRUE;
}
return YES;
}
Is there a way to control the position of the auto correct view that pops up while typing in a UITextField?
By default it appears to always appear below the text field. However in Apple's apps like SMS it sometimes appears above the text field.
For text fields aligned right above the keyboard the auto correct is blocked by the keyboard and not usable.
The position of the auto correction prompt is determined by the firstRect... method of the UITextInput protocol. Unfortunately UITextField uses a private class as delegate for this protocol so you cannot subclass and override it.
You COULD however implement your own UITextField replacement by drawing the contents yourself (like with CoreText), implement your own selection and loupe handling and then you would be able to affect the position of the auto correction prompt. Though it's designed to always be below the edited text, so you would have to essentially lie on the firstRect ... method.
Long story short: it's too much hassle.
One answer it worked for me is to use setInputAccessoryView method of the textview/textview.
I have a toolbar which contains the textView.
If I set the toolbar as the inputaccessoryview of the textfield, and set to NO clipsToBound property of the toolbar, I don't know exactly why, but the balloon appears above the keyboard
Here is a solution using private APIs as there are no ways to do this using public APIs.
Hunt through the application's -windows property to find the private UITextEffectsWindow window and figure out its frame. This is the keyboard
Hunt through the TextView's subviews to find the private UIAutocorrectInlinePrompt view. This is the autocorrect bubble.
Move that subview into a separate wrapper view (added to the TextView) and then move that wrapper view so it's above the above-mentioned keyboard window.
Using swizzled layoutSubViews,
- (void) moveSpellingCorrection {
for (UIView *view in self.subviews)
{
if ([[[view class] description] isEqualToString:#"UIAutocorrectInlinePrompt"])
{
UIView *correctionShadowView = nil; // [view correctionShadowView];
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] isEqualToString:#"UIAutocorrectShadowView"])
{
correctionShadowView = subview;
break;
}
}
if (correctionShadowView)
{
UIView *typedTextView = nil; //[view typedTextView];
UIView *correctionView = nil; //[view correctionView];
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] isEqualToString:#"UIAutocorrectTextView"])
{
if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
{
correctionView = subview;
}
else
{
typedTextView = subview;
}
}
}
if (correctionView && typedTextView)
{
CGRect textRect = [typedTextView frame];
CGRect correctionRect = [correctionView frame];
if (textRect.origin.y < correctionRect.origin.y)
{
CGAffineTransform moveUp = CGAffineTransformMakeTranslation(0,-50.0);
[correctionView setTransform: moveUp];
[correctionShadowView setTransform: moveUp];
CGRect windowPos = [self convertRect: view.frame toView: nil ];
[view removeFromSuperview];
[self.window addSubview: view];
view.frame = windowPos;
}
}
}
}
}
}
For more details check.
In my case, the AutoCorrect view's position is shifted because of the font's leading (line gap).
So, I tried to move it up the leading px by using firstRect function as the code below.
class CustomTextView: UITextView {
override var font: UIFont? {
didSet {
if let font = font {
leadingFont = font.leading
} else {
leadingFont = 0
}
}
}
var leadingFont: CGFloat = 0
override func firstRect(for range: UITextRange) -> CGRect {
var newRect = super.firstRect(for: range)
newRect.origin = CGPoint(x: newRect.origin.x, y: newRect.origin.y - leadingFont)
print("newRect: \(newRect)")
return newRect
}
}
Although it's UITextView but you can do the same thing with UITextField