I'm trying to read data from firebase and draw rectangles according to the data in firebase. The colour will also fill accordingly to the value retrieved from the firebase. I have managed to retrieve the data from firebase and append them into arrays.
As Firebase is asynchronous, I have added completion handler to only run the following code after the readMarina() has completed. I am trying to test out the readMarina{} in draw() method by inputting a simple drawing of a rectangular box. Everything ran smoothly until the context?.fill(lot), where it gave an error of:
"Thread 1: EXC_BAD_ACCESS (code=1, address=0x7ffe00000058)"
override func draw(_ rect: CGRect)
{
let context = UIGraphicsGetCurrentContext()
let lot = CGRect(x: 80,y: 20,width: 30,height: 60)
readMarina{
context?.addRect(lot)
context?.setFillColor(UIColor.red.cgColor)
context?.fill(lot)
}
}
func readMarina (_ completion: (() -> Void)?){
let ref = Database.database().reference()
let cpDB = ref.child("CarPark")
cpDB.child("MarinaSq").observe(.value, with: {snapshot in
let snapshotValue = snapshot.value as! Dictionary<String, AnyObject>
for (key, value) in snapshotValue
{
if (self.lot.count)<((snapshotValue.count)){
let v = value as! String
let k = key
self.lot.append(k)
self.status.append(v)
}
}
completion?()
})
}
I tried to debug and when I hovered over "context" and "context?", they all showed a value. When I hovered over the "lot", there is also value parsed in. What is wrong with my code and why my rectangular box did not appear on my View?
A picture of the error message
As draw(_ rect: CGRect) function description says:
This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should never call this method directly yourself. To invalidate part of your view, and thus cause that portion to be redrawn, call the setNeedsDisplay() or setNeedsDisplay(_:) method instead.
Your closure inside of draw(_ rect: CGRect) function is the one that makes interference in its execution process and most probably causes a crash. To handle this you should remove readMarina function call from inside of a draw(_ rect: CGRect). Here is a simple implementation:
class MyView: UIView {
var myColor: UIColor? {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
if let context = UIGraphicsGetCurrentContext(), let color = myColor {
let lot = CGRect(x: bounds.midX-50, y: bounds.midY-50, width: 100, height: 100)
context.addRect(lot)
context.setFillColor(color.cgColor)
context.fill(lot)
}
}
}
I added a View to my ViewController in Main.storyboard, and in identity inspector set custom class to "MyView".
class ViewController: UIViewController {
#IBOutlet weak var myView: MyView!
override func viewDidLoad() {
super.viewDidLoad()
testFunc {
self.myView.myColor = UIColor.red
}
}
func testFunc(completion:(() -> Void)?) {
// test func
completion?()
}
}
Related
How do I draw text into an NSImage with Swift and the AppKit framework? My target is a macOS application.
I would also appreciate any resources where things like this are explained. I couldn't find examples or explanations in the Apple developer documentation.
The following code contains a comment to illustrate what I want to do. It shows the NSImage in a menubar item.
import Cocoa
class ViewController: NSViewController {
let statusItem: NSStatusItem = NSStatusBar.system.statusItem(
withLength: NSStatusItem.squareLength)
override func viewDidLoad() {
super.viewDidLoad()
let imageSize = NSSize.init(width: 18.0, height: 18.0)
let statusItemImage = NSImage(
size: imageSize,
flipped: false,
drawingHandler: { (dstRect: NSRect) -> Bool in
// How do I draw text into this NSImage?
return true
})
statusItem.button?.image = statusItemImage
}
override var representedObject: Any? {
didSet {
}
}
}
This in the place where the comment is works:
let foo: NSString = "a"
foo.draw(in: dstRect)
I am coding in Swift 3.2. I have a class called EventCollectionView that extends UICollectionView. An instance of this class is an 'event' and I would like to initialize it with three UIImages, images that display the event photo, event info, and event tickets.
However, when I try to call super.init() in the custom initializer I get an error message saying 'Must call a designated initializer of the superclass UICollectionView.' I have tried implementing super.init(frame:CGRect) but that doesn't seem to work either.
Below is the first part of my EventCollectionView class.
var eventPhoto: UIImage?
var eventInfo: UIImage?
var eventTickets: UIImage?
//Initializers
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init?(eventPhoto: UIImage?, eventInfo: UIImage?, eventTickets: UIImage?) {
super.init() //Where the error occurs
self.eventPhoto = eventPhoto
self.eventInfo = eventInfo
self.eventTickets = eventTickets
Any insight would be greatly appreciated.
From the Apple developer documents, it looks like the appropriate init method signature should be: init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout). For example, your init method could become:
init?(eventPhoto: UIImage?, eventInfo: UIImage?, eventTickets: UIImage?) {
let flowLayout:UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let collectionViewFrame:CGRect = CGRect(x: 0, y: 0, width: 200, height: 200)
super.init(frame: collectionViewFrame, collectionViewLayout: flowLayout)
...
}
So if you want to initialize such EventCollectionView by storyboard you should inject those parameters in another way like:
class EventCollectionView:UICollectionView {
var eventPhoto: UIImage?
var eventInfo: UIImage?
var eventTickets: UIImage?
func setup(eventPhoto: UIImage?, eventInfo: UIImage?, eventTickets: UIImage?) {
self.eventPhoto = eventPhoto
self.eventInfo = eventInfo
self.eventTickets = eventTickets
}
}
then you should call setup anytime after the initialization such as:
import UIKit
class YourViewController: UIViewController {
#IBOutlet var collectionView:EventCollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// setup whatever here:
let photo:UIImage? = nil
let info:UIImage? = nil
let tickets:UIImage? = nil
collectionView.setup(eventPhoto: photo, eventInfo: info, eventTickets: tickets)
}
}
I have imported the iCarousel provided by nicklockwood into a macOs app.
https://github.com/nicklockwood/iCarousel
The app is written in Swift.
After managing to get everything working (importing .m and .h files, adding Bridging Header) there is one minor thing.
Once the app is started and the NSViewController is activated I see a blank ViewController. Only if I start resizing the view I see the iCarousel with all loaded picture.
My code looks like the following:
class CarouselViewController: NSViewController, iCarouselDataSource, iCarouselDelegate {
var images = [NSImage]()
#IBOutlet var carousel: iCarousel!
override func awakeFromNib() {
super.awakeFromNib()
loadImages()
}
override func viewDidLoad() {
super.viewDidLoad()
self.carousel.type = iCarouselType.coverFlow
self.carousel.dataSource = self
self.carousel.delegate = self
carousel.reloadData()
}
func loadImages() {
let filePath = "/pics/"
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.picturesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
let path = paths?.appending(filePath)
//
let fileManager = FileManager.default
let enumerator:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: path!)!
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("jpg") || element.hasSuffix("png") {
if let image = NSImage(contentsOfFile: path! + element) {
print("File: \(path!)\(element)")
self.images.append(image)
}
}
}
}
func numberOfItems(in carousel: iCarousel) -> Int {
return images.count
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: NSView?) -> NSView {
let imageView = NSImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
imageView.image = self.images[index]
imageView.imageScaling = .scaleAxesIndependently
return imageView
}
func carouselItemWidth(_ carousel: iCarousel) -> CGFloat {
return 200.0
}
}
How can I manage to display my iCarousel without resizing first?
Thank you
I guess I've found one solution:
The drawing of the actual images in the carousel is triggered by layOutItemViews or layoutSubviews. One function that is public accessible is setType which allows to set the carousel type.
If I set the type after assigning dataSource and after loading the data, the images will be displayed fine.
So the most simple answer is to change viewDidLayout like the following:
override func viewDidLoad() {
super.viewDidLoad()
self.carousel.dataSource = self
self.carousel.delegate = self
carousel.reloadData()
// Triggers layOutItemView and thus will render all images.
self.carousel.type = iCarouselType.coverFlow
}
Now it is also possible to assign datasource via storyboard and the Connection inspector.
I have a function where it goes into firebase data base and pulls the amount of items I have stored. It returns three when I print the variable postCount in the function itself but when I print it anywhere else it only has zero. It doesn't seem to be updating the global value.
import UIKit
import Firebase
class ViewController: UIViewController, iCarouselDataSource, iCarouselDelegate {
var items: [Int] = []
var postCount = 0
#IBOutlet var carousel: iCarousel!
func getData() {
Database.database().reference().child("Posts").observeSingleEvent(of: .value, with: { (snapshot) in
if let dict2 = snapshot.value as? [String: AnyObject] {
self.postCount = dict2.count
}
//This prints 3
print(self.postCount)
})
}
override func awakeFromNib() {
super.awakeFromNib()
print(self.postCount)
for i in 0 ... 99 {
items.append(i)
}
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
print(self.postCount)
carousel.type = .cylinder
}
func numberOfItems(in carousel: iCarousel) -> Int {
return postCount
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
var label: UILabel
var itemView: UIImageView
//reuse view if available, otherwise create a new view
if let view = view as? UIImageView {
itemView = view
//get a reference to the label in the recycled view
label = itemView.viewWithTag(1) as! UILabel
} else {
//don't do anything specific to the index within
//this `if ... else` statement because the view will be
//recycled and used with other index values later
itemView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 400))
itemView.image = UIImage(named: "page.png")
itemView.layer.borderWidth = 10
itemView.contentMode = .center
label = UILabel(frame: itemView.bounds)
label.backgroundColor = .clear
label.textAlignment = .center
label.font = label.font.withSize(50)
label.tag = 1
itemView.addSubview(label)
}
//set item label
//remember to always set any properties of your carousel item
//views outside of the `if (view == nil) {...}` check otherwise
//you'll get weird issues with carousel item content appearing
//in the wrong place in the carousel
label.text = "\(items[index])"
return itemView
}
func carousel(_ carousel: iCarousel, valueFor option: iCarouselOption, withDefault value: CGFloat) -> CGFloat {
if (option == .spacing) {
return value * 1.1
}
return value
}
}
I believe it is because retrieving data from Firebase is asynchronous. getData() is called and then instantly after that you print(self.postCount), even though getData() is still retrieving the data from Firebase.
Try changing the print(self.postCount) within getData() to
print("Data Loaded and there are \(self.postCount) posts")
and you should see in your log that the print(self.postCount) within viewDidLoad() is called before the print within getData(). As a result, when print(self.postCount) in viewDidLoad() is called, postCount still equals the default value of 0 since getValue() has not completed running even though that print is on the line after getData().
You probably want to call whatever functions that need to use the updated postCount inside the closure in getData() so that you ensure those functions use the updated postCount.
For example:
func getData() {
Database.database().reference().child("Posts").observeSingleEvent(of: .value, with: { (snapshot) in
if let dict2 = snapshot.value as? [String: AnyObject] {
self.postCount = dict2.count
}
print(self.postCount)
//everything within this closure will have the updated value of postCount
foo()
})
}
func foo() {
if self.postCount > 0 {
print("Lots of posts!")
}
else {
print("No posts :(")
}
}
Currently I am having multiple textfields in a view. If the user taps at one of them there should be a function responding to the event. Is there a way on how to do react (if a textfield got the focus)? I tried it with the NSTextFieldDelegate method but there is no appropriate function for this event.
This is how my code looks at the moment:
class ViewController: NSViewController, NSTextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let textField = NSTextField(frame: CGRectMake(10, 10, 37, 17))
textField.stringValue = "Label"
textField.bordered = false
textField.backgroundColor = NSColor.controlColor()
view.addSubview(textField)
textField.delegate = self
let textField2 = NSTextField(frame: CGRectMake(30, 30, 37, 17))
textField2.stringValue = "Label"
textField2.bordered = false
textField2.backgroundColor = NSColor.controlColor()
view.addSubview(textField2)
textField2.delegate = self
}
func control(control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool {
print("working") // this only works if the user enters a charakter
return true
}
}
The textShouldBeginEditing function only handles the event if the user tries to enter a character but this isn't what I want. It has to handle the event if he clicks on the textfield.
Any ideas, thanks a lot?
Edit
func myAction(sender: NSView)
{
print("aktuell: \(sender)")
currentObject = sender
}
This is the function I want to call.
1) Create a subclass of NSTextField.
import Cocoa
class MyTextField: NSTextField {
override func mouseDown(theEvent:NSEvent) {
let viewController:ViewController = ViewController()
viewController.textFieldClicked()
}
}
2) With Interface building, select the text field you want to have a focus on. Navigate to Custom Class on the right pane. Then set the class of the text field to the one you have just created.
3) The following is an example for ViewController.
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func textFieldClicked() -> Void {
print("You've clicked on me!")
}
}
4) Adding text fields programmatically...
import Cocoa
class ViewController: NSViewController {
let myField:MyTextField = MyTextField()
override func viewDidLoad() {
super.viewDidLoad()
//let myField:MyTextField = MyTextField()
myField.setFrameOrigin(NSMakePoint(20,70))
myField.setFrameSize(NSMakeSize(120,22))
let textField:NSTextField = NSTextField()
textField.setFrameOrigin(NSMakePoint(20,40))
textField.setFrameSize(NSMakeSize(120,22))
self.view.addSubview(myField)
self.view.addSubview(textField)
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func textFieldClicked() -> Void {
print("You've clicked on me!")
}
}
I know it’s been answered some while ago but I did eventually find this solution for macOS in Swift 3 (it doesn’t work for Swift 4 unfortunately) which notifies when a textfield is clicked inside (and for each key stroke).
Add this delegate to your class:-
NSTextFieldDelegate
In viewDidLoad() add these:-
imputTextField.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: Notification.Name.NSTextViewDidChangeSelection, object: nil)
Then add this function:-
func textDidChange(_ notification: Notification) {
print("Its come here textDidChange")
guard (notification.object as? NSTextView) != nil else { return }
let numberOfCharatersInTextfield: Int = textFieldCell.accessibilityNumberOfCharacters()
print("numberOfCharatersInTextfield = \(numberOfCharatersInTextfield)")
}
Hope this helps others.