Newlines in iOS 7 UITextView breaking Text Kit exclusion zone wrapping - objective-c

I'm working with Text Kit in iOS 7 and I'm finding a lot of oddities around NSTextContainer exclusion zones.
I've got two views: a UITextView and a simple draggable UIView; as the UIView moves, I create a bezier path from the UIView's frame (adjusted to within the UITextView's coordinate space) and I update the UITextView's NSTextContainer's exclusionPaths array - pretty straightforward.
In the first screenshot, you can see that Text Kit nicely wraps text around a rectangular exclusion zone:
However, when the user introduces newlines into the UITextView, TextKit seems to think that the exclusion zone is much bigger vertically - by what appears to be exactly as high as the whitespace created by the newline. The bezier path is exactly the same, so this seems to be a Text Kit issue (unless I'm doing something wrong).
Code:
ViewController.h:
#interface ViewController : UIViewController<UITextViewDelegate>
#property (nonatomic, strong) IBOutlet UITextView *textView;
#property (nonatomic, strong) IBOutlet UIView *dragView;
#end
ViewController.m:
-(void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(move:)];
[panRecognizer setMinimumNumberOfTouches:1];
[panRecognizer setMaximumNumberOfTouches:1];
[self.dragView addGestureRecognizer:panRecognizer];
[self updateExclusionZone];
}
-(void)move:(UIPanGestureRecognizer *)pan
{
[self.view bringSubviewToFront:[pan view]];
if ([pan state] == UIGestureRecognizerStateBegan) {
NSLog(#"pan began");
}
self.dragView.center = [pan locationInView:self.view];
[self updateExclusionZone];
if ([pan state] == UIGestureRecognizerStateEnded) {
NSLog(#"pan ended");
}
}
-(void)updateExclusionZone
{
CGRect dragViewFrame = self.dragView.frame;
CGRect exclusionRect = [self.view convertRect:dragViewFrame toView:self.textView];
UIBezierPath *exclusion = [UIBezierPath bezierPathWithRect:exclusionRect];
self.textView.textContainer.exclusionPaths = #[exclusion];
}
Any thoughts?

I ran into the same issue today.
This bug seem to appear, if you set editable and selectable at the same time. If only one or no is selected, it renders as expected. Both are selected by default.
If you need both options, just set them in code.
_textView.textContainer.exclusionPaths = exclusionPaths;
_textView.attributedText = attrString;
_textView.editable = YES;
_textView.selectable = YES;

Related

How to set fill color property of custom UIView (a circle) from View Controller

I'm guessing I don't know enough about Quartz or CAShapeLayer manipulations, but I wanted to know how to change the fill color to custom UIView that I have.
Here's the implementation for DotView:
#define kCustomBlue [UIColor colorWithRed:181.0/255 green:228.0/255 blue:226.0/255 alpha:1]
#implementation DotView
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
self.backgroundColor = [UIColor clearColor];
self.opaque = YES;
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
if ((self = [super initWithCoder:coder])) {
self.backgroundColor = [UIColor clearColor];
self.opaque = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:rect] CGPath]];
circleLayer.fillColor = kCustomBlue.CGColor;
[self.layer addSublayer:circleLayer];
}
#end
After placing a DotView object ((nonatomic, weak) IBOutlet in my ViewController interface) in my Storyboard, I run the code and everything is fine and dandy. That object in my ViewController interface is called dot1
I want to have a property #property (nonatomic, strong) UIColor* fillingColor; which sets the fill color for the view. How do I implement that correctly?
The idea is this: there is a tap gesture recognizer object attached to the dot1 view, and everytime I tap the dot, the color changes from blue to black (then black to blue).
I'm using XCode 9.3 and have an iPhone 7 running iOS 11.2
Thanks, Anthony
Add the property to the .h:
#interface DotView: UIView
// Add this to everything else you have
#property (nonatomic, strong) UIColor* fillingColor;
#end
Then in the .m, override the setter:
- (void)setFillingColor:(UIColor *)color {
_fillingColor = color;
[self setNeedsDisplay];
}
Then in drawRect:, use your fillingColor property for the fill color:
circleLayer.fillColor = self.fillingColor.CGColor;
Please note that you do not want to be adding layers over and over every time drawRect: is called. You don't even need to use layers for this. Just fill the UIBezierPath.

UIViewController viewDidLayoutSubviews called when transforming a subview

