I'm writing a macOS target for an old iOS based project using SwiftUI. Is's a Core Data driven application and for the macOS target, I've successfully implemented generic List using a dynamic #FetchRequest, mostly as described by Paul Hudson in his blog.
I've primarily built the target by following Apple's SwiftUI Tutorials and copying the sample code provided.
The previously used conditional if statement actively filtered each SwiftUI List based on 3 UI controls.
// PART 1
if (!self.appData.showFavouritesOnly
|| fetchedEvent.isFavourite)
// PART 2
&& (self.searchText.count == 0
|| (fetchedEvent.eventName?.contains(self.searchText) == true))
// PART 3
&& (self.filter == .all
|| self.filter.name == fetchedEvent.eventCategory
|| (self.filter.category == .featured && fetchedEvent.isFeatured)) {
Now that I have a generic #FetchRequest that uses predicates, I want to translate this conditional if statement into an NSCompoundPredicate.
I'll include the entire initialiser so you can see how the dynamic #FetchRequest is built, but it is the predicate that I require assistance with...
init(sortDescriptors: [NSSortDescriptor],
searchKey: String,
searchValue: String?,
showFavourites: Bool,
filterKey: String,
filter: FilterType,
#ViewBuilder content: #escaping (T) -> Content) {
let entity = T.entity
let predicateTrue = NSPredicate(value: true)
// PART 1
let predicateFavourite = showFavourites == false ? predicateTrue : NSPredicate(format: "isFavourite == TRUE")
// PART 2
let predicateSearch = searchValue?.count == 0 ? predicateTrue : NSPredicate(format: "%K CONTAINS[cd] %#", searchKey, searchValue!)
// The initialiser works perfectly down to this point...then...
// PART 3
let predicateFilterName = filter == .all ? predicateTrue : NSPredicate(format: "%K == %#", filterKey, filter.name as CVarArg)
let predicateFilterFeature = filter.category == .featured ? NSPredicate(format: "isFeatured == TRUE") : predicateTrue
let predicateOr = NSCompoundPredicate(orPredicateWithSubpredicates: [predicateFilterName, predicateFilterFeature])
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateFavourite, predicateSearch, predicateOr])
fetchRequest =
FetchRequest<T>(entity: entity,
sortDescriptors: sortDescriptors,
predicate: predicate)
self.content = content
}
The code that is included here under Part 3 works partially. Switching between FilterType.all and FilterType.featured makes the expected changes, however I'm struggling to write the predicate for the "other" cases where another Category is chosen - that is - NOT Featured, but EITHER .lakes, .rivers or .mountains.
For completeness, I've also included the enum Category and struct FilterType...
enum Category: String, CaseIterable, Codable, Hashable {
case featured = "Featured"
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
}
struct FilterType: CaseIterable, Hashable, Identifiable {
var name: String
var category: Category?
init(_ category: Category) {
self.name = category.rawValue
self.category = category
}
init(name: String) {
self.name = name
self.category = nil
}
static var all = FilterType(name: "All")
static var allCases: [FilterType] {
return [.all] + Category.allCases.map(FilterType.init)
}
var id: FilterType {
return self
}
}
I think the problem is here:
let predicateFilterFeature = filter.category == .featured ? NSPredicate(format: "isFeatured == TRUE") : predicateTrue
If the filter is .lakes etc, then this subpredicate will be TRUE, which when ORed with predicateFilterName overrides it. Try returning FALSE:
let predicateFilterFeature = filter.category == .featured ? NSPredicate(format: "isFeatured == TRUE") : predicateFalse
Mine is a fraudulent answer because it is a hacked solution I stumbled across after trying to think about the construction of the predicates in a different manner. (It doesn't demonstrate any careful thought of the actual problem of understanding the syntax of the predicates that I had written!)
So this response doesn't answer my original question - that has been answered by #pbasdf - although this response achieves the same outcome.
So perhaps an alternate solution?!?
let predicateTrue = NSPredicate(value: true)
let predicateFavourite = showFavourites == false ? predicateTrue : NSPredicate(format: "isFavourite == TRUE")
let predicateSearch = searchValue?.count == 0 ? predicateTrue : NSPredicate(format: "%K CONTAINS[cd] %#", searchKey, searchValue!)
let predicateFilterFeatured = NSPredicate(format: "isFeatured == TRUE")
let predicateFilterName = NSPredicate(format: "%K == %#", filterKey, filter.name as CVarArg)
let predicateFilterCategory = filter.category == .featured ? predicateFilterFeatured : predicateFilterName
let predicateFilter = filter == .all ? predicateTrue : predicateFilterCategory
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateFavourite, predicateSearch, predicateFilter])
Related
I get the warning xcode 11.1 for iOS 13 (swift 5)
Cannot force unwrap value of non-optional type 'Reachability' on the line let reachability = Reachability()!
I tried, if statements and try do, but none seem to work.Removing the ! gives the warning "Call can throw, but errors cannot be thrown out of a property initializer"
import Reachability
class ReachabilityDetect {
let reachability = Reachability()!
var dm = DataModel()
func addObservers(datamodel: DataModel) {
self.dm = datamodel
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability)
do{
try reachability.startNotifier()
}catch{
Util.DLog("Reachability notifier niet te starten.")
}
}
#objc func reachabilityChanged(note: Notification) {
let reachability = note.object as! Reachability
switch reachability.connection {
case .wifi:
Util.DLog("WiFi is actief")
self.dm.dataConnectionisWifi = true
self.dm.dataConnectionisCellular = false
case .cellular:
Util.DLog("Celluar data is actief")
self.dm.dataConnectionisWifi = false
self.dm.dataConnectionisCellular = true
case .none:
Util.DLog("geen celluar of wifi data actief")
self.dm.dataConnectionisWifi = false
self.dm.dataConnectionisCellular = false
default: break
}
}
}
I had the same problem.
Instead of
let reachability = Reachability()!
use this
let reachability = try! Reachability()
I've got a tableview showing some data and I filter the shown data uisng UISearchbar. Each data struct consists of different values and
struct Cake {
var name = String()
var size = String()
var filling = String()
}
When a user starts typing I don't know whether he is filtering for name, size or filling. I don't want to use a scopebar. Is there a way to filter for various fields at the same time in swift 3?
This is the code I use to filter:
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text! == "" {
filteredCakes = cakes
} else {
// Filter the results
filteredCakes = cakes.filter { $0.name.lowercased().contains(searchController.searchBar.text!.lowercased()) }
}
self.tableView.reloadData()
}
thanks for your help!
func updateSearchResults(for searchController: UISearchController)
{
guard let searchedText = searchController.searchBar.text?.lowercased() else {return}
filteredCakes = cakes.filter
{
$0.name.lowercased().contains(searchedText) ||
$0.size.lowercased().contains(searchedText) ||
$0.filling.lowercased().contains(searchedText)
}
self.tableView.reloadData()
}
I have two buttons as right and left arrow on my main page and every time a button is clicked UILabel is updated through an array e.g let names = ["John, Smith, "Ahmed", "Jack", "Kathy"]. I have two UIActions as rightArrowClicked and leftArrowClicked. Initially the label should appear as John with left arrow disabled, which'll be done by the isHidden property of leftArrow and likewise when the value is Kathy then rightArrow should be disabled. Along with that i also need to update another label w.r.t to the name value e.g john can have a value of "cute", and kathy as "pretty" and these values are coming from an external library which i've added via cocoapods which are being generated in a function of that class. Any ideas how would i do that?
This is code with simple logic that you need
let names = ["John", "Smith", "Ahmed", "Jack", "Kathy"]
var currentIndex = 0
leftButton.isHidden = true
#IBAction func left(_ sender: Any) {
currentIndex --
label.text = names[currentIndex]
if currentIndex == 0 {
leftButton.isHidden = true
}
rightButton.isHidden = false
}
#IBAction func right(_ sender: Any) {
currentIndex ++
label.text = names[currentIndex]
if currentIndex == names.count - 1 {
rightButton.isHidden = true
}
leftButton.isHidden = false
}
You can try like below and call updateButtonsState() in viewDidLoad method
let names = ["John", "Smith", "Ahmed", "Jack", "Kathy"]
let nature = ["cute", "pretty", "cute", "pretty", "cute"]
var currenIndex = 0
#IBAction func leftBtnClick(_ sender: UIButton) {
currenIndex = currenIndex - 1
updateButtonsState()
}
#IBAction func rignthBtnClick(_ sender: UIButton) {
currenIndex = currenIndex + 1
updateButtonsState()
}
func updateButtonsState() {
leftBtn.isEnabled = currenIndex > 0 ? true : false
rightBtn.isEnabled = currenIndex < names.count - 1 ? true : false
if currenIndex >= 0 && currenIndex < names.count {
lblName.text = names[currenIndex]
lblNameWithNature.text = nature[currenIndex]
}
print("currenIndex == \(currenIndex)")
}
I have just changed my code to Swift 3 from swift 2.3. The following code is working fine with swift 2.3 but there is no effect with swift 3.
I want to develop a customise text editor. And let user to select text and change its color. For that i am using this code.
let selectedRange = textView.selectedRange
let currentAttr = textView.textStorage.attributes(at: selectedRange.location, effectiveRange: nil)
var attrName = NSForegroundColorAttributeName
if !isForeGround{
attrName = NSBackgroundColorAttributeName
}
if currentAttr[attrName] == nil || currentAttr[attrName] as! UIColor != selectedUIColor{
let dict = [attrName:selectedUIColor]
let currentFont = currentAttr[NSFontAttributeName]
let fontDescriptor = (currentFont! as AnyObject).fontDescriptor
let updatedFont = UIFont(descriptor: fontDescriptor!, size: 0.0)
let dict2 = [NSFontAttributeName: updatedFont]
textView.textStorage.beginEditing()
if currentAttr.count>0{
textView.textStorage.addAttributes(dict, range: selectedRange)
}else{
textView.textStorage.setAttributes(dict, range: selectedRange)
}
textView.textStorage.addAttributes(dict2, range: selectedRange)
textView.textStorage.endEditing()
}
Runs successfully but there is no effect on text color.
This worked for me.
First you need to catch your selected text, with:
let selectedText = textView.selectedRange
Then, you create the attribute:
let myAttribute = [ NSForegroundColorAttributeName: selectedUIColor]
And lastly:
textView.textStorage.addAttributes(myAttribute, range: selectedText)
This must be in an Action called by any sender
Hope it works also for you!
Regards
Selected text can be changed with
self.textView.tintColor = .red
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
}()