iOS 开发问与答(28-38)

iOS 开发问与答(28-38)28 LocalSubstit 不生效 AMapLocation 和 LocalSubstit 有冲突 无法和后者同时使用 将 AMapLocation 换成苹果的 CoreLocation 即可 29 如何将按钮图标置于按钮文本的右侧 正常情况下 按钮图片

大家好,我是讯享网,很高兴认识大家。

28.LocalSubstitionCache 不生效

AMapLocation 和 LocalSubstitutionCache 有冲突,无法和后者同时使用。将 AMapLocation 换成苹果的 CoreLocation 即可。

29.如何将按钮图标置于按钮文本的右侧?

正常情况下,按钮图片(ImageView)位于按钮文本(titleLabel)的左侧。要将二者左右交换,有以下几种方法。其中,最简单的是第一个方法:

func flipButtonTitleImage(button:UIButton){// 将按钮图标置于文本右侧 button.transform = CGAffineTransformMakeScale(-1.0, 1.0); button.titleLabel!.transform = CGAffineTransformMakeScale(-1.0, 1.0); button.imageView?.transform = CGAffineTransformMakeScale(-1.0, 1.0); }

讯享网

或者修改二者的 Insets:

讯享网thebutton.titleEdgeInsets = UIEdgeInsetsMake(0, -thebutton.imageView.frame.size.width, 0, thebutton.imageView.frame.size.width); thebutton.imageEdgeInsets = UIEdgeInsetsMake(0, thebutton.titleLabel.frame.size.width, 0, -thebutton.titleLabel.frame.size.width);

或者子类化 UIButton 并覆盖这两个方法:

@implementation UIButtonSubclass - (CGRect)imageRectForContentRect:(CGRect)contentRect { CGRect frame = [super imageRectForContentRect:contentRect]; frame.origin.x = CGRectGetMaxX(contentRect) - CGRectGetWidth(frame) - self.imageEdgeInsets.right + self.imageEdgeInsets.left; return frame; } - (CGRect)titleRectForContentRect:(CGRect)contentRect { CGRect frame = [super titleRectForContentRect:contentRect]; frame.origin.x = CGRectGetMinX(frame) - CGRectGetWidth([self imageRectForContentRect:contentRect]); return frame; } @end

当然,最简单的还是第一个方法。

30.如何定制 JSQMessagesLoadEarlierHeaderView?

默认 JSQMessagesLoadEarlierHeaderView 显示一个按钮,标题为“Load Earlier Messages”。我们可以修改这个按钮,让它显示“加载更多历史消息”,如下图所示。

讯享网
在 JSQMessagesViewController 子类中实现这个方法:

讯享网// 数据源方法:设置除了 cell 之外的 View,比如 HeaderView - (UICollectionReusableView *)collectionView:(JSQMessagesCollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { if (self.showLoadEarlierMessagesHeader && [kind isEqualToString:UICollectionElementKindSectionHeader]) { 
  
    
  // 如果是 JSQMessagesLoadEarlierHeaderView JSQMessagesLoadEarlierHeaderView *header = [collectionView dequeueLoadEarlierMessagesViewHeaderForIndexPath:indexPath]; // Customize header [header.loadButton setTitle:@"加载更多历史消息" forState:UIControlStateNormal]; return header; } return [super collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; }

31.如何定制 JSQMessagesToolbarContentView

在左边 voice 按钮前加入一个 add 按钮:

UIButton *btn=[UIButton buttonWithType:UIButtonTypeContactAdd]; JSQMessagesToolbarContentView *contentView= self.inputToolbar.contentView; [contentView.leftBarButtonContainerView addSubview:btn];//add the new button contentView.leftBarButtonItemWidth=66; //make containerView wider to hold more buttons //remove unused leftBarButtonItem constraint for (NSLayoutConstraint *constraint in contentView.leftBarButtonItem.superview.constraints) { if(constraint.secondItem ==contentView.leftBarButtonItem &&constraint.firstAttribute ==NSLayoutAttributeLeading){ constraint.active=NO; } } // update two button constraint [contentView.leftBarButtonItem mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(@30); }]; [btn mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(btn.superview.mas_top); make.bottom.equalTo(btn.superview.mas_bottom); make.left.equalTo(btn.mas_left); make.width.equalTo(@30); }];


在右边 send 按钮前加入一个 add 按钮:

讯享网-(void)customInputToolbar{ UIButton *btn=[UIButton buttonWithType:UIButtonTypeContactAdd]; JSQMessagesToolbarContentView *contentView= self.inputToolbar.contentView; [contentView.rightBarButtonContainerView addSubview:btn];//add the new button contentView.rightBarButtonItemWidth=76; //make containerView wider to hold more buttons //remove unused rightBarButtonItem constraint for (NSLayoutConstraint *constraint in contentView.rightBarButtonItem.superview.constraints) { if(constraint.secondItem ==contentView.rightBarButtonItem &&constraint.firstAttribute ==NSLayoutAttributeLeading){ constraint.active=NO; } } // update two button constraint [contentView.rightBarButtonItem mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(@40); }]; [btn mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(btn.superview.mas_top); make.bottom.equalTo(btn.superview.mas_bottom); make.right.equalTo(contentView.rightBarButtonItem.mas_left); make.width.equalTo(@30); }]; [btn addTarget:self action:@selector(addAction:) forControlEvents:UIControlEventTouchUpInside]; // 设置 voice 按钮和 send 按钮 [contentView.leftBarButtonItem setImage:[UIImage imageNamed:@"chat_bottom_voice_press"] forState:UIControlStateNormal]; [contentView.leftBarButtonItem setImage:[UIImage imageNamed:@"chat_bottom_voice_nor"] forState:UIControlStateHighlighted]; [contentView.rightBarButtonItem setTitle:@"发送" forState:UIControlStateNormal]; }

32.如何判断 TextView 中回车键按下?

如果是 UITextField,可以用 textFieldShouldReturn(_) 委托方法。但 UITextView 中却没有类似的委托方法,因此只能用 textView(_, shouldChangeTextInRange:, replacementText:) 方法替代:

func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool { if text == "\n" { if !textView.text.isEmpty { textView.resignFirstResponder() ... ... // 回车键按下,进行相应处理 } return false; } return true; }

O-C 代码:

讯享网- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { if([text isEqualToString:@"\n"]) { if( [textView.text length]>0){ [textView resignFirstResponder]; ... ... // 回车键按下,进行相应处理 } return NO; } return YES; }

33. 如何检测 UITextView 的 contentSize 被改变?

[self.inputToolbar.contentView.textView addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];

然后实现 observeValueForKeyPath 方法,检测 contentSize 变化:

讯享网- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == self.inputToolbar.contentView.textView && [keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) { CGSize oldContentSize = [[change objectForKey:NSKeyValueChangeOldKey] CGSizeValue]; CGSize newContentSize = [[change objectForKey:NSKeyValueChangeNewKey] CGSizeValue]; CGFloat dy = newContentSize.height - oldContentSize.height; [self jsq_adjustInputToolbarForComposerTextViewContentSizeChange:dy]; [self jsq_updateCollectionViewInsets]; if (self.automaticallyScrollsToMostRecentMessage) { [self scrollToBottomAnimated:NO]; } } }

但是,UIKit 中的 KVO 兼容并不是非常可靠,特别在 iOS 9 中。替代的方案是子类化 UITextView 并覆盖 setContentSize 方法。

34. 第三方库编译出现错误“Too many arguments to function call,expected 0,have 3”

例如,编译 nimbus 库时, NIButtonUtilities.m 中的 method(: : :)方法调用出现编译错误:Too many arguments to function call,expected 0,have 3

根据 2014 WWDC 视频中 Session 417 “What’s New in LLVM” 所述,编译器会强制要求对 objc_msgSend 使用强类型声明。例如:

typedef void (*send_type)(void*, SEL, void*); send_type func = (send_type)objc_msgSend; func(anItem.callback_object, NSSelectorFromString(anItem.selector), dict);

注意 sent_type 的使用。现在 objc_msgSend 变成了一个强类型。如果是调用实例方法,也是同样:

讯享网typedef void (*send_type)(void*, SEL, void*); send_type methodInstance = (send_type)[SomeClass instanceMethodForSelector:someSelector]; methodInstance(self, someSelector, someArgument);

35. 在 lldb 中如何打印 indexPath.row?

当我们使用 lldb 命令 p indexPath.row 时,总是报错:error: property ‘row’ not found on object of type ‘NSIndexPath *’

其实,只需要换成如下命令即可:

(lldb) p (NSInteger)[indexPath section] (NSInteger) $15 = 0

36. 如何用 CocoaHttpServer 实现一个 https 服务器?

继承 HTTPConnection 类:

讯享网@interface MyHTTPConnection : HTTPConnection

HTTPConnection 子类中,实现 - (BOOL)isSecureServer 方法并返回 YES,表示支持 https。

制作一个 SSL 自签名证书(见问题 37),安装到钥匙串,然后导出为 .p12 文件,记住导出密码。

制作证书时需要注意 /CN 参数必须指定主机名,比如 /CN 参数指定为 127.0.0.1,则访问时只能用 https://127.0.0.1,而不能用 https://localhost 替代。

实现 sslIdentityAndCertificates 方法:

- (NSArray *)sslIdentityAndCertificates { SecIdentityRef identityRef = NULL; SecCertificateRef certificateRef = NULL; SecTrustRef trustRef = NULL; NSString *thePath = [[NSBundle mainBundle] pathForResource:@"127.0.0.1" ofType:@"p12"]; NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath]; CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data; CFStringRef password = CFSTR("你导出 .p12 的密码"); const void *keys[] = { kSecImportExportPassphrase }; const void *values[] = { password }; CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); OSStatus securityError = errSecSuccess; securityError = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items); if (securityError == 0) { CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0); const void *tempIdentity = NULL; tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity); identityRef = (SecIdentityRef)tempIdentity; const void *tempTrust = NULL; tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust); trustRef = (SecTrustRef)tempTrust; } else { NSLog(@"Failed with error code %d",(int)securityError); return nil; } SecIdentityCopyCertificate(identityRef, &certificateRef); NSArray *result = [[NSArray alloc] initWithObjects:(__bridge id)identityRef, (__bridge id)certificateRef, nil]; return result; }
讯享网[settings setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(NSString *)kCFStreamSSLLevel]

替换为:

[settings setObject:[NSNumber numberWithInteger:2] forKey:GCDAsyncSocketSSLProtocolVersionMin]; [settings setObject:[NSNumber numberWithInteger:2] forKey:GCDAsyncSocketSSLProtocolVersionMax];

在启动 HTTPServer (调用 startServer )之前,使用 setConnectionClass 方法将 HTTPConnecdtion 替换为 MyHTTPConnection:

讯享网[httpServer setConnectionClass:[MyHTTPConnection class]];

37 如何制作 SSL 自签名证书?

1. CA 私钥 openssl genrsa -out myCA.key 2048 2. CA 证书 openssl req -x509 -new -key myCA.key -out myCA.cer -days 35600 -subj /CN="Yunnan Yuan Xin" 3. SSL 证书私钥 openssl genrsa -out myserver.key 2048 4. CSR openssl req -new -out sslcert.req -key myserver.key -subj /CN=127.0.0.1 5. ssl 证书 openssl x509 -req -in sslcert.req -out sslcert.cer -CAkey myCA.key -CA myCA.cer -days 36500 -CAcreateserial -CAserial serial 6. 合并私钥及证书 openssl pkcs12 -export -out sslcert.pfx -inkey myserver.key -in sslcert.cer

38. 实现本地 HTTPs 服务器后,在 iOS 9 上无法访问

讯享网 [settings setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(NSString *)kCFStreamSSLLevel];

替换为:

[settings setObject:[NSNumber numberWithInteger:kTLSProtocol12] forKey:GCDAsyncSocketSSLProtocolVersionMin]; [settings setObject:[NSNumber numberWithInteger:kTLSProtocol12] forKey:GCDAsyncSocketSSLProtocolVersionMax];

或者在 Info.plist 中设置:

讯享网<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> <key>localhost</key> <dict> <key>NSIncludesSubdomains</key> <true/> <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> <true/> <key>NSTemporaryExceptionMinimumTLSVersion</key> <string>TLSv1.0</string> <key>NSTemporaryExceptionRequiresForwardSecrecy</key> <false/> <key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key> <false/> <key>NSThirdPartyExceptionRequiresForwardSecrecy</key> <true/> <key>NSThirdPartyExceptionMinimumTLSVersion</key> <string>服务器的TSL/SSL协议版本</string><!-- 或者 SSLv3.0、TLSv1.0、TLSv1.1 等--> <key>NSRequiresCertificateTransparency</key> <false/> </dict> </dict>

注意,这里“服务器的TSL/SSL协议版本”要替换成服务器真正的 TSL/SSL 版本,比如 SSLv3.0、TLSv1.0、TLSv1.1。

小讯
上一篇 2025-04-08 09:20
下一篇 2025-03-20 11:13

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/30010.html