I have the following use case:
a view controller (RotationVC) that contains a bit of logic, among others, rotating one of the subviews (by setting its transform to CGAffineTransformMakeRotation)
this RotationVC overrides the viewDidLayoutSubviews method to set its subviews to be as big as the parent view (self.view)
(creating the view controllers and its views is all done programmatically and it has to stay this way; I tried using autoresizingMask with flexible width and flexible height on the subviews but it didn't work)
another view controller (ParentVC) adds the RotationVC as its child, adds its view as its subview and sets its frame at some point to be square - the idea is that when the frame is set, RotationVC viewDidLayoutSubviews will be called and set the subviews to correct sizes (which actually works fine), and then all works fine
(the RotationVC will be used in other view controllers as well and will have different sized, that's the reason it has been extracted to a 'child' view controller and its size should be flexible)
Well, it doesn't work correctly. The problem is that after each rotation, RotationVC viewDidLayoutSubviews method is called, which sets the rotated views frame. This is wrong, as according to Apple's documentation, using the frame property is wrong when the view's transform is anything else than identity (as is in this case). As a result, the rotated view is 'skewed'. Here is the source code down to the delegate, you should be able to paste it to a single file and run. In case you do, what I'm trying to achieve is for the rotated subview to preserve its shape (in this example it's a rectangle):
#import "AppDelegate.h"
#interface RotationVC : UIViewController
#property (nonatomic, weak) UIView *background;
#property (nonatomic, weak) UIView *foreground;
#property (nonatomic) int degrees;
#end
static const double DURATION = 0.5;
#implementation RotationVC
- (void)viewDidLoad {
[super viewDidLoad];
UIView *tmp = [UIView new];
self.background = tmp;
self.background.layer.borderWidth = 2;
self.background.layer.borderColor = [UIColor redColor].CGColor;
[self.view addSubview:self.background];
tmp = [UIView new];
self.foreground = tmp;
self.foreground.layer.borderWidth = 2;
self.foreground.layer.borderColor = [UIColor blueColor].CGColor;
[self.view addSubview:self.foreground];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.background.frame = self.view.bounds;
CGAffineTransform tmp = self.foreground.transform;
self.foreground.transform = CGAffineTransformIdentity;
self.foreground.frame = self.view.bounds;
self.foreground.transform = tmp;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self performSelector:#selector(rotate) withObject:self];
}
- (void)rotate {
self.degrees = (self.degrees + 15) % 360;
[UIView animateWithDuration:DURATION
delay:0
options:UIViewAnimationOptionCurveLinear|UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.foreground.transform = CGAffineTransformMakeRotation(self.degrees * M_PI/180);
}
completion:^(BOOL finished) {
[self performSelector:_cmd withObject:self afterDelay:0];
}];
}
#end
#interface ParentVC : UIViewController
#property (nonatomic, weak) RotationVC *rotationVC;
#end
#implementation ParentVC
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
RotationVC *tmp = [RotationVC new];
self.rotationVC = tmp;
[self addChildViewController:self.rotationVC];
[self.rotationVC didMoveToParentViewController:self];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.rotationVC.view];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.rotationVC.view.bounds = CGRectMake(0, 0, 100, 100);
self.rotationVC.view.center = self.view.center;
}
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:[ParentVC new]];
navi.navigationBarHidden = YES;
self.window.rootViewController = navi;
[self.window makeKeyAndVisible];
return YES;
}
#end
There is a hack that works - in the viewDidLayoutSubviews method, store the rotated view's transform to a temp variable, set the view's transform to identity, set the frame, and set it to rotation again - this is ugly as hell and I bet there has to be a better way. I was also able to devise a few other workarounds, but each one was uglier than the previous one...
Would anybody have a hint as to what to do in this case?
I was able to solve the issue. Just use the following code:
self.foreground.bounds = self.view.bounds;
self.foreground.center = self.background.center;
instead of setting the frame.
This is explained in https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW7:
Important: If a view’s transform property is not the identity transform, the value of that view’s frame property is undefined and must be ignored. When applying transforms to a view, you must use the view’s bounds and center properties to get the size and position of the view. The frame rectangles of any subviews are still valid because they are relative to the view’s bounds.
I always thought that setting the frame is setting the bounds and the center at the same time, but apparently this is very untrue.

UIGestureRecognizer hooked up to UIVIew

