diff --git a/JSQMessages.xcodeproj/project.pbxproj b/JSQMessages.xcodeproj/project.pbxproj index 8a3395016..9ff643bb1 100644 --- a/JSQMessages.xcodeproj/project.pbxproj +++ b/JSQMessages.xcodeproj/project.pbxproj @@ -95,6 +95,12 @@ 88D1B0CD190606F100AFE162 /* typing@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 88D1B0CB190606F100AFE162 /* typing@2x.png */; }; C78CEF093F584B85B4321C83 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 343577012CC24261BE4B61A1 /* libPods.a */; }; D68A9FB68FC5463D9A5B23E0 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 343577012CC24261BE4B61A1 /* libPods.a */; }; + EB426BB6194F410300A89161 /* JSQImagePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = EB426BB5194F410300A89161 /* JSQImagePicker.m */; }; + EBB0AB9F1950448100388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.m in Sources */ = {isa = PBXBuildFile; fileRef = EBB0AB9E1950448100388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.m */; }; + EBB0ABA21950449A00388B97 /* JSQMessagesCollectionViewCellIncomingMedia.m in Sources */ = {isa = PBXBuildFile; fileRef = EBB0ABA11950449A00388B97 /* JSQMessagesCollectionViewCellIncomingMedia.m */; }; + EBB0ABA41950537E00388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.xib in Resources */ = {isa = PBXBuildFile; fileRef = EBB0ABA31950537E00388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.xib */; }; + EBB0ABA61950547200388B97 /* JSQMessagesCollectionViewCellIncomingMedia.xib in Resources */ = {isa = PBXBuildFile; fileRef = EBB0ABA51950547200388B97 /* JSQMessagesCollectionViewCellIncomingMedia.xib */; }; + EBB0ABA91950868F00388B97 /* JSQMessagesMediaHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = EBB0ABA81950868F00388B97 /* JSQMessagesMediaHandler.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -230,6 +236,16 @@ 88D1B0CB190606F100AFE162 /* typing@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "typing@2x.png"; sourceTree = ""; }; 88FFE06619B2E5CB0038C3FF /* JSQMessagesCollectionViewDelegateFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewDelegateFlowLayout.h; sourceTree = ""; }; A0F1B1EFE54F44FEA6C4F786 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; + EB426BB4194F410300A89161 /* JSQImagePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQImagePicker.h; sourceTree = ""; }; + EB426BB5194F410300A89161 /* JSQImagePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQImagePicker.m; sourceTree = ""; }; + EBB0AB9D1950448100388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewCellOutgoingMedia.h; sourceTree = ""; }; + EBB0AB9E1950448100388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCellOutgoingMedia.m; sourceTree = ""; }; + EBB0ABA01950449A00388B97 /* JSQMessagesCollectionViewCellIncomingMedia.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesCollectionViewCellIncomingMedia.h; sourceTree = ""; }; + EBB0ABA11950449A00388B97 /* JSQMessagesCollectionViewCellIncomingMedia.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesCollectionViewCellIncomingMedia.m; sourceTree = ""; }; + EBB0ABA31950537E00388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQMessagesCollectionViewCellOutgoingMedia.xib; sourceTree = ""; }; + EBB0ABA51950547200388B97 /* JSQMessagesCollectionViewCellIncomingMedia.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JSQMessagesCollectionViewCellIncomingMedia.xib; sourceTree = ""; }; + EBB0ABA71950868F00388B97 /* JSQMessagesMediaHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesMediaHandler.h; sourceTree = ""; }; + EBB0ABA81950868F00388B97 /* JSQMessagesMediaHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaHandler.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -265,6 +281,7 @@ isa = PBXGroup; children = ( 8816A67218E9250400111919 /* JSQMessages.h */, + EB426BB3194F410300A89161 /* Utilities */, 8816A64718E9250400111919 /* Assets */, 8816A65A18E9250400111919 /* Categories */, 8816A66518E9250400111919 /* Controllers */, @@ -396,15 +413,23 @@ 8816A68118E9250400111919 /* JSQMessagesCollectionViewCellIncoming.h */, 8816A68218E9250400111919 /* JSQMessagesCollectionViewCellIncoming.m */, 8816A68318E9250400111919 /* JSQMessagesCollectionViewCellIncoming.xib */, + EBB0ABA01950449A00388B97 /* JSQMessagesCollectionViewCellIncomingMedia.h */, + EBB0ABA11950449A00388B97 /* JSQMessagesCollectionViewCellIncomingMedia.m */, + EBB0ABA51950547200388B97 /* JSQMessagesCollectionViewCellIncomingMedia.xib */, 8816A68418E9250400111919 /* JSQMessagesCollectionViewCellOutgoing.h */, 8816A68518E9250400111919 /* JSQMessagesCollectionViewCellOutgoing.m */, 8816A68618E9250400111919 /* JSQMessagesCollectionViewCellOutgoing.xib */, + EBB0AB9D1950448100388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.h */, + EBB0AB9E1950448100388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.m */, + EBB0ABA31950537E00388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.xib */, 8816A68718E9250400111919 /* JSQMessagesComposerTextView.h */, 8816A68818E9250400111919 /* JSQMessagesComposerTextView.m */, 8816A68918E9250400111919 /* JSQMessagesInputToolbar.h */, 8816A68A18E9250400111919 /* JSQMessagesInputToolbar.m */, 8816A68B18E9250400111919 /* JSQMessagesLabel.h */, 8816A68C18E9250400111919 /* JSQMessagesLabel.m */, + EBB0ABA71950868F00388B97 /* JSQMessagesMediaHandler.h */, + EBB0ABA81950868F00388B97 /* JSQMessagesMediaHandler.m */, 88BC4D31190C6057002E5CC6 /* JSQMessagesLoadEarlierHeaderView.h */, 88BC4D32190C6057002E5CC6 /* JSQMessagesLoadEarlierHeaderView.m */, 88BC4D34190C6086002E5CC6 /* JSQMessagesLoadEarlierHeaderView.xib */, @@ -566,6 +591,15 @@ path = FactoryTests; sourceTree = ""; }; + EB426BB3194F410300A89161 /* Utilities */ = { + isa = PBXGroup; + children = ( + EB426BB4194F410300A89161 /* JSQImagePicker.h */, + EB426BB5194F410300A89161 /* JSQImagePicker.m */, + ); + path = Utilities; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -652,6 +686,7 @@ files = ( 88D1B0CC190606F100AFE162 /* typing.png in Resources */, 885D597718CBD43800D77BB3 /* Main.storyboard in Resources */, + EBB0ABA41950537E00388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.xib in Resources */, 8816A69E18E9250400111919 /* message_received.aiff in Resources */, 885D596918CBD2A600D77BB3 /* Images.xcassets in Resources */, 8816A6B318E9250400111919 /* JSQMessagesCollectionViewCellOutgoing.xib in Resources */, @@ -669,6 +704,7 @@ 8816A6B818E9250400111919 /* JSQMessagesToolbarContentView.xib in Resources */, 8816A69F18E9250400111919 /* message_sent.aiff in Resources */, 8816A69518E9250400111919 /* bubble_min_tailless@2x.png in Resources */, + EBB0ABA61950547200388B97 /* JSQMessagesCollectionViewCellIncomingMedia.xib in Resources */, 8816A69A18E9250400111919 /* bubble_tailless.png in Resources */, 8816A69818E9250400111919 /* bubble_stroked_tailless.png in Resources */, 8816A69718E9250400111919 /* bubble_stroked@2x.png in Resources */, @@ -765,6 +801,7 @@ 8816A6AB18E9250400111919 /* JSQMessagesCollectionViewFlowLayout.m in Sources */, 8816A6B518E9250400111919 /* JSQMessagesInputToolbar.m in Sources */, 88BC4D33190C6057002E5CC6 /* JSQMessagesLoadEarlierHeaderView.m in Sources */, + EBB0ABA21950449A00388B97 /* JSQMessagesCollectionViewCellIncomingMedia.m in Sources */, 8816A6A318E9250400111919 /* UIImage+JSQMessages.m in Sources */, 8816A6A918E9250400111919 /* JSQMessagesTimestampFormatter.m in Sources */, 8816A6B018E9250400111919 /* JSQMessagesCollectionViewCellIncoming.m in Sources */, @@ -777,6 +814,8 @@ 8816A6A018E9250400111919 /* JSQSystemSoundPlayer+JSQMessages.m in Sources */, 8816A6A818E9250400111919 /* JSQMessagesBubbleImageFactory.m in Sources */, 8816A6AD18E9250400111919 /* JSQMessage.m in Sources */, + EBB0ABA91950868F00388B97 /* JSQMessagesMediaHandler.m in Sources */, + EBB0AB9F1950448100388B97 /* JSQMessagesCollectionViewCellOutgoingMedia.m in Sources */, 8897FBF918CBF967004F59C3 /* JSQDemoViewController.m in Sources */, 8816A6AF18E9250400111919 /* JSQMessagesCollectionViewCell.m in Sources */, 885D596B18CBD2A600D77BB3 /* JSQTableViewController.m in Sources */, @@ -785,6 +824,7 @@ 8816A6B418E9250400111919 /* JSQMessagesComposerTextView.m in Sources */, 8816A6AA18E9250400111919 /* JSQMessagesToolbarButtonFactory.m in Sources */, 8816A6A418E9250400111919 /* UIView+JSQMessages.m in Sources */, + EB426BB6194F410300A89161 /* JSQImagePicker.m in Sources */, 8816A6A718E9250400111919 /* JSQMessagesAvatarFactory.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/JSQMessagesDemo/Base.lproj/Main.storyboard b/JSQMessagesDemo/Base.lproj/Main.storyboard index bb64b35f7..c651b4104 100644 --- a/JSQMessagesDemo/Base.lproj/Main.storyboard +++ b/JSQMessagesDemo/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + diff --git a/JSQMessagesDemo/Images.xcassets/accesoryImage.imageset/Contents.json b/JSQMessagesDemo/Images.xcassets/accesoryImage.imageset/Contents.json new file mode 100644 index 000000000..b3e38a948 --- /dev/null +++ b/JSQMessagesDemo/Images.xcassets/accesoryImage.imageset/Contents.json @@ -0,0 +1,17 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "accesoryImage@2x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/JSQMessagesDemo/Images.xcassets/accesoryImage.imageset/accesoryImage@2x.png b/JSQMessagesDemo/Images.xcassets/accesoryImage.imageset/accesoryImage@2x.png new file mode 100644 index 000000000..fd0723474 Binary files /dev/null and b/JSQMessagesDemo/Images.xcassets/accesoryImage.imageset/accesoryImage@2x.png differ diff --git a/JSQMessagesDemo/Images.xcassets/keepcalm.imageset/Contents.json b/JSQMessagesDemo/Images.xcassets/keepcalm.imageset/Contents.json new file mode 100644 index 000000000..a3b453354 --- /dev/null +++ b/JSQMessagesDemo/Images.xcassets/keepcalm.imageset/Contents.json @@ -0,0 +1,17 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "keep-calm-and-don-t-feed-the-troll-22.png" + }, + { + "idiom" : "universal", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/JSQMessagesDemo/Images.xcassets/keepcalm.imageset/keep-calm-and-don-t-feed-the-troll-22.png b/JSQMessagesDemo/Images.xcassets/keepcalm.imageset/keep-calm-and-don-t-feed-the-troll-22.png new file mode 100644 index 000000000..a848cea81 Binary files /dev/null and b/JSQMessagesDemo/Images.xcassets/keepcalm.imageset/keep-calm-and-don-t-feed-the-troll-22.png differ diff --git a/JSQMessagesDemo/JSQDemoViewController.h b/JSQMessagesDemo/JSQDemoViewController.h index d2af45cac..1fdbc6378 100644 --- a/JSQMessagesDemo/JSQDemoViewController.h +++ b/JSQMessagesDemo/JSQDemoViewController.h @@ -19,7 +19,7 @@ #import "JSQMessages.h" @class JSQDemoViewController; - +@class JSQImagePicker; @protocol JSQDemoViewControllerDelegate @@ -39,6 +39,7 @@ @property (strong, nonatomic) UIImageView *outgoingBubbleImageView; @property (strong, nonatomic) UIImageView *incomingBubbleImageView; +@property (strong, nonatomic) JSQImagePicker *picker; - (void)receiveMessagePressed:(UIBarButtonItem *)sender; diff --git a/JSQMessagesDemo/JSQDemoViewController.m b/JSQMessagesDemo/JSQDemoViewController.m index b16f3d0ce..97730f020 100644 --- a/JSQMessagesDemo/JSQDemoViewController.m +++ b/JSQMessagesDemo/JSQDemoViewController.m @@ -17,7 +17,7 @@ // #import "JSQDemoViewController.h" - +#import "JSQImagePicker.h" static NSString * const kJSQDemoAvatarNameCook = @"Tim Cook"; static NSString * const kJSQDemoAvatarNameJobs = @"Jobs"; @@ -42,8 +42,8 @@ - (void)setupTestModel [[JSQMessage alloc] initWithText:@"JSQMessagesViewController is nearly an exact replica of the iOS Messages App. And perhaps, better." sender:kJSQDemoAvatarNameJobs date:[NSDate date]], [[JSQMessage alloc] initWithText:@"It is unit-tested, free, and open-source." sender:kJSQDemoAvatarNameCook date:[NSDate date]], [[JSQMessage alloc] initWithText:@"Oh, and there's sweet documentation." sender:self.sender date:[NSDate date]], + [JSQMessage messageWithImage:[UIImage imageNamed:@"keepcalm"] sender:kJSQDemoAvatarNameCook], nil]; - /** * Create avatar images once. * @@ -137,6 +137,8 @@ - (void)viewDidLoad self.incomingBubbleImageView = [JSQMessagesBubbleImageFactory incomingMessageBubbleImageViewWithColor:[UIColor jsq_messageBubbleGreenColor]]; + self.picker = [JSQImagePicker new]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"typing"] style:UIBarButtonItemStyleBordered target:self @@ -163,11 +165,9 @@ - (void)viewDidAppear:(BOOL)animated * You must set this from `viewDidAppear:` * Note: this feature is mostly stable, but still experimental */ - self.collectionView.collectionViewLayout.springinessEnabled = YES; + self.collectionView.collectionViewLayout.springinessEnabled = NO; } - - #pragma mark - Actions - (void)receiveMessagePressed:(UIBarButtonItem *)sender @@ -247,12 +247,23 @@ - (void)didPressSendButton:(UIButton *)button - (void)didPressAccessoryButton:(UIButton *)sender { NSLog(@"Camera pressed!"); - /** - * Accessory button has no default functionality, yet. - */ -} + + __weak __typeof(self) weakSelf = self; + + [self.inputToolbar hideKeyboard]; + [self.picker pickImageFromViewController:self + handler:^(UIImage *image, NSError *error) { + + __typeof(self) strongSelf = weakSelf; + JSQMessage *copyMessage = [JSQMessage messageWithImage:image sender:strongSelf.sender]; + [strongSelf.messages addObject:copyMessage]; + [strongSelf finishReceivingMessage]; + } + dismissHandler:^{ + }]; +} #pragma mark - JSQMessages CollectionView DataSource @@ -358,6 +369,16 @@ - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionVi return nil; } +-(UIImage *)collectionView:(JSQMessagesCollectionView *)collectionView accesoryViewForCellAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.item == 0) + { + return [UIImage imageNamed:@"accesoryImage"]; + } + else + return nil; +} + #pragma mark - UICollectionView DataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section @@ -370,7 +391,7 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection /** * Override point for customizing cells */ - JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath]; + UICollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath]; /** * Configure almost *anything* on the cell @@ -388,16 +409,21 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection JSQMessage *msg = [self.messages objectAtIndex:indexPath.item]; - if ([msg.sender isEqualToString:self.sender]) { - cell.textView.textColor = [UIColor blackColor]; - } - else { - cell.textView.textColor = [UIColor whiteColor]; + if (msg.kind == JSQMessageTextKind) + { + JSQMessagesCollectionViewCell *textCell = (JSQMessagesCollectionViewCell *) cell; + + if ([msg.sender isEqualToString:self.sender]) { + textCell.textView.textColor = [UIColor blackColor]; + } + else { + textCell.textView.textColor = [UIColor whiteColor]; + } + + textCell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : textCell.textView.textColor, + NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) }; } - cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : cell.textView.textColor, - NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) }; - return cell; } diff --git a/JSQMessagesViewController.podspec b/JSQMessagesViewController.podspec index 12578a3e5..539735c11 100644 --- a/JSQMessagesViewController.podspec +++ b/JSQMessagesViewController.podspec @@ -16,4 +16,6 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency 'JSQSystemSoundPlayer' + s.dependency 'SDWebImage' + end diff --git a/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.m b/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.m index f282423b0..57449aa59 100644 --- a/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.m +++ b/JSQMessagesViewController/Controllers/JSQMessagesKeyboardController.m @@ -227,13 +227,11 @@ - (void)jsq_handleKeyboardNotification:(NSNotification *)notification completion double animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - CGRect keyboardEndFrameConverted = [self.contextView convertRect:keyboardEndFrame fromView:nil]; - [UIView animateWithDuration:animationDuration delay:0.0 options:animationCurveOption animations:^{ - [self jsq_notifyKeyboardFrameNotificationForFrame:keyboardEndFrameConverted]; + [self jsq_notifyKeyboardFrameNotificationForFrame:keyboardEndFrame]; } completion:^(BOOL finished) { if (completion) { @@ -274,9 +272,15 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N return; } - // do not convert frame to contextView coordinates here - // KVO is triggered during panning (see below) - // panning occurs in contextView coordinates already + /** + * The math done in jsq_handlePanGestureRecognizer is + * for UIPeripheralHostView, which is rotated when in landscape. + * Here we translate it back to UIWindow coordinates + */ + if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { + newKeyboardFrame = CGRectMake(newKeyboardFrame.origin.y, newKeyboardFrame.origin.x, newKeyboardFrame.size.height, newKeyboardFrame.size.width); + } + [self jsq_notifyKeyboardFrameNotificationForFrame:newKeyboardFrame]; } } @@ -302,30 +306,33 @@ - (void)jsq_removeKeyboardFrameObserver - (void)jsq_handlePanGestureRecognizer:(UIPanGestureRecognizer *)pan { - CGPoint touch = [pan locationInView:self.contextView]; - + CGPoint touchInWindow = [pan locationInView:nil]; + // system keyboard is added to a new UIWindow, need to operate in window coordinates // also, keyboard always slides from bottom of screen, not the bottom of a view CGFloat contextViewWindowHeight = CGRectGetHeight(self.contextView.window.frame); if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { contextViewWindowHeight = CGRectGetWidth(self.contextView.window.frame); } - + CGFloat keyboardViewHeight = CGRectGetHeight(self.keyboardView.frame); CGFloat dragThresholdY = (contextViewWindowHeight - keyboardViewHeight - self.keyboardTriggerPoint.y); CGRect newKeyboardViewFrame = self.keyboardView.frame; - BOOL userIsDraggingNearThresholdForDismissing = (touch.y > dragThresholdY); + BOOL userIsDraggingNearThresholdForDismissing = (touchInWindow.y > dragThresholdY); self.keyboardView.userInteractionEnabled = !userIsDraggingNearThresholdForDismissing; switch (pan.state) { - case UIGestureRecognizerStateChanged: + case UIGestureRecognizerStateChanged: { - newKeyboardViewFrame.origin.y = touch.y + self.keyboardTriggerPoint.y; - + newKeyboardViewFrame.origin.y = touchInWindow.y + self.keyboardTriggerPoint.y; + if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { + newKeyboardViewFrame.origin.y = touchInWindow.x + self.keyboardTriggerPoint.y; + } + // bound frame between bottom of view and height of keyboard newKeyboardViewFrame.origin.y = MIN(newKeyboardViewFrame.origin.y, contextViewWindowHeight); newKeyboardViewFrame.origin.y = MAX(newKeyboardViewFrame.origin.y, contextViewWindowHeight - keyboardViewHeight); @@ -333,7 +340,7 @@ - (void)jsq_handlePanGestureRecognizer:(UIPanGestureRecognizer *)pan if (CGRectGetMinY(newKeyboardViewFrame) == CGRectGetMinY(self.keyboardView.frame)) { return; } - + [UIView animateWithDuration:0.0 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionTransitionNone diff --git a/JSQMessagesViewController/Controllers/JSQMessagesViewController.h b/JSQMessagesViewController/Controllers/JSQMessagesViewController.h index feb5eabd9..7763ec7ca 100644 --- a/JSQMessagesViewController/Controllers/JSQMessagesViewController.h +++ b/JSQMessagesViewController/Controllers/JSQMessagesViewController.h @@ -92,6 +92,9 @@ */ @property (copy, nonatomic) NSString *incomingCellIdentifier; +@property (copy, nonatomic) NSString *outgoingMediaCellIdentifier; +@property (copy, nonatomic) NSString *incomingMediaCellIdentifier; + /** * The color for the typing indicator for incoming messages. * diff --git a/JSQMessagesViewController/Controllers/JSQMessagesViewController.m b/JSQMessagesViewController/Controllers/JSQMessagesViewController.m index 6db736fbd..e958168d3 100644 --- a/JSQMessagesViewController/Controllers/JSQMessagesViewController.m +++ b/JSQMessagesViewController/Controllers/JSQMessagesViewController.m @@ -27,6 +27,8 @@ #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" +#import "JSQMessagesCollectionViewCellIncomingMedia.h" +#import "JSQMessagesCollectionViewCellOutgoingMedia.h" #import "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesLoadEarlierHeaderView.h" @@ -40,7 +42,6 @@ #import "NSString+JSQMessages.h" #import "UIColor+JSQMessages.h" - static void * kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObservingContext; @@ -134,6 +135,9 @@ - (void)jsq_configureMessagesViewController self.outgoingCellIdentifier = [JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]; self.incomingCellIdentifier = [JSQMessagesCollectionViewCellIncoming cellReuseIdentifier]; + self.outgoingMediaCellIdentifier = [JSQMessagesCollectionViewCellOutgoingMedia cellReuseIdentifier]; + self.incomingMediaCellIdentifier = [JSQMessagesCollectionViewCellIncomingMedia cellReuseIdentifier];; + self.typingIndicatorColor = [UIColor jsq_messageBubbleLightGrayColor]; self.showTypingIndicator = NO; @@ -371,6 +375,11 @@ - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionVi return nil; } +-(UIImage *)collectionView:(JSQMessagesCollectionView *)collectionView accesoryViewForCellAtIndexPath:(NSIndexPath *)indexPath +{ + return nil; +} + #pragma mark - Collection view data source - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section @@ -392,28 +401,55 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection NSParameterAssert(messageSender != nil); BOOL isOutgoingMessage = [messageSender isEqualToString:self.sender]; + NSString *cellIdentifier; - NSString *cellIdentifier = isOutgoingMessage ? self.outgoingCellIdentifier : self.incomingCellIdentifier; - JSQMessagesCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; - cell.delegate = collectionView; + switch (messageData.kind) { + case JSQMessageTextKind: + { + cellIdentifier = isOutgoingMessage ? self.outgoingCellIdentifier : self.incomingCellIdentifier; + } + break; + case JSQMessageLocalMediaKind: + case JSQMessageRemoteMediaKind: + { + cellIdentifier = isOutgoingMessage ? self.outgoingMediaCellIdentifier : self.incomingMediaCellIdentifier; + } + break; + } - NSString *messageText = [messageData text]; - NSParameterAssert(messageText != nil); + /** + * Common parameters + */ - cell.textView.text = messageText; + JSQMessagesCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; + cell.delegate = collectionView; + cell.messageBubbleImageView = [collectionView.dataSource collectionView:collectionView bubbleImageViewForItemAtIndexPath:indexPath]; cell.avatarImageView = [collectionView.dataSource collectionView:collectionView avatarImageViewForItemAtIndexPath:indexPath]; cell.cellTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellTopLabelAtIndexPath:indexPath]; cell.messageBubbleTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:indexPath]; cell.cellBottomLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath]; - if (isOutgoingMessage) { + UIImage *accesoryImage = [collectionView.dataSource collectionView:collectionView accesoryViewForCellAtIndexPath:indexPath]; + if (accesoryImage) { + [cell.accessoryImageView setImage:accesoryImage]; + } else { + [cell.accessoryImageView setImage:nil]; + } + + CGFloat bubbleTopLabelInset = 60.0f; + + if (isOutgoingMessage) + { + cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, bubbleTopLabelInset); cell.avatarImageView.bounds = CGRectMake(CGRectGetMinX(cell.avatarImageView.bounds), CGRectGetMinY(cell.avatarImageView.bounds), collectionView.collectionViewLayout.outgoingAvatarViewSize.width, collectionView.collectionViewLayout.outgoingAvatarViewSize.height); } - else { + else + { + cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, bubbleTopLabelInset, 0.0f, 0.0f); cell.avatarImageView.bounds = CGRectMake(CGRectGetMinX(cell.avatarImageView.bounds), CGRectGetMinY(cell.avatarImageView.bounds), collectionView.collectionViewLayout.incomingAvatarViewSize.width, @@ -421,18 +457,40 @@ - (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collection } cell.backgroundColor = [UIColor clearColor]; - - CGFloat bubbleTopLabelInset = 60.0f; - - if (isOutgoingMessage) { - cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, bubbleTopLabelInset); + + if (messageData.kind == JSQMessageTextKind) + { + NSString *messageText = [messageData text]; + NSParameterAssert(messageText != nil); + + cell.textView.text = messageText; + cell.textView.dataDetectorTypes = UIDataDetectorTypeAll; } - else { - cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, bubbleTopLabelInset, 0.0f, 0.0f); + else + { + JSQMessagesMediaHandler *mediaHandler; + + if (isOutgoingMessage) + { + JSQMessagesCollectionViewCellOutgoingMedia *mediaCell = (JSQMessagesCollectionViewCellOutgoingMedia *) cell; + mediaHandler = mediaCell.mediaHandler; + } + else + { + JSQMessagesCollectionViewCellIncomingMedia *mediaCell = (JSQMessagesCollectionViewCellIncomingMedia *) cell; + mediaHandler = mediaCell.mediaHandler; + } + + if (messageData.kind == JSQMessageLocalMediaKind) + { + [mediaHandler setMediaFromImage:messageData.image]; + } + else if (messageData.kind == JSQMessageRemoteMediaKind) + { + [mediaHandler setMediaFromURL:messageData.url]; + } } - - cell.textView.dataDetectorTypes = UIDataDetectorTypeAll; - + return cell; } @@ -543,6 +601,37 @@ - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapAvatarImageView:(UIImageView *)avatarImageView atIndexPath:(NSIndexPath *)indexPath { } +- (void)collectionView:(JSQMessagesCollectionView *)collectionView + didTapMediaImageView:(UIImageView *)mediaImageView + atIndexPath:(NSIndexPath *)indexPath { } + +- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCopyMessageAtIndexPath:(NSIndexPath *)indexPath; +{ + id messageData = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath]; + + switch ([messageData kind]) { + case JSQMessageTextKind: + { + NSString *text = [messageData text]; + [[UIPasteboard generalPasteboard] setString:text]; + } + break; + case JSQMessageLocalMediaKind: + { + UIImage *image = [messageData image]; + [[UIPasteboard generalPasteboard] setImage:image]; + } + break; + + case JSQMessageRemoteMediaKind: + { + NSString *remoteURL = [[messageData url] absoluteString]; + [[UIPasteboard generalPasteboard] setString:remoteURL]; + } + break; + } +} + - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath { } - (void)collectionView:(JSQMessagesCollectionView *)collectionView @@ -692,9 +781,16 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N - (void)keyboardController:(JSQMessagesKeyboardController *)keyboardController keyboardDidChangeFrame:(CGRect)keyboardFrame { - CGFloat heightFromBottom = CGRectGetHeight(self.collectionView.frame) - CGRectGetMinY(keyboardFrame); + UIWindow *window = self.view.window; + + CGRect collectionViewFrame = [window convertRect:self.collectionView.frame + fromView:self.view]; + + CGRect coveredFrame = CGRectIntersection(collectionViewFrame, keyboardFrame); + + coveredFrame = [window convertRect:coveredFrame toView:self.view]; - heightFromBottom = MAX(0.0f, heightFromBottom); + CGFloat heightFromBottom = CGRectGetHeight(coveredFrame); [self jsq_setToolbarBottomLayoutGuideConstant:heightFromBottom]; } diff --git a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m index 951dfd84d..dbab59924 100644 --- a/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m +++ b/JSQMessagesViewController/Layout/JSQMessagesCollectionViewFlowLayout.m @@ -31,6 +31,8 @@ #import "JSQMessagesCollectionViewLayoutAttributes.h" #import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h" +#import + const CGFloat kJSQMessagesCollectionViewCellLabelHeightDefault = 20.0f; @@ -376,17 +378,35 @@ - (CGSize)messageBubbleSizeForItemAtIndexPath:(NSIndexPath *)indexPath CGFloat maximumTextWidth = self.itemWidth - avatarSize.width - self.messageBubbleLeftRightMargin; CGFloat textInsetsTotal = [self jsq_messageBubbleTextContainerInsetsTotal]; - - CGRect stringRect = [[messageData text] boundingRectWithSize:CGSizeMake(maximumTextWidth - textInsetsTotal, CGFLOAT_MAX) - options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) - attributes:@{ NSFontAttributeName : self.messageBubbleFont } - context:nil]; - - CGSize stringSize = CGRectIntegral(stringRect).size; - - CGFloat verticalInsets = self.messageBubbleTextViewTextContainerInsets.top + self.messageBubbleTextViewTextContainerInsets.bottom; - - CGSize finalSize = CGSizeMake(stringSize.width, stringSize.height + verticalInsets); + + CGSize finalSize; + + switch (messageData.kind) { + case JSQMessageTextKind: + { + /** + * Fixed with diff https://github.com/rahulgautam/JSQMessagesViewController/commit/6846215619e414b625af02a8cd276681188a3421 + */ + + NSAttributedString *string = [[NSAttributedString alloc] initWithString:[messageData text] + attributes:@{ NSFontAttributeName : self.messageBubbleFont }]; + + CGSize stringSize = [self jsq_frameSizeForAttributedString:string + maxwidth:maximumTextWidth - textInsetsTotal]; + + CGFloat verticalInsets = self.messageBubbleTextViewTextContainerInsets.top + self.messageBubbleTextViewTextContainerInsets.bottom; + + finalSize = CGSizeMake(stringSize.width, stringSize.height + verticalInsets); + } + break; + + case JSQMessageLocalMediaKind: + case JSQMessageRemoteMediaKind: + { + finalSize = CGSizeMake(100, 100); + } + break; + } [self.messageBubbleSizes setObject:[NSValue valueWithCGSize:finalSize] forKey:indexPath]; @@ -445,6 +465,33 @@ - (CGSize)jsq_avatarSizeForIndexPath:(NSIndexPath *)indexPath return self.incomingAvatarViewSize; } +- (CGSize)jsq_frameSizeForAttributedString:(NSAttributedString *)attributedString maxwidth:(CGFloat)width +{ + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)attributedString); + CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(width, CGFLOAT_MAX), NULL); + CFRelease(framesetter); + + CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attributedString); + CFIndex offset = 0, length; + CGFloat y = 0; + do { + length = CTTypesetterSuggestLineBreak(typesetter, offset, width); + CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(offset, length)); + + CGFloat ascent, descent, leading; + CTLineGetTypographicBounds(line, &ascent, &descent, &leading); + + CFRelease(line); + + offset += length; + y += ascent + descent + leading; + } while (offset < [attributedString length]); + + CFRelease(typesetter); + + return CGSizeMake(suggestedSize.width, ceil(y)); +} + #pragma mark - Spring behavior utilities - (UIAttachmentBehavior *)jsq_springBehaviorWithLayoutAttributesItem:(UICollectionViewLayoutAttributes *)item diff --git a/JSQMessagesViewController/Model/JSQMessage.h b/JSQMessagesViewController/Model/JSQMessage.h index a9b717e30..2559181d8 100644 --- a/JSQMessagesViewController/Model/JSQMessage.h +++ b/JSQMessagesViewController/Model/JSQMessage.h @@ -28,10 +28,25 @@ @interface JSQMessage : NSObject /** - * The body text of the message. This value must not be `nil`. + * Message kind + */ +@property (nonatomic) JSQMessageKind kind; + +/** + * The body text of the message. */ @property (copy, nonatomic) NSString *text; +/** + * The image of the message. + */ +@property (copy, nonatomic) UIImage *image; + +/** + * The URL of the remote message. + */ +@property (copy, nonatomic) NSURL *url; + /** * The name of user who sent the message. This value must not be `nil`. */ @@ -67,6 +82,52 @@ sender:(NSString *)sender date:(NSDate *)date; +/** + * Initializes and returns a message object having the given image, sender, and current system date. + * + * @param image The image of the message + * @param sender The name of the user who sent the message + * + * @return An initialized `JSQMessage` object or `nil` if the object could not be successfully initialized + */ ++ (instancetype)messageWithImage:(UIImage *)image sender:(NSString *)sender; + +/** + * Initializes and returns a message object having the given image, sender, and date. + * + * @param image The image of the message. + * @param sender The name of the user who sent the message. + * @param date The date that the message was sent. + * + * @return An initialized `JSQMessage` object or `nil` if the object could not be successfully initialized. + */ +- (instancetype)initWithImage:(UIImage *)image + sender:(NSString *)sender + date:(NSDate *)date; + +/** + * Initializes and returns a message object having the given URL, sender, and current system date. + * + * @param url The URL for the remote image of the message + * @param sender The name of the user who sent the message + * + * @return An initialized `JSQMessage` object or `nil` if the object could not be successfully initialized + */ ++ (instancetype)messageWithURL:(NSURL *)url sender:(NSString *)sender; + +/** + * Initializes and returns a message object having the given image, sender, and date. + * + * @param url The URL for the remote image of the message. + * @param sender The name of the user who sent the message. + * @param date The date that the message was sent. + * + * @return An initialized `JSQMessage` object or `nil` if the object could not be successfully initialized. + */ +- (instancetype)initWithURL:(NSURL *)url + sender:(NSString *)sender + date:(NSDate *)date; + /** * Returns a boolean value that indicates whether a given message is equal to the receiver. * diff --git a/JSQMessagesViewController/Model/JSQMessage.m b/JSQMessagesViewController/Model/JSQMessage.m index d0dd78f47..578a4dad1 100644 --- a/JSQMessagesViewController/Model/JSQMessage.m +++ b/JSQMessagesViewController/Model/JSQMessage.m @@ -40,6 +40,55 @@ - (instancetype)initWithText:(NSString *)text _text = text; _sender = sender; _date = date; + _kind = JSQMessageTextKind; + } + return self; +} + ++ (instancetype)messageWithImage:(UIImage *)image sender:(NSString *)sender; +{ + return [[self alloc] initWithImage:image sender:sender date:[NSDate date]]; +} + +- (instancetype)initWithImage:(UIImage *)image + sender:(NSString *)sender + date:(NSDate *)date; +{ + NSParameterAssert(image != nil); + NSParameterAssert(sender != nil); + NSParameterAssert(date != nil); + + self = [self init]; + if (self) + { + _sender = sender; + _date = date; + _image = image; + _kind = JSQMessageLocalMediaKind; + } + return self; +} + ++ (instancetype)messageWithURL:(NSURL *)url sender:(NSString *)sender; +{ + return [[self alloc] initWithURL:url sender:sender date:[NSDate date]]; +} + +- (instancetype)initWithURL:(NSURL *)url + sender:(NSString *)sender + date:(NSDate *)date; +{ + NSParameterAssert(url != nil); + NSParameterAssert(sender != nil); + NSParameterAssert(date != nil); + + self = [self init]; + if (self) + { + _sender = sender; + _date = [NSDate date]; + _url = url; + _kind = JSQMessageRemoteMediaKind; } return self; } @@ -51,6 +100,7 @@ - (instancetype)init _text = @""; _sender = @""; _date = [NSDate date]; + _kind = JSQMessageTextKind; } return self; } @@ -88,7 +138,7 @@ - (BOOL)isEqual:(id)object - (NSUInteger)hash { - return [self.text hash] ^ [self.sender hash] ^ [self.date hash]; + return [self.text hash] ^ [self.sender hash] ^ [self.date hash] ^ [self.url hash] ^ [self.image hash] ^ [@(self.kind) hash]; } - (NSString *)description @@ -103,6 +153,9 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder self = [super init]; if (self) { _text = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(text))]; + _url = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(url))]; + _image = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(image))]; + _kind = [[aDecoder decodeObjectForKey:NSStringFromSelector(@selector(kind))] integerValue]; _sender = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(sender))]; _date = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(date))]; } @@ -112,6 +165,9 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.text forKey:NSStringFromSelector(@selector(text))]; + [aCoder encodeObject:self.url forKey:NSStringFromSelector(@selector(url))]; + [aCoder encodeObject:self.image forKey:NSStringFromSelector(@selector(image))]; + [aCoder encodeObject:@(self.kind) forKey:NSStringFromSelector(@selector(kind))]; [aCoder encodeObject:self.sender forKey:NSStringFromSelector(@selector(sender))]; [aCoder encodeObject:self.date forKey:NSStringFromSelector(@selector(date))]; } diff --git a/JSQMessagesViewController/Model/JSQMessageData.h b/JSQMessagesViewController/Model/JSQMessageData.h index 3cc1685b3..d1c8e83a9 100644 --- a/JSQMessagesViewController/Model/JSQMessageData.h +++ b/JSQMessagesViewController/Model/JSQMessageData.h @@ -21,6 +21,12 @@ #import +typedef NS_ENUM(NSUInteger, JSQMessageKind) { + JSQMessageTextKind, + JSQMessageLocalMediaKind, + JSQMessageRemoteMediaKind, +}; + /** * The `JSQMessageData` protocol defines the common interface through * which `JSQMessagesViewController` and `JSQMessagesCollectionView` interacts with message model objects. @@ -33,10 +39,10 @@ @required /** - * @return The body text of the message. - * @warning You must not return `nil` from this method. + * @return The message kind. + * @warning Must also conform to the optionals below. */ -- (NSString *)text; +- (JSQMessageKind) kind; /** * @return The name of the user who sent the message. @@ -50,6 +56,23 @@ */ - (NSDate *)date; +@optional + +/** + * @return The body text of the message. + */ +- (NSString *)text; + +/** + * @return URL for the remote media on the message. + */ +- (NSURL *)url; + +/** + * @return UIImage of the message. + */ +- (UIImage *)image; + @end -#endif \ No newline at end of file +#endif diff --git a/JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h b/JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h index 04fede483..730b66603 100644 --- a/JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h +++ b/JSQMessagesViewController/Model/JSQMessagesCollectionViewDataSource.h @@ -138,6 +138,17 @@ */ - (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath; +/** + * Asks the data source for the UIImage to set as + * an accesory view for the cell at a given indexPat + * + * @param collectionView The object representing the collection view requesting this information. + * @param indexPath The index path that specifies the location of the item. + * + * @return A 15x15 UIImage to set as the cell's accesoryView + */ +- (UIImage *) collectionView:(JSQMessagesCollectionView *)collectionView accesoryViewForCellAtIndexPath:(NSIndexPath *)indexPath; + @end #endif diff --git a/JSQMessagesViewController/Utilities/JSQImagePicker.h b/JSQMessagesViewController/Utilities/JSQImagePicker.h new file mode 100644 index 000000000..beebda730 --- /dev/null +++ b/JSQMessagesViewController/Utilities/JSQImagePicker.h @@ -0,0 +1,19 @@ +// +// VImagePicker.h +// VoalteClientCommon +// +// Created by Pierluigi Cifani on 16/6/14. +// Copyright (c) 2014 Voalte Inc. All rights reserved. +// + +#import + +typedef void(^JSQPickerHandler)(UIImage *image, NSError *error); + +@interface JSQImagePicker : NSObject + +- (void) pickImageFromViewController:(UIViewController *)viewController + handler:(JSQPickerHandler)uploadHandler + dismissHandler:(dispatch_block_t)dismissHandler; + +@end diff --git a/JSQMessagesViewController/Utilities/JSQImagePicker.m b/JSQMessagesViewController/Utilities/JSQImagePicker.m new file mode 100644 index 000000000..ea1e88f84 --- /dev/null +++ b/JSQMessagesViewController/Utilities/JSQImagePicker.m @@ -0,0 +1,92 @@ +// +// VImagePicker.m +// VoalteClientCommon +// +// Created by Pierluigi Cifani on 16/6/14. +// Copyright (c) 2014 Voalte Inc. All rights reserved. +// + +#import "JSQImagePicker.h" + +#define kCameraIndex 0 +#define kAlbumIndex 1 +#define kDismissIndex 2 + +@interface JSQImagePicker () + +@property (nonatomic) UIViewController *presentingVC; +@property (nonatomic, copy) JSQPickerHandler handler; +@property (nonatomic, copy) dispatch_block_t dismissHandler; + +@end + +@implementation JSQImagePicker + +- (void) pickImageFromViewController:(UIViewController *)viewController + handler:(JSQPickerHandler)uploadHandler + dismissHandler:(dispatch_block_t)dismissHandler; + +{ + self.presentingVC = viewController; + self.handler = uploadHandler; + self.dismissHandler = dismissHandler; + + UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"Upload a photo" delegate:self cancelButtonTitle:@"Dismiss" destructiveButtonTitle:nil otherButtonTitles:@"From Camera", @"From Photo library", nil]; + + [actionSheet showInView:viewController.view]; +} + +#pragma mark + +- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex; +{ + UIImagePickerController * picker = [[UIImagePickerController alloc] init]; + picker.delegate = (id ) self; + + if (buttonIndex == kDismissIndex) + { + if (self.dismissHandler) + { + self.dismissHandler(); + } + } + else + { + if (buttonIndex == kAlbumIndex) + { + [picker setSourceType:(UIImagePickerControllerSourceTypePhotoLibrary)]; + } + else if (buttonIndex == kCameraIndex) + { + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) + { + [picker setSourceType:(UIImagePickerControllerSourceTypeCamera)]; + [picker setCameraDevice:UIImagePickerControllerCameraDeviceFront]; + } + } + + [self.presentingVC presentViewController:picker + animated:YES + completion:nil]; + } + +} + +#pragma mark - UIImagePickerControllerDelegate + +- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info; +{ + __weak __typeof(self) weakSelf = self; + + [self.presentingVC dismissViewControllerAnimated:YES + completion:^{ + + UIImage *selectedImage = info[UIImagePickerControllerOriginalImage]; + __typeof(self) strongSelf = weakSelf; + + + strongSelf.handler(selectedImage, nil); + }]; +} + +@end diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionView.h b/JSQMessagesViewController/Views/JSQMessagesCollectionView.h index 223345ec4..959296920 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionView.h +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionView.h @@ -26,6 +26,7 @@ @class JSQMessagesTypingIndicatorFooterView; @class JSQMessagesLoadEarlierHeaderView; +@protocol JSQMessageData; /** * The `JSQMessagesCollectionView` class manages an ordered collection of message data items and presents diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionView.m b/JSQMessagesViewController/Views/JSQMessagesCollectionView.m index 14199088e..523322da9 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionView.m +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionView.m @@ -21,6 +21,8 @@ #import "JSQMessagesCollectionViewFlowLayout.h" #import "JSQMessagesCollectionViewCellIncoming.h" #import "JSQMessagesCollectionViewCellOutgoing.h" +#import "JSQMessagesCollectionViewCellIncomingMedia.h" +#import "JSQMessagesCollectionViewCellOutgoingMedia.h" #import "JSQMessagesTypingIndicatorFooterView.h" #import "JSQMessagesLoadEarlierHeaderView.h" @@ -52,6 +54,12 @@ - (void)jsq_configureCollectionView [self registerNib:[JSQMessagesCollectionViewCellOutgoing nib] forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]]; + [self registerNib:[JSQMessagesCollectionViewCellIncomingMedia nib] +forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellIncomingMedia cellReuseIdentifier]]; + + [self registerNib:[JSQMessagesCollectionViewCellOutgoingMedia nib] +forCellWithReuseIdentifier:[JSQMessagesCollectionViewCellOutgoingMedia cellReuseIdentifier]]; + [self registerNib:[JSQMessagesTypingIndicatorFooterView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[JSQMessagesTypingIndicatorFooterView footerReuseIdentifier]]; @@ -136,4 +144,9 @@ - (void)messagesCollectionViewCellDidTapCell:(JSQMessagesCollectionViewCell *)ce touchLocation:position]; } +-(void)messagesCollectionViewCellDidRequestCopy:(JSQMessagesCollectionViewCell *)cell +{ + +} + @end diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h index ad3625a06..65ec851ee 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.h @@ -37,6 +37,13 @@ */ - (void)messagesCollectionViewCellDidTapAvatar:(JSQMessagesCollectionViewCell *)cell; +/** + * Tells the delegate that the user requested to copy this cell's contents. + * + * @param cell The cell that received the tap touch event. + */ +- (void)messagesCollectionViewCellDidRequestCopy:(JSQMessagesCollectionViewCell *)cell; + /** * Tells the delegate that the message bubble of the cell has been tapped. * @@ -99,6 +106,13 @@ */ @property (weak, nonatomic, readonly) UITextView *textView; + +/** + * Returns the image view for displaying media in the cell. + */ + +@property (weak, nonatomic) IBOutlet UIImageView *mediaImageView; + /** * Returns the message bubble container view of the cell. This view is the superview of * the cell's textView and messageBubbleImageView. @@ -137,6 +151,19 @@ */ @property (weak, nonatomic) UIImageView *avatarImageView; +/** + * The accessory image view. + */ +@property (weak, nonatomic) IBOutlet UIImageView *accessoryImageView; +@property (nonatomic) CGFloat accesoryImageSize; + +/** + * Returns the underlying gesture recognizer for long press gestures in the cell. + * This gesture handles the copy action for the cell. + * Access this property when you need to override or more precisely control the long press gesture. + */ +@property (weak, nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer; + /** * Returns the underlying gesture recognizer for tap gestures in the avatarImageView of the cell. * This gesture handles the tap event for the avatarImageView and notifies the cell's delegate. diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m index fcef5dbef..9e40f7dd4 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCell.m @@ -55,6 +55,7 @@ @interface JSQMessagesCollectionViewCell () @property (assign, nonatomic) CGSize avatarViewSize; @property (weak, nonatomic, readwrite) UITapGestureRecognizer *tapGestureRecognizer; +@property (weak, nonatomic, readwrite) UITapGestureRecognizer *tapMediaGestureRecognizer; - (void)jsq_handleTapGesture:(UITapGestureRecognizer *)tap; @@ -123,6 +124,8 @@ - (void)awakeFromNib UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(jsq_handleTapGesture:)]; [self addGestureRecognizer:tap]; self.tapGestureRecognizer = tap; + + [self setAccesoryImageSize:15.0]; } - (void)dealloc @@ -138,6 +141,9 @@ - (void)dealloc [_tapGestureRecognizer removeTarget:nil action:NULL]; _tapGestureRecognizer = nil; + + [_tapMediaGestureRecognizer removeTarget:nil action:NULL]; + _tapMediaGestureRecognizer = nil; } #pragma mark - Collection view cell @@ -236,7 +242,15 @@ - (void)setMessageBubbleImageView:(UIImageView *)messageBubbleImageView CGRectGetHeight(self.messageBubbleContainerView.bounds)); [messageBubbleImageView setTranslatesAutoresizingMaskIntoConstraints:NO]; - [self.messageBubbleContainerView insertSubview:messageBubbleImageView belowSubview:self.textView]; + + if (self.textView) + { + [self.messageBubbleContainerView insertSubview:messageBubbleImageView belowSubview:self.textView]; + } + else if (self.mediaImageView) + { + [self.messageBubbleContainerView insertSubview:messageBubbleImageView belowSubview:self.mediaImageView]; + } [self.messageBubbleContainerView jsq_pinAllEdgesOfSubview:messageBubbleImageView]; [self setNeedsUpdateConstraints]; @@ -289,6 +303,16 @@ - (void)setTextViewFrameInsets:(UIEdgeInsets)textViewFrameInsets [self jsq_updateConstraint:self.textViewMarginHorizontalSpaceConstraint withConstant:textViewFrameInsets.left]; } +-(void)setAccesoryImageSize:(CGFloat)accesoryImageSize +{ + [self.accessoryImageView removeConstraints:self.accessoryImageView.constraints]; + + NSLayoutConstraint *widhtConstraint = [NSLayoutConstraint constraintWithItem:self.accessoryImageView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:accesoryImageSize]; + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.accessoryImageView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:self.accessoryImageView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; + + [self.accessoryImageView addConstraints:@[widhtConstraint, heightConstraint]]; +} + #pragma mark - Getters - (CGSize)avatarViewSize @@ -317,6 +341,29 @@ - (void)jsq_updateConstraint:(NSLayoutConstraint *)constraint withConstant:(CGFl [self setNeedsUpdateConstraints]; } +#pragma mark - UIResponder + +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (BOOL)becomeFirstResponder +{ + return [super becomeFirstResponder]; +} + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +{ + return (action == @selector(copy:)); +} + +- (void)copy:(id)sender +{ + [self.delegate messagesCollectionViewCellDidRequestCopy:self]; + [self resignFirstResponder]; +} + #pragma mark - Gesture recognizers - (void)jsq_handleTapGesture:(UITapGestureRecognizer *)tap diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib index 29ed4cd6f..3727e044c 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncoming.xib @@ -75,12 +75,22 @@ + + + + + + + + + + @@ -98,6 +108,7 @@ + diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.h b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.h new file mode 100644 index 000000000..252b771ae --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.h @@ -0,0 +1,16 @@ +// +// JSQMessagesCollectionViewCellIncomingMedia.h +// JSQMessages +// +// Created by Pierluigi Cifani on 17/6/14. +// Copyright (c) 2014 Hexed Bits. All rights reserved. +// + +#import "JSQMessagesCollectionViewCellIncoming.h" +#import "JSQMessagesMediaHandler.h" + +@interface JSQMessagesCollectionViewCellIncomingMedia : JSQMessagesCollectionViewCellIncoming + +@property (nonatomic) JSQMessagesMediaHandler *mediaHandler; + +@end diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.m b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.m new file mode 100644 index 000000000..c942b6fc8 --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.m @@ -0,0 +1,42 @@ +// +// JSQMessagesCollectionViewCellIncomingMedia.m +// JSQMessages +// +// Created by Pierluigi Cifani on 17/6/14. +// Copyright (c) 2014 Hexed Bits. All rights reserved. +// + +#import "JSQMessagesCollectionViewCellIncomingMedia.h" + +@implementation JSQMessagesCollectionViewCellIncomingMedia + ++ (UINib *)nib +{ + return [UINib nibWithNibName:NSStringFromClass([JSQMessagesCollectionViewCellIncomingMedia class]) + bundle:[NSBundle mainBundle]]; +} + ++ (NSString *)cellReuseIdentifier; +{ + return NSStringFromClass([JSQMessagesCollectionViewCellIncomingMedia class]); +} + +#pragma mark - Initialization + +- (void) awakeFromNib +{ + [super awakeFromNib]; + + self.mediaHandler = [JSQMessagesMediaHandler mediaHandlerWithCell:self]; +} + +#pragma mark - UITableViewCell + +-(void)prepareForReuse +{ + [super prepareForReuse]; + + [self.mediaHandler cellWillBeReused]; +} + +@end diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.xib b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.xib new file mode 100644 index 000000000..518f0bc2a --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellIncomingMedia.xib @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib index c964eee58..a6fc7c80e 100644 --- a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoing.xib @@ -6,7 +6,7 @@ - + @@ -75,6 +75,14 @@ + + + + + + + + @@ -84,6 +92,7 @@ + @@ -95,9 +104,11 @@ + + diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.h b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.h new file mode 100644 index 000000000..5e1692bd8 --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.h @@ -0,0 +1,16 @@ +// +// JSQMessagesCollectionViewCellOutgoingMedia.h +// JSQMessages +// +// Created by Pierluigi Cifani on 17/6/14. +// Copyright (c) 2014 Hexed Bits. All rights reserved. +// + +#import "JSQMessagesCollectionViewCellOutgoing.h" +#import "JSQMessagesMediaHandler.h" + +@interface JSQMessagesCollectionViewCellOutgoingMedia : JSQMessagesCollectionViewCellOutgoing + +@property (nonatomic) JSQMessagesMediaHandler *mediaHandler; + +@end diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.m b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.m new file mode 100644 index 000000000..319f1ee28 --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.m @@ -0,0 +1,42 @@ +// +// JSQMessagesCollectionViewCellOutgoingMedia.m +// JSQMessages +// +// Created by Pierluigi Cifani on 17/6/14. +// Copyright (c) 2014 Hexed Bits. All rights reserved. +// + +#import "JSQMessagesCollectionViewCellOutgoingMedia.h" + +@implementation JSQMessagesCollectionViewCellOutgoingMedia + ++ (UINib *)nib +{ + return [UINib nibWithNibName:NSStringFromClass([JSQMessagesCollectionViewCellOutgoingMedia class]) + bundle:[NSBundle mainBundle]]; +} + ++ (NSString *)cellReuseIdentifier; +{ + return NSStringFromClass([JSQMessagesCollectionViewCellOutgoingMedia class]); +} + +#pragma mark - Initialization + +- (void) awakeFromNib +{ + [super awakeFromNib]; + + self.mediaHandler = [JSQMessagesMediaHandler mediaHandlerWithCell:self]; +} + +#pragma mark - UITableViewCell + +-(void)prepareForReuse +{ + [super prepareForReuse]; + + [self.mediaHandler cellWillBeReused]; +} + +@end diff --git a/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.xib b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.xib new file mode 100644 index 000000000..f83e7e625 --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesCollectionViewCellOutgoingMedia.xib @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JSQMessagesViewController/Views/JSQMessagesInputToolbar.h b/JSQMessagesViewController/Views/JSQMessagesInputToolbar.h index 5cef52bf2..9c1550f13 100644 --- a/JSQMessagesViewController/Views/JSQMessagesInputToolbar.h +++ b/JSQMessagesViewController/Views/JSQMessagesInputToolbar.h @@ -91,4 +91,10 @@ FOUNDATION_EXPORT const CGFloat kJSQMessagesInputToolbarHeightDefault; */ - (void)toggleSendButtonEnabled; + +/** + * Programatically hide the keyboard + */ +- (void) hideKeyboard; + @end diff --git a/JSQMessagesViewController/Views/JSQMessagesInputToolbar.m b/JSQMessagesViewController/Views/JSQMessagesInputToolbar.m index 52175e2c9..1ac7db6a0 100644 --- a/JSQMessagesViewController/Views/JSQMessagesInputToolbar.m +++ b/JSQMessagesViewController/Views/JSQMessagesInputToolbar.m @@ -106,6 +106,11 @@ - (void)toggleSendButtonEnabled } } +- (void) hideKeyboard; +{ + [self.contentView.textView resignFirstResponder]; +} + #pragma mark - Key-value observing - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context diff --git a/JSQMessagesViewController/Views/JSQMessagesMediaHandler.h b/JSQMessagesViewController/Views/JSQMessagesMediaHandler.h new file mode 100644 index 000000000..a4a539b14 --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesMediaHandler.h @@ -0,0 +1,22 @@ +// +// JSQMessagesMedia.h +// JSQMessages +// +// Created by Pierluigi Cifani on 17/6/14. +// Copyright (c) 2014 Hexed Bits. All rights reserved. +// + +#import + +@class JSQMessagesCollectionViewCell; + +@interface JSQMessagesMediaHandler : NSObject + ++ (instancetype)mediaHandlerWithCell:(JSQMessagesCollectionViewCell *)cell; + +- (void) setMediaFromImage:(UIImage *)image; +- (void) setMediaFromURL:(NSURL *)imageURL; + +- (void) cellWillBeReused; + +@end diff --git a/JSQMessagesViewController/Views/JSQMessagesMediaHandler.m b/JSQMessagesViewController/Views/JSQMessagesMediaHandler.m new file mode 100644 index 000000000..ca1d418c4 --- /dev/null +++ b/JSQMessagesViewController/Views/JSQMessagesMediaHandler.m @@ -0,0 +1,123 @@ +// +// JSQMessagesMedia.m +// JSQMessages +// +// Created by Pierluigi Cifani on 17/6/14. +// Copyright (c) 2014 Hexed Bits. All rights reserved. +// + +#import "JSQMessagesMediaHandler.h" +#import "JSQMessagesCollectionViewCell.h" +#import + +@interface JSQMessagesMediaHandler () + +@property (nonatomic, weak) JSQMessagesCollectionViewCell *cell; +@property (nonatomic, weak) UIActivityIndicatorView *activityIndicator; + +@end + +@implementation JSQMessagesMediaHandler + ++ (instancetype)mediaHandlerWithCell:(id)cell +{ + JSQMessagesMediaHandler *instance = [self new]; + instance.cell = cell; + return instance; +} + +-(void)setCell:(JSQMessagesCollectionViewCell *)cell +{ + _cell = cell; + + cell.mediaImageView.contentMode = UIViewContentModeScaleAspectFill; + cell.mediaImageView.clipsToBounds = YES; +} + +- (void) setMediaFromImage:(UIImage *)image; +{ + self.cell.mediaImageView.image = image; + [self maskImageViewWithBubble]; +} + +- (void) setMediaFromURL:(NSURL *)imageURL; +{ + [self addActitityIndicator]; + + __weak __typeof(self) weakSelf = self; + + [self.cell.mediaImageView setImageWithURL:imageURL + completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) { + + __typeof(self) strongSelf = weakSelf; + [strongSelf maskImageViewWithBubble]; + [strongSelf removeActitityIndicator]; + }]; +} + +- (void) cellWillBeReused; +{ + self.cell.mediaImageView.image = nil; + [self.cell.mediaImageView cancelCurrentImageLoad]; + [self removeActitityIndicator]; +} + +#pragma mark Private + +- (void) maskImageViewWithBubble +{ + /** + * For the next snippet of code to work, the mediaImageView's frame + * must be the same as the messageBubbleImageView's frame + */ + [self.cell.mediaImageView removeConstraints:self.cell.mediaImageView.constraints]; + [self.cell layoutIfNeeded]; + [self.cell.mediaImageView setFrame:self.cell.messageBubbleImageView.frame]; + + /** + * Snippet from https://github.com/SocialObjects-Software/SOMessaging + */ + CALayer *layer = self.cell.messageBubbleImageView.layer; + layer.frame = (CGRect){{0,0},self.cell.messageBubbleImageView.layer.frame.size}; + self.cell.mediaImageView.layer.mask = layer; + [self.cell.mediaImageView setNeedsDisplay]; +} + +- (void) addActitityIndicator +{ + UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + [activityIndicator setTranslatesAutoresizingMaskIntoConstraints:NO]; + + [self.cell.messageBubbleImageView addSubview:activityIndicator]; + + /** + * Center in superview + */ + UIView *superview = self.cell.messageBubbleImageView; + NSDictionary *variables = NSDictionaryOfVariableBindings(activityIndicator, superview); + NSArray *constraints = + [NSLayoutConstraint constraintsWithVisualFormat:@"V:[superview]-(<=1)-[activityIndicator]" + options: NSLayoutFormatAlignAllCenterX + metrics:nil + views:variables]; + [self.cell.contentView addConstraints:constraints]; + + constraints = + [NSLayoutConstraint constraintsWithVisualFormat:@"H:[superview]-(<=1)-[activityIndicator]" + options: NSLayoutFormatAlignAllCenterY + metrics:nil + views:variables]; + [self.cell.contentView addConstraints:constraints]; + + [activityIndicator startAnimating]; + + self.activityIndicator = activityIndicator; +} + +- (void) removeActitityIndicator +{ + [self.activityIndicator removeFromSuperview]; + self.activityIndicator = nil; +} + +@end diff --git a/Podfile b/Podfile index e6716a1fd..f3d657d6d 100644 --- a/Podfile +++ b/Podfile @@ -2,5 +2,6 @@ platform :ios, '7.0' pod 'JSQSystemSoundPlayer' pod 'OCMock' +pod 'SDWebImage' link_with 'JSQMessages' diff --git a/Podfile.lock b/Podfile.lock index 95e0131d4..cd3fc57f0 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,13 +1,18 @@ PODS: - JSQSystemSoundPlayer (1.5.1) - OCMock (2.2.4) + - SDWebImage (3.6): + - SDWebImage/Core + - SDWebImage/Core (3.6) DEPENDENCIES: - JSQSystemSoundPlayer - OCMock + - SDWebImage SPEC CHECKSUMS: JSQSystemSoundPlayer: a662f27f4ebac9f867116fc6c3926c19206c5e60 OCMock: 6db79185520e24f9f299548f2b8b07e41d881bd5 + SDWebImage: c6989652c1cdf27cbdf3f56957e2030f825af5bc COCOAPODS: 0.33.1