在本文中,我们将为您详细介绍Swift4Codable-Bool或String值的相关知识,并且为您解答关于swiftrangeofstring的疑问,此外,我们还会提供一些关于com.faceboo
在本文中,我们将为您详细介绍Swift 4 Codable-Bool或String值的相关知识,并且为您解答关于swift rangeofstring的疑问,此外,我们还会提供一些关于com.facebook.swift.codec.ThriftConstructor的实例源码、iOS swift NSMutableData没有成员appendString、ios – Swift:Codable – 提取单个编码密钥、iOS – 如何在swift中使用`NSMutableString`的有用信息。
本文目录一览:- Swift 4 Codable-Bool或String值(swift rangeofstring)
- com.facebook.swift.codec.ThriftConstructor的实例源码
- iOS swift NSMutableData没有成员appendString
- ios – Swift:Codable – 提取单个编码密钥
- iOS – 如何在swift中使用`NSMutableString`
Swift 4 Codable-Bool或String值(swift rangeofstring)
寻找一些有关如何处理我最近遇到的情况的意见。
我一直在使用Swift 4s Codable取得成功,但今天发现了我没有想到的崩溃。我正在使用的API说,它boolean
为key 返回amanage_stock
。
我的存根结构如下:
struct Product: Codable { var manage_stock: Bool?}
效果很好,问题在于API 有时会 返回a string
而不是a boolean
。
因此,我的解码失败并显示:
Expected to decode Bool but found a string/data instead.
该字符串只能等于"parent"
,我希望它等于false
。
我也可以将结构更改为var manage_stock:String?
是否可以简化从API导入JSON数据的过程。但是,当然,如果我更改了此设置,我的错误将变为:
Expected to decode String but found a number instead.
有没有一种简单的方法来处理这种突变,或者我需要失去所有Codable
带来表的自动化并实现我自己的自动化init(decoder: Decoder)
。
干杯
答案1
小编典典由于您无法始终控制要使用的API,因此这是一种Codable
通过重写直接解决此问题的简单方法init(from:)
:
struct Product : Decodable { // Properties in Swift are camelCased. You can provide the key separately from the property. var manageStock: Bool? private enum CodingKeys : String, CodingKey { case manageStock = "manage_stock" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) do { self.manageStock = try container.decodeIfPresent(Bool.self, forKey: .manageStock) } catch DecodingError.typeMismatch { // There was something for the "manage_stock" key, but it wasn''t a boolean value. Try a string. if let string = try container.decodeIfPresent(String.self, forKey: .manageStock) { // Can check for "parent" specifically if you want. self.manageStock = false } } }}
有关更多信息,请参见编码和解码自定义类型。
com.facebook.swift.codec.ThriftConstructor的实例源码
/** * Constructor * @param length the number of bytes the file has * @param isdir if the path is a directory * @param block_replication the replication factor * @param blocksize the block size * @param modification_time modification time * @param access_time access time * @param permission permission * @param owner the owner of the path * @param group the group of the path * @param path the local name in java UTF8 encoding the same as that in-memory */ @ThriftConstructor public HdfsFileStatus(@ThriftField(1) long length,@ThriftField(2) boolean isdir,@ThriftField(3) int block_replication,@ThriftField(4) long blocksize,@ThriftField(5) long modification_time,@ThriftField(6) long access_time,@ThriftField(7) FsPermission permission,@ThriftField(8) String owner,@ThriftField(9) String group,@ThriftField(10) byte[] path) { this.length = length; this.isdir = isdir; this.block_replication = (short)block_replication; this.blocksize = blocksize; this.modification_time = modification_time; this.access_time = access_time; this.permission = (permission == null) ? FsPermission.getDefault() : permission; this.owner = (owner == null) ? "" : owner; this.group = (group == null) ? "" : group; this.path = path; }
@ThriftConstructor // Last ThriftField used in superclass: 4 public DatanodeInfo(@ThriftField(1) String name,@ThriftField(2) String storageID,@ThriftField(3) int infoPort,@ThriftField(4) int ipcPort,@ThriftField(6) long capacity,@ThriftField(7) long dfsUsed,@ThriftField(8) long remaining,@ThriftField(9) long namespaceUsed,@ThriftField(10) long lastUpdate,@ThriftField(11) int xceiverCount,@ThriftField(12) String networkLocation,@ThriftField(13) String hostName,@ThriftField(14) int adminStateOrdinal) { super(name,storageID,infoPort,ipcPort); this.capacity = capacity; this.dfsUsed = dfsUsed; this.remaining = remaining; this.namespaceUsed = namespaceUsed; this.lastUpdate = lastUpdate; this.xceiverCount = xceiverCount; this.location = networkLocation; this.hostName = hostName; setAdminState(AdminStates.values()[adminStateOrdinal]); }
@ThriftConstructor public Index( @ThriftField(value = 1,name = "indexName") final String indexName,@ThriftField(value = 2,name = "indexHandlerClass") final String indexHandlerClass,@ThriftField(value = 3,name = "dbname") final String dbname,@ThriftField(value = 4,name = "origTableName") final String origTableName,@ThriftField(value = 5,name = "createTime") final int createTime,@ThriftField(value = 6,name = "lastAccesstime") final int lastAccesstime,@ThriftField(value = 7,name = "indexTableName") final String indexTableName,@ThriftField(value = 8,name = "sd") final StorageDescriptor sd,@ThriftField(value = 9,name = "parameters") final Map<String,String> parameters,@ThriftField(value = 10,name = "deferredRebuild") final boolean deferredRebuild) { this.indexName = indexName; this.indexHandlerClass = indexHandlerClass; this.dbname = dbname; this.origTableName = origTableName; this.createTime = createTime; this.lastAccesstime = lastAccesstime; this.indexTableName = indexTableName; this.sd = sd; this.parameters = parameters; this.deferredRebuild = deferredRebuild; }
@ThriftConstructor public Partition( @ThriftField(value = 1,name = "values") final List<String> values,name = "tableName") final String tableName,name = "privileges") final PrincipalPrivilegeSet privileges,name = "linkTarget") final PartitionIdentifier linkTarget,name = "linkPartitions") final List<PartitionIdentifier> linkPartitions) { this.values = values; this.dbname = dbname; this.tableName = tableName; this.createTime = createTime; this.lastAccesstime = lastAccesstime; this.sd = sd; this.parameters = parameters; this.privileges = privileges; this.linkTarget = linkTarget; this.linkPartitions = linkPartitions; }
/** Constructor */ @ThriftConstructor public ContentSummary( @ThriftField(1) long length,@ThriftField(2) long fileCount,@ThriftField(3) long directoryCount,@ThriftField(4) long quota,@ThriftField(5) long spaceConsumed,@ThriftField(6) long spaceQuota) { this.length = length; this.fileCount = fileCount; this.directoryCount = directoryCount; this.quota = quota; this.spaceConsumed = spaceConsumed; this.spaceQuota = spaceQuota; }
/** * Create DatanodeID * @param name (hostname:portNumber) * @param storageID data storage ID * @param infoPort info server port * @param ipcPort ipc server port */ @ThriftConstructor public DatanodeID(@ThriftField(1) String name,@ThriftField(4) int ipcPort) { this.name = name; this.storageID = storageID; this.infoPort = infoPort; this.ipcPort = ipcPort; }
@ThriftConstructor public VersionedLocatedBlock(@ThriftField(1) Block block,@ThriftField(2) List<DatanodeInfo> datanodes,@ThriftField(3) long startOffset,@ThriftField(4) boolean corrupt,@ThriftField(5) int dataProtocolVersion) { super(block,datanodes,startOffset,corrupt); this.dataProtocolVersion = dataProtocolVersion; }
@ThriftConstructor public LocatedBlocks(@ThriftField(1) long fileLength,@ThriftField(2) List<LocatedBlock> locatedBlocks,@ThriftField(3) boolean isUnderConstuction) { this.fileLength = fileLength; blocks = locatedBlocks; underConstruction = isUnderConstuction; }
@ThriftConstructor public LocatedBlockWithMetaInfo(@ThriftField(1) Block block,@ThriftField(5) int dataProtocolVersion,@ThriftField(6) int namespaceId,@ThriftField(7) int methodFingerPrint) { super(block,corrupt,dataProtocolVersion); this.namespaceid = namespaceId; this.methodFingerPrint = methodFingerPrint; }
@ThriftConstructor public LocatedBlockWithFileName(@ThriftField(1) Block block,@ThriftField(5) String fileName) { super(block,corrupt); this.fullPath = fileName; }
@ThriftConstructor public LocatedBlocksWithMetaInfo(@ThriftField(1) long fileLength,@ThriftField(3) boolean isUnderConstuction,@ThriftField(4) int dataProtocolVersion,@ThriftField(5) int namespaceId,@ThriftField(6) int methodFingerPrint) { super(fileLength,locatedBlocks,isUnderConstuction,dataProtocolVersion); this.namespaceid = namespaceId; this.methodFingerPrint = methodFingerPrint; }
@ThriftConstructor public LocatedBlockWithOldGS(@ThriftField(1) Block block,@ThriftField(7) int methodFingerPrint,@ThriftField(8) long oldGenerationStamp) { super(block,dataProtocolVersion,namespaceId,methodFingerPrint); this.oldGenerationStamp = oldGenerationStamp; }
@ThriftConstructor public VersionedLocatedBlocks(@ThriftField(1) long fileLength,@ThriftField(4) int dataProtocolVersion) { super(fileLength,isUnderConstuction); this.dataProtocolVersion = dataProtocolVersion; }
@ThriftConstructor public RequestMetaInfo(int clusterId,String resourceId,int namespaceId,int applicationId,UnixUserGroupinformation origCaller) { this.clusterId = clusterId; this.resourceId = resourceId; this.namespaceId = namespaceId; this.applicationId = applicationId; this.origCaller = origCaller; }
@ThriftConstructor public GetBlockLocationsRequest(@ThriftField(1) RequestMetaInfo requestMetaInfo,@ThriftField(2) String src,@ThriftField(3) long offset,@ThriftField(4) long length) { super(requestMetaInfo,src); this.offset = offset; this.length = length; }
@ThriftConstructor public OpenRequest(@ThriftField(1) RequestMetaInfo requestMetaInfo,src); this.offset = offset; this.length = length; }
@ThriftConstructor public CreateRequest(@ThriftField(1) RequestMetaInfo requestMetaInfo,@ThriftField(3) String clientName,@ThriftField(4) @NotNull FsPermission masked,@ThriftField(5) boolean overwrite,@ThriftField(6) boolean createParent,@ThriftField(7) short replication,@ThriftField(8) long blockSize) { super(requestMetaInfo,src,clientName); notNull(masked); this.masked = masked; this.overwrite = overwrite; this.createParent = createParent; this.replication = replication; this.blockSize = blockSize; }
@ThriftConstructor public CloseRecoverLeaseRequest(@ThriftField(1) RequestMetaInfo requestMetaInfo,@ThriftField(4) boolean discardLastBlock) { super(requestMetaInfo,clientName); this.discardLastBlock = discardLastBlock; }
@ThriftConstructor public SetPermissionRequest(@ThriftField(1) RequestMetaInfo requestMetaInfo,@ThriftField(3) @NotNull FsPermission permission) { super(requestMetaInfo,src); notNull(permission); this.permission = permission; }
@ThriftConstructor public SetownerRequest(@ThriftField(1) RequestMetaInfo requestMetaInfo,@ThriftField(3) String username,@ThriftField(4) String groupname) { super(requestMetaInfo,src); this.username = username; this.groupname = groupname; }
@ThriftConstructor public AbandonBlockRequest(@ThriftField(1) RequestMetaInfo requestMetaInfo,@ThriftField(4) Block block) { super(requestMetaInfo,clientName); this.block = block; }
@ThriftConstructor public AddBlockRequest(RequestMetaInfo requestMetaInfo,String src,String clientName,List<DatanodeInfo> excludednodes,List<DatanodeInfo> favorednodes,long startPos,Block lastBlock) { super(requestMetaInfo,clientName); this.excludednodes = excludednodes; this.favorednodes = favorednodes; this.startPos = startPos; this.lastBlock = lastBlock; }
@ThriftConstructor public CompleteRequest(RequestMetaInfo requestMetaInfo,long fileLen,clientName); this.fileLen = fileLen; this.lastBlock = lastBlock; }
@ThriftConstructor public ConcatRequest(RequestMetaInfo requestMetaInfo,String trg,List<String> srcs,boolean restricted) { super(requestMetaInfo); this.trg = trg; this.srcs = srcs; this.restricted = restricted; }
@ThriftConstructor public MkdirsRequest(RequestMetaInfo requestMetaInfo,@NotNull FsPermission masked) { super(requestMetaInfo,src); notNull(masked); this.masked = masked; }
@ThriftConstructor public IterativeGetopenFilesRequest(RequestMetaInfo requestMetaInfo,int millis,String start) { super(requestMetaInfo,src); this.millis = millis; this.start = start; }
@ThriftConstructor public UpdatePipelineRequest(RequestMetaInfo requestMetaInfo,Block oldBlock,Block newBlock,List<DatanodeID> newNodes) { super(requestMetaInfo,clientName); this.oldBlock = oldBlock; this.newBlock = newBlock; this.newNodes = newNodes; }
@ThriftConstructor public RaidFileRequest(RequestMetaInfo requestMetaInfo,String codecId,short expectedSourceReplication) { super(requestMetaInfo,src); this.codecId = codecId; this.expectedSourceReplication = expectedSourceReplication; }
/** * Creates a new {@link PageRequest}. Pages are zero indexed,thus providing 0 for {@code page} will return * the first page. * * @param page * must not be less than zero. * @param size * must not be less than one. */ @ThriftConstructor public PageRequest(@ThriftField(value = 1,name = "pageNumber") int page,name = "pageSize") int size) { if (page < 0) { throw new IllegalArgumentException("Page index must not be less than zero!"); } if (size < 1) { throw new IllegalArgumentException("Page size must not be less than one!"); } this.pageNumber = page; this.pageSize = size; }
@ThriftConstructor public ValidationException(int errorCode,String field,String message) { super(message); this.errorCode = errorCode; this.message = message; this.field = field; }
@ThriftConstructor public StorageDescriptor( @ThriftField(value = 1,name = "cols") final List<FieldSchema> cols,name = "location") final String location,name = "inputFormat") final String inputFormat,name = "outputFormat") final String outputFormat,name = "compressed") final boolean compressed,name = "numBuckets") final int numBuckets,name = "serdeInfo") final SerDeInfo serdeInfo,name = "bucketCols") final List<String> bucketCols,name = "sortCols") final List<Order> sortCols,@ThriftField(value = 11,name = "skewedInfo") final SkewedInfo skewedInfo,@ThriftField(value = 12,name = "storedAsSubDirectories") final boolean storedAsSubDirectories,@ThriftField(value = 13,name = "statsFresh") final boolean statsFresh) { this.cols = cols; this.location = location; this.inputFormat = inputFormat; this.outputFormat = outputFormat; this.compressed = compressed; this.numBuckets = numBuckets; this.serdeInfo = serdeInfo; this.bucketCols = bucketCols; this.sortCols = sortCols; this.parameters = parameters; this.skewedInfo = skewedInfo; this.storedAsSubDirectories = storedAsSubDirectories; this.statsFresh = statsFresh; }
@ThriftConstructor public PartitionIdentifier( @ThriftField(value = 1,name = "values") final List<String> values) { this.dbname = dbname; this.tableName = tableName; this.values = values; }
@ThriftConstructor public Order( @ThriftField(value = 1,name = "col") final String col,name = "order") final int order) { this.col = col; this.order = order; }
@ThriftConstructor public ColumnStatistics( @ThriftField(value = 1,name = "statsDesc") final ColumnStatisticsDesc statsDesc,name = "statsObj") final List<ColumnStatisticsObj> statsObj) { this.statsDesc = statsDesc; this.statsObj = statsObj; }
@ThriftConstructor public BooleanColumnStatsData( @ThriftField(value = 1,name = "numTrues") final long numTrues,name = "numFalses") final long numFalses,name = "numNulls") final long numNulls) { this.numTrues = numTrues; this.numFalses = numFalses; this.numNulls = numNulls; }
@ThriftConstructor public BinaryColumnStatsData( @ThriftField(value = 1,name = "maxColLen") final long maxColLen,name = "avgColLen") final double avgColLen,name = "numNulls") final long numNulls) { this.maxColLen = maxColLen; this.avgColLen = avgColLen; this.numNulls = numNulls; }
@ThriftConstructor public DoubleColumnStatsData( @ThriftField(value = 1,name = "lowValue") final double lowValue,name = "highValue") final double highValue,name = "numNulls") final long numNulls,name = "numDVs") final long numDVs) { this.lowValue = lowValue; this.highValue = highValue; this.numNulls = numNulls; this.numDVs = numDVs; }
@ThriftConstructor public Version( @ThriftField(value = 1,name = "version") final String version,name = "comments") final String comments) { this.version = version; this.comments = comments; }
@ThriftConstructor public HiveObjectPrivilege( @ThriftField(value = 1,name = "hiveObject") final HiveObjectRef hiveObject,name = "principalName") final String principalName,name = "principalType") final PrincipalType principalType,name = "grantInfo") final PrivilegeGrantInfo grantInfo) { this.hiveObject = hiveObject; this.principalName = principalName; this.principalType = principalType; this.grantInfo = grantInfo; }
@ThriftConstructor public ColumnStatisticsData(final BooleanColumnStatsData booleanStats) { this.value = booleanStats; this.id = 1; this.name = "booleanStats"; }
iOS swift NSMutableData没有成员appendString
func createBodyWithParameters(parameters: [String: String]?,filePathKey: String?,imageDataKey: NSData,boundary: String) -> NSData { let body = NSMutableData() if parameters != nil { for (key,value) in parameters! { body.("--\(boundary)\r\n") body.appendString("Content-disposition: form-data; name=\"\(key)\"\r\n\r\n") body.appendString("\(value)\r\n") } } let filename = "user-profile.jpg" let mimetype = "image/jpg" body.appendString(options: <#T##NSData.Base64EncodingOptions#>)("--\(boundary)\r\n") body.appendString("Content-disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n") body.appendString("Content-Type: \(mimetype)\r\n\r\n") body.appendString("\r\n") body.appendString("--\(boundary)--\r\n") return body }
问题再次出现在appendString中,因为我收到错误:
NSMutableData类型的值没有成员appendString
我一直在寻找周围的工作,但没有找到任何,我有可用的追加方法,但它不需要一个字符串.
解决方法
首先,不要使用NSMutableData;相反,使用新的Data结构,根据您是使用var还是let,它将是可变的或不可变的:
var body = Data()
然后追加UTF-8字节:
body.append(Data("foo".utf8))
(如果你需要UTF-8以外的东西,还有其他编码的String方法.)
如果你想要教程的确切行为,这里是如何将其方法转换为Swift 3:
extension Data { mutating func append(string: String) { let data = string.data( using: String.Encoding.utf8,allowLossyConversion: true) append(data!) } } … body.append("foo")
但是,我不建议使用此代码,原因有两个.首先,有损转换意味着您的应用可能会默默地丢弃重要数据.其次,强制解包(数据!而不是数据)意味着如果编码出现问题,您的应用程序将崩溃而不是显示有用的错误.
ios – Swift:Codable – 提取单个编码密钥
let value = try! decoder.decode([String:Applmusic].self,from: $0["applmusic"])
这成功处理了以下JSON:
{ "applmusic":{ "code":"AAPL","quality":"good","line":"She told me don't worry",}
但是,无法使用以下代码中的applmusic编码密钥提取JSON:
{ "applmusic":{ "code":"AAPL",},"spotify":{ "differentcode":"SPOT","music_quality":"good","spotify_specific_code":"absent in apple" },"amazon":{ "amzncode":"SPOT","stanley":"absent in apple" } }
applmusic,spotify和amazon的数据模型是不同的.但是,我只需要提取applmusic并省略其他编码密钥.
我的Swift数据模型如下:
public struct Applmusic: Codable { public let code: String public let quality: String public let line: String }
API以完整的JSON响应,我不能要求它只给我所需的字段.
如何只解码json的特定部分?看来,Decodable要求我先对整个json进行反序列化,所以我必须知道它的完整数据模型.
显然,其中一个解决方案是创建一个单独的Response模型,只是为了包含applmusicparameter,但它看起来像一个hack:
public struct Response: Codable { public struct Applmusic: Codable { public let code: String public let quality: String public let line: String } // The only parameter is `applmusic`,ignoring the other parts - works fine public let applmusic: Applmusic }
你能提出一个更好的方法来处理这样的JSON结构吗?
多一点洞察力
我在通用扩展中使用了以下技术,它为我自动解码API响应.因此,我更倾向于概括一种处理此类情况的方法,而无需创建根结构.如果我需要的密钥在JSON结构中是3层深度怎么办?
这是为我解码的扩展:
extension Endpoint where Response: Swift.Decodable { convenience init(method: Method = .get,path: Path,codingKey: String? = nil,parameters: Parameters? = nil) { self.init(method: method,path: path,parameters: parameters,codingKey: codingKey) { if let key = codingKey { guard let value = try decoder.decode([String:Response].self,from: $0)[key] else { throw RestClientError.valueNotFound(codingKey: key) } return value } return try decoder.decode(Response.self,from: $0) } } }
API的定义如下:
extension API { static func getMusic() -> Endpoint<[Applmusic]> { return Endpoint(method: .get,path: "/api/music",codingKey: "applmusic") } }
解决方法
你可以像这样使用它:
let post = try decoder.decode(Post.self,from: data,keyPath: "nested.post")
您可以创建一个Decodable包装器(例如,ModelResponse),并将所有逻辑用于提取嵌套模型,其中包含一个键:
struct DecodingHelper { /// Dynamic key private struct Key: CodingKey { let stringValue: String init?(stringValue: String) { self.stringValue = stringValue self.intValue = nil } let intValue: Int? init?(intValue: Int) { return nil } } /// Dummy model that handles model extracting logic from a key private struct ModelResponse<nestedModel: Decodable>: Decodable { let nested: nestedModel public init(from decoder: Decoder) throws { let key = Key(stringValue: decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!]! as! String)! let values = try decoder.container(keyedBy: Key.self) nested = try values.decode(nestedModel.self,forKey: key) } } static func decode<T: Decodable>(modelType: T.Type,fromKey key: String) throws -> T { // mock data,replace with network response let path = Bundle.main.path(forResource: "test",ofType: "json")! let data = try Data(contentsOf: URL(fileURLWithPath: path),options: .mappedIfSafe) let decoder = JSONDecoder() // ***Pass in our key through `userInfo` decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!] = key let model = try decoder.decode(ModelResponse<T>.self,from: data).nested return model } }
您可以通过JSONDecoder的userInfo(“my_model_key”)传递所需的密钥.然后将其转换为ModelResponse中的动态Key以实际提取模型.
然后你可以像这样使用它:
let appl = try DecodingHelper.decode(modelType: Applmusic.self,fromKey: "applmusic") let amazon = try DecodingHelper.decode(modelType: Amazon.self,fromKey: "amazon") let spotify = try DecodingHelper.decode(modelType: Spotify.self,fromKey: "spotify") print(appl,amazon,spotify)
完整代码:
https://gist.github.com/aunnnn/2d6bb20b9dfab41189a2411247d04904
额外奖励:深层嵌套密钥
在玩了更多之后,我发现你可以使用这个修改过的ModelResponse轻松解码任意深度的键:
private struct ModelResponse<nestedModel: Decodable>: Decodable { let nested: nestedModel public init(from decoder: Decoder) throws { // Split nested paths with '.' var keypaths = (decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!]! as! String).split(separator: ".") // Get last key to extract in the end let lastKey = String(keypaths.popLast()!) // Loop getting container until reach final one var targetContainer = try decoder.container(keyedBy: Key.self) for k in keypaths { let key = Key(stringValue: String(k))! targetContainer = try targetContainer.nestedContainer(keyedBy: Key.self,forKey: key) } nested = try targetContainer.decode(nestedModel.self,forKey: Key(stringValue: lastKey)!) }
然后你可以像这样使用它:
let deeplynestedModel = try DecodingHelper.decode(modelType: Amazon.self,fromKey: "nest1.nest2.nest3")
从这个json:
{ "apple": { ... },"amazon": { "amzncode": "SPOT","music_quality": "good","stanley": "absent in apple" },"nest1": { "nest2": { "amzncode": "nest works","music_quality": "Great","stanley": "Oh yes","nest3": { "amzncode": "nest works,again!!!","stanley": "Oh yes" } } } }
完整代码:https://gist.github.com/aunnnn/9a6b4608ae49fe1594dbcabd9e607834
iOS – 如何在swift中使用`NSMutableString`
NSMutableAttributedString *res = [self.richTextEditor.attributedText mutablecopy]; [res beginEditing]; __block BOOL found = NO; [res enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0,res.length) options:0 usingBlock:^(id value,NSRange range,BOOL *stop) { if (value) { UIFont *oldFont = (UIFont *)value; UIFont *newFont = [oldFont fontWithSize:oldFont.pointSize * 2]; [res removeAttribute:NSFontAttributeName range:range]; [res addAttribute:NSFontAttributeName value:newFont range:range]; found = YES; } }]; if (!found) { // No font was found - do something else? } [res endEditing]; self.richTextEditor.attributedText = res;
我试图通过迭代每个属性来更改NSMutableAttributedString中的字体.我很高兴听到有更好的方法,但如果有人能帮助我翻译上述内容,我会感到非常满意.
解决方法
一个区别是这个实现使用可选的转换(如?),我这样做是为了演示这个概念.实际上,这不需要是可选的,因为NSFontAttributeName保证提供UIFont.
var res : NSMutableAttributedString = NSMutableAttributedString(string: "test"); res.beginEditing() var found = false res.enumerateAttribute(NSFontAttributeName,inRange: NSMakeRange(0,res.length),options: NSAttributedStringEnumerationoptions(0)) { (value,range,stop) -> Void in if let oldFont = value as? UIFont { let newFont = oldFont.fontWithSize(oldFont.pointSize * 2) res.removeAttribute(NSFontAttributeName,range: range) res.addAttribute(NSFontAttributeName,value: newFont,range: range) found = true } } if found == false { } res.endEditing()
关于Swift 4 Codable-Bool或String值和swift rangeofstring的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于com.facebook.swift.codec.ThriftConstructor的实例源码、iOS swift NSMutableData没有成员appendString、ios – Swift:Codable – 提取单个编码密钥、iOS – 如何在swift中使用`NSMutableString`的相关信息,请在本站寻找。
本文标签: