I just migrated my project to Swift 3 and am stuck on an error for my lazy instantiated NSFetchResultController. I use this method here :
https://www.andrewcbancroft.com/2015/03/05/displaying-data-with-nsfetchedresultscontroller-and-swift/
My current code
lazy var fetchedResultsController: NSFetchedResultsController = {
let primarySortDescriptor = NSSortDescriptor(key: "company", ascending: true)
let sortDescriptors = [primarySortDescriptor]
self.fetchRequest.sortDescriptors = sortDescriptors
let frc = NSFetchedResultsController(
fetchRequest: self.fetchRequest,
managedObjectContext: self.managedObjectContext!,
sectionNameKeyPath: nil,
cacheName: nil)
frc.delegate = self
return frc
}()
It produces 2 errors as shown below
Is this method no longer possible under Swift 3? I tried adding () -> <<error type>> in as suggested by Xcode but failed to produce the correct results.
The suggested () -> <<error type>> is misleading.
In Swift 3 NSFetchedResultsController has become a generic type.
You have to initialize it:
lazy var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult> = {
...
}()
as well as NSFetchRequest
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "MyEntity")
If you are using a subclass of NSManagedObject – which is recommended – you can use the subclass type for being more specific
lazy var fetchedResultsController: NSFetchedResultsController<MyEntity> = {
....
let fetchRequest = NSFetchRequest<MyEntity>(entityName: "MyEntity")
The huge benefit is you get rid of all type casts using fetch, insert etc.
Related
I would like to perform a test in one of my ViewModels that contains a BehaviorRelay object called "nearByCity" that it is bind to BehaviorRelay called "isNearBy". That's how my view model looks like.
class SearchViewViewModel: NSObject {
//MARK:- Properties
//MARK: Constants
let disposeBag = DisposeBag()
//MARK: Vars
var nearByCity:BehaviorRelay<String?> = BehaviorRelay(value: nil)
var isNearBy = BehaviorRelay(value: true)
//MARK:- Constructor
init() {
super.init()
setupBinders()
}
}
//MARK:- Private methods
private extension SearchViewViewModel{
func setupBinders(){
nearByCity
.asObservable()
.distinctUntilChanged()
.map({$0 ?? ""})
.map({$0 == ""})
.bind(to: isNearBy)
.disposed(by: disposeBag)
}
}
The test that i want to perform is to actually verify that when the string is accepted, the bool value also changes according to the function setupBinders().
Any Idea?
Thank you
Here's one way to test:
class RxSandboxTests: XCTestCase {
func testBinders() {
let scheduler = TestScheduler(initialClock: 0)
let source = scheduler.createColdObservable([.next(5, "hello"), .completed(10)])
let sink = scheduler.createObserver(Bool.self)
let disposeBag = DisposeBag()
let viewModel = SearchViewViewModel(appLocationManager: StubManager())
source.bind(to: viewModel.nearByCity).disposed(by: disposeBag)
viewModel.isNearBy.bind(to: sink).disposed(by: disposeBag)
scheduler.start()
XCTAssertEqual(sink.events, [.next(0, true), .next(5, false)])
}
}
Some other points:
Don't make your subject properties var use let instead because you don't want anybody to be able to replace them with unbound versions.
The fact that you have to use the AppLocationManager in this code that has no need of it implies that the object is doing too much. There is nothing wrong with having multiple view models in a view controller that each handle different parts of the view.
Best would be to avoid using Subjects (Relays) at all in your view model code, if needed, they are better left in the imperative side of the code.
At minimum, break up your setupBinders function so that the parts are independently testable. Your above could have been written as a simple, easily tested, free function:
func isNearBy(city: Observable<String?>) -> Observable<Bool> {
return city
.distinctUntilChanged()
.map {$0 ?? ""}
.map {$0 == ""}
}
Moving an app from Swift2 to Swift3 and I've hit an error that I've been unable to fix after trying several different suggestions.
lazy var address: AddressModel? = {
[unowned self] in
var dict = self.getpayloadDict()
var model: AddressModel
model = dict
return model
}()
model = dict throws Cannot assign value of type 'NSDictionary?' to type 'AddressModel'
The AddressModel . . .
class AddressModel: Deserializable {
var City: String?
var State: String?
var PostalCode: String?
required init(data: [String: AnyObject]) {
City = data["City"] as! String?
State = data["State"] as! String?
PostalCode = data["PostalCode"] as! String?
}
}
Any help appreciated.
The error is supposed to occur also in Swift 2. It's pretty clear: getpayloadDict() returns a dictionary which doesn't match AddressModel.
You might create an AddressModel instance from the dictionary
lazy var address: AddressModel? = { // this closure does not cause a retain cycle
let dict = self.getpayloadDict()
return AddressModel(data: dict)
}()
Side note:
as! String? (force unwrap an optional to an optional) is horrible syntax. Use regular conditional downcast as? String. And please conform to the naming convention that variable names start with a lowercase letter.
In FirstViewController i'm fetching the response from JSON and want to pass that fetched response to another view controller.Below is the code which i have used so far for parsing and passing the response.
FirstViewController
var fn:String! //globally declared variable
code i have tried for parsing in FirstViewController
do {
let detailsDictionary:NSDictionary = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! Dictionary<String, AnyObject> as NSDictionary
print(detailsDictionary)
let details = detailsDictionary["Data"] as! [[String:AnyObject]]
print(details)
for dtl in details
{
self.fn = dtl["Father_Name"] as? String ?? "NA"
print(self.fn) //here i'm getting the exact value from JSON
}
}
}
SecondViewController
In SecondViewController there is a Label called profile_name and want to set that parsed string(fn) as Label's text. for that i declared another variable as global.
var pname:String!
below is the code i have used to fetch the value from FirstViewController.
viewDidLoad()
{
let othervc = FirstViewController()
self.pname = othervc.fn
self.profile_name.text = self.pname
}
Problem : I tried my best efforts to get the desired output but i'm getting nil response.
Please Help.
In Second ViewController
let strName:String!
In First ViewController
let strOne = "This is for testing"
let objstory = self.storyboard?.instantiateViewController(withIdentifier: "yout Secoond ViewController Storybord ID") as! YourSecondViewControllerName
objstory.strNam = strOne
self.navigationController?.pushViewController(objstory, animated: true)
Your updated code just won't work.
let othervc = FirstViewController()
creates a new instance of FirstViewController (not the one that got the JSON).
You should be handling it something like this:
In FirstViewController
let fn = dtl["Father_Name"] as? String ?? "NA"
let svc = SecondViewController() // Or maybe instantiate from Storyboard, or maybe you already have a reference to it
svc.pname = fn
present(svc, animated: true, completion: nil)
Then in SecondViewController
override func viewDidLoad() {
super.viewDidLoad()
profile_name.text = pname
}
I'd suggest you take some time out and re-read Apple's View Controller programming guide.
Original Answer
The problem you have here…
vcvalue.profile_name.text = fn
is that profile_name is nil as the view for the view controller hasn't been loaded at this point.
You should handle this by creating a property in LeftSideMenuViewController
var name: String?
Then set
vcvalue.name = fn
And then in LeftSideMenuViewController
override func viewDidLoad() {
super.viewDidLoad()
profile_name.text = name
}
Also, some basic tips…
Don't force unwrap (!) apart from IBOutlets. You may have to write a bit more code, but you will reduce crashes.
Make #IBOutlets private - this will prevent you accidentally assigning to them as you are now
If you're overriding any viewWill/DidDis/Appear methods, you must call super at some point.
You need to re-read the section on switch/case
So this…
let a = indexPath.row
switch(a)
{
case 0 :
if(a == 0)
{
return 45
}
break
etc
could just be…
switch indexPath.row {
case 0...4:
return 45
case 5:
return 50
default:
break
}
I am facing a problem while saving the NSManagedObject to NSManagedObjectContext in Swift 3.0 and Xcode 8. Adding the code snippets for better Understanding
let config = NSManagedObject(entity: entityDescription!, insertInto: self.moc) as! Config
Here Config class is derived from NSManagedObject
class Config: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
Assigning the Key and value to my config as below and calling a save
config.key = "access_token"
config.value = access_token
do
{
try config.managedObjectContext?.save()
}catch let error as NSError
{
NSLog(error.localizedDescription)
onCompletion("Login Failed")
return
}
This doesnt throw any error to me, but while fetching the value of access_token from NSManagedObject, value is nil
do
{
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Config")
let predicate = NSPredicate(format: "key == %#", "access_token")
fetchRequest.predicate = predicate
let fetchResults = try moc.fetch(fetchRequest) as? [Config]
if(fetchResults?.count > 0)
{
//NSLog((fetchResults?.first!.value)!)
return fetchResults?.first!.value
}
} catch let error as NSError{
NSLog(error.localizedDescription)
}
What is wrong with this piece of code?
EDIT: I can see the following code where persistentStoreCoordinator is set for managedObjectContext
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
This is the code I used in Xcode 7.3.1 and that worked fine:
var selectedFiles = NSMutableArray(capacity:1)
let finder: AnyObject! = SBApplication(bundleIdentifier:"com.apple.finder")
let finderObject = finder.selection as! SBObject
let selection: AnyObject! = finderObject.get()
let items = selection.arrayByApplyingSelector(Selector("URL"))
let filteredfiles = (items as NSArray).pathsMatchingExtensions(["ai","pdf","ap","paf","pafsc"])
for item in filteredfiles {
let url = NSURL(string:item ,relativeToURL:nil)
selectedFiles.addObject(url!)
}
This is the code corrected for Xcode 8.0 and that does not work:
the error is generated for the last line
error = Cannot call value of non-function type '[Any]!'
var selectedFiles = NSMutableArray(capacity:1)
let finder: AnyObject! = SBApplication(bundleIdentifier:"com.apple.finder")
let finderObject = finder.selection as! SBObject
if let selection = finderObject.get() as AnyObject?{
let items = selection.array(#selector(getter: NSTextCheckingResult.url))
let filteredfiles = (items as NSArray).pathsMatchingExtensions(["ai","pdf","ap","paf","pafsc"])
for item in filteredfiles {
let url = NSURL(string:item ,relativeToURL:nil)
selectedFiles.addObject(url!)
}
}
I have tried many solutions, but unfortunately cannot find a clue.
I guess this is because Swift 3.0x APIs have changed drastically....
Any help is welcome!
This is a slightly different approach using a couple of native Swift functions for Swift 3
var selectedFiles = [URL]()
let finder : AnyObject = SBApplication(bundleIdentifier:"com.apple.finder")!
let finderObject = finder.selection as! SBObject
if let selection = finderObject.get() as? [SBObject] {
selection.forEach { item in
let url = URL(string: item.value(forKey:"URL") as! String)!
selectedFiles.append(url)
}
let goodExtensions = ["ai","pdf","ap","paf","pafsc"]
let filteredURLs = selectedFiles.filter({goodExtensions.contains($0.pathExtension)})
print(filteredURLs)
}
PS: I highly recommend to use AppleScriptObjC. It's so much easier to use.
PPS: valueForKey is intentionally used because KVC is really needed to get the property value.