I'm a noob to iOS development (so please bear with me on this), and I'm trying to create a simple touch application with Xcode from scratch. I have spent a week on this, but I couldn't seem to find out what I have been missing so here I am, asking for some guidance :)
First, I created an empty application, then created a xib file (MainWindow.xib) and added a window object (Main_Window) to it. Then, I created a View object (Main_View) within this Main_Window, and added a label object (lblTitle) to this view. The Main_View object pretty much covered the entire Main_Window screen.
So, in short, the hierarchy of my MainWindow.xib is like this: Main_Window --> Main_View --> lblTitle.
Finally, I created a ViewController object (Main_View_Controller) with its "view" set to Main_View and its "rootViewController" set to "Main_Window".
In the project,
I subclassed UIView with "TouchEvent_View", hooked up to "Main_View" in the xib file.
I subclassed UIViewController with "TouchEvent_ViewController", hooked up to "Main_View_Controller" in the xib file.
In my AppDelegate.h,
I created an "IBOutlet UIWindow *window", hooked up to "Main_Window" in the xib file.
I created an object for my viewController and view classes.
#property (strong, nonatomic) IBOutlet UIWindow *window;
#property (strong, nonatomic) TouchEvent_ViewController * myViewController;
#property (strong, nonatomic) TouchEvent_View *myView;
In AppDelegate.m, I hooked up "MainWindow.xib" with myViewController:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window.backgroundColor = [UIColor whiteColor];
self.myViewController = [[[TouchEvent_ViewController alloc]
initWithNibName:#"MainWindow" bundle:nil] autorelease];
[self.window makeKeyAndVisible];
return YES;
}
In "Touch_Event_ViewController.m", I coded the viewDidLoad message as followed:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Hi! ViewController's viewDidLoad msg sent!");
TouchEvent_View * mView = [[TouchEvent_View alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
mView.backgroundColor = [UIColor blueColor];
[self.view addSubview:mView];
[mView release];
}
In "TouchEvent_View.m", I instantiated a UITapGestureRecognizer object and hooked it up to a handler method as followed:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSLog(#"[TouchEvent_View initWithFrame] sent!");
// Initialization code
//-----------------------
//Touch event declaration
//Single tap
UITapGestureRecognizer * singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(SingleTap_Handler)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
//singleTap.delegate = self;
[self addGestureRecognizer:singleTap];
}
return self;
}
-(void) SingleTap_Handler :(UITapGestureRecognizer *)GR
{
NSLog(#"Hi! You just touched the screen!");
}
When I compiled and deployed the project into my iPad3, every thing worked just as planned until the Touch event, which didn't work.
I got the following messages printed out to the console window:
2012-08-26 14:03:59.589 Ex_TouchEvents_01[806:707] Hi! ViewController's viewDidLoad msg sent!
2012-08-26 14:03:59.593 Ex_TouchEvents_01[806:707] [TouchEvent_View initWithFram] sent!
But I did not see the "Hi, you just touched the screen!" message after I touched the screen. In addition, I didn't see the background of the View area set to blue either. So, I must have been missing something that was very simple. I have been googling all over the web, but I couldn't figure out what I missed. Would some body kindly point out what I have done wrong?
I don't have access to my Mac to test this, but I believe you're missing a colon in this line.
UITapGestureRecognizer * singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(SingleTap_Handler)];
It needs to be:
UITapGestureRecognizer * singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(SingleTap_Handler:)];
If a method takes any parameters, the colon(s) are part of the selector.
Erm, can you try putting:
self.userinteractionEnabled = YES;
in your init method?
UIViews by default is set to not respond to touch events so you have to enabled them first before your tap gestures work.

Changing an image in MKMapKit MKAnnotation Subclass

