Originally I tried to use something like this:
extension URL: CustomStringConvertible{
public override var description: String {
let url = self
return url.path.removingPercentEncoding ?? ""
}
}
After fixing compiler warning code became:
extension URL{
public var description: String {
let url = self
return url.path.removingPercentEncoding ?? ""
}
}
but
print(fileURL) still shows old URL description with percentages.
You can't override a method in an extension. What you're trying to do isn't possible in Swift. It's possible in ObjC (on NSURL) by swizzling the methods, but this should never be done in production code. Even if you could get the above working in Swift through some trickery, you should never use that in production code for the same reason. It could easily impact you in very surprising ways (for instance, it could break NSCoding implementations that expect description to work a certain way.
If you want this style of string, create a method for it and call that when you want it. Don't modify description in an existing class.
Related
As the Core Data Programming Guide - Object Validation, updated to Swift 3, suggests that the Key-Value Validation utilizing the Key-Value Coding of objective-c runtime is the recommended approach to perform single property validation.
As the evolutions of Swift and iOS in recent years, does this approach still represent the best practice? And what are the practical caveats when applying this technic in modern iOS?
For example,
#objc(AuthorMO)
public class AuthorMO: NSManagedObject, Identifiable {
#NSManaged public var uuid: UUID?
#NSManaged public var name: String?
}
// MARK: Key-Value Property Validation
extension AuthorMO {
#objc public func validateUuid(_ value: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
guard let newValue = value.pointee as? UUID else { return }
// Custom property validation.
}
#objc public func validateName(_ value: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
guard let newValue = value.pointee as? String else { return }
// Custom property validation.
}
}
// MARK: LifeCycle Validation Alternative
// previously mainly used for inter-properties validation.
extension AuthorMO {
public override func validateForInsert() throws {
try super.validateForUpdate()
try propertyValidations()
}
public override func validateForUpdate() throws {
try super.validateForUpdate()
try propertyValidations()
}
public func propertyValidations() throws {
try validateUUID()
try validateName()
}
public func validateUUID() throws {
let newValue = primitiveValue(forKey: #keyPath(AuthorMO.uuid))
// Custom property validation
}
public func validateName() throws {
let newValue = primitiveValue(forKey: #keyPath(AuthorMO.name))
// Custom property validation
}
}
This is one way of doing it. It's only automatically used when you're saving changes, and in that case all it can do is prevent saving and return an error message. You might display that error message to the user. If that fits your needs, then it's a good approach. You can also run it manually. It might be less useful in other situations, for example importing data from a server API. This is the only built-in technique specifically designed for property validation.
There are other ways-- literally any way that works for you to check that the property values fit your requirements. For example, you could write your own failable initializer, and have it return nil if any of the arguments are no good. Or have it throw so it can return an Error explaining why it can't initialize. Custom code like this won't prevent saving changes, but if you use it carefully it'll still make sure values match your requirements.
When working with CoreData this good way of validating a data. If you would like to work with SQLite using full potential of Swift then you should chose the GRDB project. This project is promoted by Swift Developers by means of dedicated sub forum on https://forums.swift.org forum site.
Just updated to Xcode 12.0 from the last version of Xcode 11/iOS13 and am getting an error in AppDelegate: Thread 1: "Subclass MPMediaItem must implement -valueForProperty: defined in [MPMediaItem class]."
When the app starts, I MPMediaQuery the library for songs and store them to a #Published variable in an Observable Object like this:
#Published var songs = [MPMediaItem]()
init() {
self.songs = MPMediaQuery.songs().items
}
Later when I want to access a property I do so like this:
Text(self.observableObject.songs[0].title)
I’ve tried changing these to use .value(forProperty: "MPMediaItemPropertyTitle") but it doesn’t feel to be use a string over a property (and Xcode then pops up errors like Failed to produce diagnostic for expression; please file a bug report apple.) AFAIK, I’m not subclassing MPMediaItem anywhere and I’ve tried Googling the error above with no luck.
Anyone know what’s going on?
I'm not sure if this is useful to your situation, but I recently came across a simiar problem.
In my case I needed to change 2 things that triggered this crash:
I was initialising an empty MPMediaItem() as placeholder when no current song was loaded. The crash happened when trying to access a property (e.g. title) on this empty instance. Upon removing the empty MPMediaItem() and implementing a different placeholder approach, the problem went away.
To test music on the simlator I was using a DummyMediaQuery: MPMediaQuery which returned an array of DummyMediaItem: MPMediaItem. In the DummyMediaItem class I did:
final class DummyMediaItem: MPMediaItem {
private let _artist: String
override var artist: String { return _artist }
private let _title: String
override var title: String { return _title }
// and so on...
// fix: crash in iOS 14
override func value(forProperty property: String) -> Any? {
return nil
}
}
In Android, we can access some standard string resources provided by the platform, e.g. cancel, OK, delete etc. It is convenient since I don't have to store all the translation for those simple texts.
I know that Swift provides something similar to UIColor, e.g. UIColor.systemgray.
My question is: Is there something similar but for String. Thanks!
You can technically load localized strings from Apple's own frameworks. For instance, to load from UIKit, you'd use
let bundle = Bundle(for: UIView.self)
let string = bundle.localizedString(forKey: "Cancel", value: nil, table: nil)
When the current locale is German, this gives "Abbrechen".
The downside of this approach is that you'll only know whether or not a specific string exists in the framework at runtime.
The best workaround I can think of for this is to define the keys that you use yourself and continuously verify them with a unit test.
For example you could gather all your system-string keys in a case-iterable enumeration
public enum SystemStringKey: String, CaseIterable {
case cancel = "Cancel"
...
}
and use them to load the system strings via an extension on String
public extension String {
static var systemStringBundle: Bundle {
return Bundle(for: UIView.self)
}
static func systemString(for key: SystemStringKey) -> String {
return systemStringBundle.localizedString(forKey: key.rawValue, value: nil, table: nil)
}
}
In code you can then use the system strings like constants
label.text = .systemString(for: .cancel)
To verify their continued existence, you could use a unit test such as this
class SystemStringKeyTests: XCTestCase {
func testAllKeysExist() throws {
let path = try XCTUnwrap(String.systemStringBundle.path(forResource: "Localizable", ofType: "strings"))
let dict = try XCTUnwrap(NSDictionary(contentsOfFile: path))
for key in SystemStringKey.allCases {
XCTAssertNotNil(dict[key.rawValue])
}
}
}
This isn't 100% bulletproof as it could still happen that Apple removes a string in an iOS update and then any released app that relied on that string would show the untranslated English string until you ship an update yourself.
Update: I threw together a Swift package for conveniently exploring and accessing available strings from system bundles. There are some caveats though. See https://nosuchdomain.mooo.com/git/doc/swift-systemstrings for further info.
No, there are no such static properties in Swift
I am trying to use a selector to see if a certain protocol can perform an action. When I try it like this:
protocol Test {
func hello()
func goodBye(a: String)
}
class Tester: NSObject, Test {
override init() {}
func hello() { }
func goodBye(a: String) { }
}
let a: Test = Tester()
let result = a.responds(to: Selector("goodByeWithA:"))
In this case, result evaluates to false.
But if I add the #objc tag to the protocol, it evaluates as true.
#objc protocol Test {
func hello()
func goodBye(a: String)
}
Why is this?
On a side note, I know that it is now recommended to use the #selector syntax and to move away from using strings, but for various reasons, I have to use a string in this case.
EDIT: This only started happening once I migrated my project to Swift 4.2
By default Swift generates code that is only available to other Swift code, but if you need to interact with the Objective-C runtime – all of UIKit, for example – you need to tell Swift what to do.
That’s where the #objc attribute comes in: when you apply it to a class or method it instructs Swift to make those things available to Objective-C as well as Swift code. So, any time you want to call a method from a UIBarButtonItem or a Timer, you’ll need to mark that method using #objc so it’s exposed – both of those, and many others, are Objective-C code.
Don’t worry: if you forget to add #objc when it’s needed, your code simply won’t compile – it’s not something you can forget by accident and introduce a bug.
This is from debug.
url String "https://openweathermap.org/img/w/Optional(\50n\).png"
The problem is in this line:
self.imgURL = "https://openweathermap.org/img/w/\(self.dodatek).png"
When I change (self.dodatek) for example to icon 50n it works and show me the icon.
When I start my weather app and write name of the city I want to have url like this, but for 50n it has to be my variable that is taken from json.
https://openweathermap.org/img/w/50n.png
Looks like the quick and dirty fix for your problem is unwrapping the optional dodatek(which is probably String?) like this
self.imgURL = "https://openweathermap.org/img/w/\(self.dodatek!).png"
The cleaner solution is definitely
guard let dodatek = self.dodatek else {
// handle that dodatek is nil
// or just return
return
}
self.imgURL = "https://openweathermap.org/img/w/\(dodatek).png"
Explanation
The problem is that your property dodatek can theoretically be nil when you declare it as String?
If you are sure that it can never be nil, declare it as
var dodatek: String // not String?
instead.
In case it can be nil, the guard let statement above should be used to either define a fallback value of an url that should be used (like a url to a generic weather icon maybe)