分类存档: OS X & iOS - 第2页

给 iOS 项目添加自动更新属性

在 iOS 项目中,有些时候为了方便跟踪发布时的版本,方便调试,或者为了展示程序的编译日期,可以通过在 Info.plist 里面添加自定义属性的方式来实现,但是这个工作是一个纯粹机械的重复劳动,每次手工去更新是比较麻烦的一个事情。

好在 Xcode 项目支持自定义脚本的编译步骤,我们可以通过在编译过程中使用自定义脚本去更新 Info.plist。

在 /usr/libexec 目录下面,有一个工具 PlistBuddy,它可以很方便地修改 plist 文件,而不需要去用 sed 之类的编辑工具来修改。

为了在使用 PlistBuddy 的时候可以直接用 Set 命令设置属性,我们先在 iOS 项目的 Info.plist 里面加一个需要自动更新的属性,例如 BUILD_DATE 和 GIT_REVISION,类型选择为字符串,值填 AUTO_GENERATED。

然后在 iOS 项目的 Target 上添加一个新的编译步骤 Run Script Build Phase:

下页就是要编写脚本了

# 获取编译的日期
DATE=`date +%Y.%m.%d`
 
# 获取编译时 Git 库的短版本号
GIT_REVISION=`git rev-parse --short HEAD`
 
# 设置 Info.plist 中相关的属性
/usr/libexec/PlistBuddy "$TARGET_BUILD_DIR/$INFOPLIST_PATH" -c "Set BUILD_DATE $DATE"
/usr/libexec/PlistBuddy "$TARGET_BUILD_DIR/$INFOPLIST_PATH" -c "Set GIT_REVISION $GIT_REVISION"
 
# 将 Info.plist 转换为二进制格式
plutil -convert binary1 "$TARGET_BUILD_DIR/$INFOPLIST_PATH"

如果需要在程序里获取这些属性,那么就可以用下面的方法:

NSString *buildDate = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"BUILD_DATE"];
NSString *revision = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"GIT_REVISION"];

— EOF —

[Cocoa学习]格式化日期和解析日期字符串

在应用程序中,显示消息的时候一般都需要附带一个消息接收的时间,使用 NSDate 和 NSDateFormatter 即可完成格式化输出日期的任务,但是在使用时会比较麻烦,通过 Category 的方式来给 NSDate 类直接添加两个类方法来做这些事情会比较好,在以后需要格式化字符串时不需要每次都去创建一个 NSDateFormatter 对象。

对于解析日期时间也是同理。

@interface NSDate (FormatString)
 
- (NSString*)stringWithFormat:(NSString*)fmt;
+ (NSDate*)dateFromString:(NSString*)str withFormat:(NSString*)fmt;
 
@end
 
@implementation NSDate (FormatString)
 
- (NSString*)stringWithFormat:(NSString*)fmt {
    static NSDateFormatter *fmtter;
 
    if (fmtter == nil) {
        fmtter = [[NSDateFormatter alloc] init];
    }
 
    if (fmt == nil || [fmt isEqualToString:@""]) {
        fmt = @"HH:mm:ss";
    }
 
    [fmtter setDateFormat:fmt];
 
    return [fmtter stringFromDate:self];
}
 
+ (NSDate*)dateFromString:(NSString*)str withFormat:(NSString*)fmt {
    static NSDateFormatter *fmtter;
 
    if (fmtter == nil) {
        fmtter = [[NSDateFormatter alloc] init];
    }
 
    if (fmt == nil || [fmt isEqualToString:@""]) {
        fmt = @"HH:mm:ss";
    }
 
    [fmtter setDateFormat:fmt];
 
    return [fmtter dateFromString:str];
}
 
@end

在这里,dataFromString 和 stringWithFormat 如果没有指定格式字符串参数的话,默认会使用 HH:mm:ss 来格式日期,当然,如果实际应用场景不一样,这里也可以修改。

如果需要更强大的功能,也可以去修改两个方法来符合自己的需要。

// 获取当前时间的字符串
NSString *now = [[NSDate date] stringWithFormat:@"HH:mm:ss"];
 
// 将一个字符串解析成 NSDate 对象
NSStirng *recvTime = @"2010-05-25 16:58:34 +0800";
NSDate  *recv = [NSDate dateFromString:recvTime withFormat:@"yyyy-MM-dd HH:mm:ss Z"];

使用 Category 给 NSDate 类添加这两个方法后,再要格式化日期或者解析日期时间字符串,就不需要额外去创建 NSDateFormatter 对象了,可以省不少力气。

参考资料

[Cocoa学习] 限制 NSTextField 中输入文本的长度

需求