I am having some problems with changing an image view inside of an MKAnnotationView subclass. I have a timer that updates the annotation position based on data about where a user was at a certain point. This works fine and the annotation position is updated correctly. I do this with the following code:
[_myAnnotation setCoordinate:point.position];
Below this, I also calculate whether the user was heading east or west. If the user is heading east, I call the setIsMovingRight:YES method on the MyAnnotation class, and do the opposite for when the user is heading west.
I have verified that the method is being called with the correct values at the correct times by using NSLog inside the method to print out whenever the value changes. However, it doesn't seem like the setIsMovingRight method has any effect on the visual appearance of the annotation. I try setting the background colour of the imageview to red whenever the method is called, but even this has no effect. I have tried calling setNeedsDisplay in various places with no success.
I can add and recreate an annotation view, but then the annotation flashes on and off whenever the position changes and it really does not look very good.
The following is the code for my custom annotation view, "MyAnnotation".
#interface MyAnnotation : MKAnnotationView <MKAnnotation>
{
UIImageView *_imageView;
BOOL _currentlyMovingRight;
}
#property (nonatomic) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy) NSString *title;
#property (nonatomic, copy) NSString *subtitle;
#property (nonatomic,retain) UIImage *image;
-(void)setIsMovingRight:(BOOL)movingRight;
#end
#implementation MyAnnotation
#synthesize coordinate, title, subtitle, image;
-(id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if(self)
{
_currentlyMovingRight = NO;
_imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"right.png"]];
[self addSubview:_imageView];
}
return self;
}
-(void)setIsMovingRight:(BOOL)movingRight
{
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[_imageView setBackgroundColor:[UIColor redColor]]; // THIS LINE NEVER DOES ANYTHING.
if(movingRight && !_currentlyMovingRight)
{
NSLog(#"right now.");
[_imageView setImage:[UIImage imageNamed:#"right.png"]];
}
else if (!movingRight && _currentlyMovingRight)
{
NSLog(#"left now.");
[_imageView setImage:[UIImage imageNamed:#"left.png"]];
}
_currentlyMovingRight = movingRight;
[self addSubview:_imageView];
[self setNeedsDisplay];
}
I would appreciate any help or advice that anyone could give. Thanks!
I think that the annotation views use KVO to listen for changes. Have you tried changing the image property? Something like [self setImage:myNewImage]

How to Implement ScrollView In TableViewCell

Hey guys I'm trying to implement a scroll with page control, like the iPhone home screen, but in a uitableview cell.
What I tried to do to attempt this is create a custom table view cell with a xib file, and placed the uipagecontrol and uiscrollview on it, and connected it with iboutlets to the uitableviewcell.
This is the code in the .h file for the custom cell.
#interface ScrollableCell : UITableViewCell <UIScrollViewDelegate>
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) IBOutlet UIPageControl *pageControl;
#property (nonatomic, strong) NSMutableArray *viewControllers;
This is the code from the .m file for the custom cell after synthesizing the properties.
- (CGSize)contentSizeForPagingScrollView {
CGRect bounds = self.scrollView.bounds;
return CGSizeMake(bounds.size.width * [self.viewControllers count] , bounds.size.height );
}
#- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
UIView *blueView = [[UIView alloc] initWithFrame:CGRectZero];
[blueView setBackgroundColor:[UIColor blueColor]];
UIView *redView = [[UIView alloc] initWithFrame:CGRectZero];
[redView setBackgroundColor:[UIColor redColor]];
UIView *yellowView = [[UIView alloc] initWithFrame:CGRectZero];
[yellowView setBackgroundColor:[UIColor yellowColor]];
self.viewControllers = [NSArray arrayWithObjects:blueView, redView, yellowView,nil];
for (UIView *view in self.viewControllers) {
[view setFrame:CGRectMake([self.viewControllers indexOfObject:view]*self.scrollView.frame.size.width+5,
5,
self.scrollView.frame.size.width-10,
self.scrollView.frame.size.height-10.0)];
[self.scrollView addSubview:view];
}
self.pageControl.numberOfPages = [self.viewControllers count];
self.pageControl.currentPage = 0;
self.scrollView.pagingEnabled = YES;
self.scrollView.backgroundColor = [UIColor blackColor];
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.contentSize = [self contentSizeForPagingScrollView];
self.scrollView.delegate = self;
[self.contentView addSubview:self.scrollView];
[self.contentView addSubview:self.pageControl];
}
return self;
}
-(void) scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;
}
Hope you guys can help me get this working. If there is a different approach/ better approach that what I'm attempting, let me know.
For visual reference of what I'm trying to achieve, i think the pulse news app fits the bill
http://itunes.apple.com/us/app/pulse-news-for-iphone/id377594176?mt=8
its a table view, with horizontal scrolling on each cell.
Thanks.
If there is a different approach/ better approach that what I'm attempting, let me know.
For visual reference of what I'm trying to achieve, i think the pulse news app fits the bill.
If your main reference for designing this is the Pulse app, you should know that the developers actually did NOT resort to scrollviews. The Pulse app is all tableviews. One main vertical tableview and several horizontal "hacked" tableviews inside each cell that composes the vertical one.
Its quite a smart implementation for a tableview. But who better to explain this to you than the developers of Pulse themselves; they were invited to a lecture at Stanford for their iOS programming course.
Its on iTunes U and it's at 6 min in the lecture called 10 Hacks to Go From Idea To #1 App. You should watch it and maybe rethink your app design if it suits you better.