NSTextField 作为一个常用的输入控件,有些在使用时需要去限制其中可以输入文本的长度,而 NSTextField 本身并没有提供这个功能,这就需要我们自己去想办法来实现了。

方法

在网上找了一下,发现可以通过继承 NSFormatter 实现一个子类来实现这个功能,参考了一下找到的代码[1],再将主要的 isPartialStringValid 方法根据自己的需要改了一下。

 
- (BOOL)isPartialStringValid:(NSString **)partialStringPtr
       proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
              originalString:(NSString *)origString
       originalSelectedRange:(NSRange)origSelRange
            errorDescription:(NSString **)error 
{
    int size = [*partialStringPtr length];
    if ( size > maxLength ) {
        if (origSelRange.location == [origString length]) {
            // 如果修改的位置在原来字符串的最后,则不做修改,只是拒绝内容修改
        } else {
            // 如果修改的位置在原来字符串的中间,就根据剩余的可用的长度把新增加的字符串进行截取
            int preLen = origSelRange.location + (maxLength - [origString length]) + origSelRange.length;
            *partialStringPtr = [NSString stringWithFormat:@"%@%@",
                                 [*partialStringPtr substringToIndex:preLen],
                                 [origString substringFromIndex:origSelRange.location+origSelRange.length]];
 
            (*proposedSelRangePtr).location = preLen;
        }
 
        return NO;
    }
    return YES;
}

参考资料

在 REALbasic 中注册 AppleEvent

之前为了注册一个自定义协议,需要通过注册 AppleEvent 来实现,在 Objective-C 中,可以很方便的使用 NSAppleEventManager 来注册 AppleEvent 句柄,但是在 REALbaisc 中,是没有办法直接去调用 NSAppleEventManager 的,所以需要通过声明然后调用 C API 来实现相应的功能。

与 NSAppleEventManager 中功能相对应的 C API 有 AEInstallEventHandler, NewAEEventHandlerUPP 等,通过这些 API 我们也可以在 REALbasic 中来注册 AppleEvent 了,再配合 Info.plist 中的 URLScheme 声明,即可实现 URL 自定义协议处理句柄。

#if TargetCarbon
    soft declare function AEInstallEventHandler Lib CarbonLib ( _
    theAEEventClass as Integer, _
    theAEEventID as Integer, _
    handler as Integer, _
    handlerRefcon as Integer, _
    isSysHandler as Boolean) as Integer
 
    Soft Declare Function NewAEEventHandlerUPP Lib CarbonLib (userRoutine as Ptr) as Integer
 
    Static CallbackUPP as Integer = 0
    If CallbackUPP = 0 then
      dim m as MemoryBlock =  AddressOf ForwardCarbonAEEventToObject
      If m is nil then
        Return
      End if
      CallbackUPP = NewAEEventHandlerUPP(m)
    End if
 
    dim v as Variant = me
 
    dim OSError as Integer = AEInstallEventHandler( _
    OSTypeToUInt(kInternetEventClass), _
    OSTypeToUInt(kAEGetURL), _
    CallbackUPP, _
    v.Hash, false)
 
    msgbox str(OSError)
#endif

先使用 NewAEEventHandlerUPP 来生成一个 AppleEvent 回调函数的句柄,然后调用 AEInstallEventHandler 来注册一个共享函数 ForwardCarbonAEEventToObject 为 AppleEvent 事件处理句柄。

AEInstallEventHandler 所需的 AEEventClass 和 AEEventID 都是一个 4 字节的整型,但是通常我们在调用的时候,是用的一个 4 字符的字符串,因此需要一个函数来将 4 字符转换为 4 字节的整形。

// code from ToolbarSearchField by The ZAZ Studios
// http://www.thezaz.com/opensource/realbasic/macosx/searchfield/
static m as new MemoryBlock(4)
m.LittleEndian = false
m.StringValue(0, 4) = s
return m.UInt32Value(0)

在 ForwardCartonAEEventToObject 里,参数 theEvent 和 replyEvent 都量个整形,为了从这两个参数里拿到数据,还需要使用 AEGetParamPtr 来从 AppleEvent 中拿到数据。

soft declare function AEGetParamPtr lib CarbonLib ( _
    theAppleEvent as Integer, theAEKeyword as Integer, _
    desiredType as Integer, byref actualType as Integer, _
    dataPtr as Ptr, maximumSize as Integer, _
    byref actualSize as Integer) as Integer

当然还有一系列的 AEGetDataDesc、AEGetDescSize 等函数可以,具体可以查 Xocde 随带的库文档。

关于注册自定义协议,可以参考这篇文章

通过 Core Foundation 中的一些 C API,在 REALbasic 也可以完成一些平台相关的工作,虽然麻烦了些:)