Initializers of a Class Extending UICollectionView (Swift 3.2) - swift

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)
}
}

Related

UIPageViewController + firebase

I'm using UIPageViewController to make book pages and the pages are images that will be fetched from the firebase. the problem is I got this error
(Cannot convert value of type 'books' to expected element type 'Array.ArrayLiteralElement' (aka 'UIViewController')
'books' is a struct I made for firebase
import Foundation
import Firebase
struct books {
let bookId: String
let imageURL: String
}
extension booksC {
init(_ dictionary: [String: Any]) {
self.bookId = dictionary["bookId"] as? String ?? "no book id"
self.imageURL = dictionary["imageURL"] as? String ?? "no image url"
}
}
I'm using this code, what I understand is I can't use an array of images what I should use is an array of controllers and I don't know how to do that in this case. help is appreciated
import UIKit
import FirebaseFirestore
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
let pageViewController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil)
private var pages = [books]()
private var listener: ListenerRegistration?
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
// index
guard let pagesFirst = pages.last else { return }
setViewControllers([pagesFirst], direction: .reverse, animated: true)
//TODO: add listener
}
func pageViewController(_ pageViewController: UIPageViewController, spineLocationFor orientation: UIInterfaceOrientation) -> UIPageViewController.SpineLocation {
var location = SpineLocation.max
if orientation == .landscapeLeft || orientation == .landscapeRight {
self.pageViewController.isDoubleSided = true
location = .mid
}
return location
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let index = self.pages.firstIndex(where: {$0 == viewController}) ?? 0
if index == 0 { return nil }
return pages[index - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let index = self.pages.firstIndex(where: {$0 == viewController}) ?? 0
if index == pages.count - 1 { return nil }
return pages[index + 1]
}
}
class ImageViewController: UIViewController {
let imageView = UIImageView()
init(image: UIImage) {
imageView.image = image
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageView)
imageView.fillSuperview()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.backgroundColor = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func setupImage(for book: books){
imageView.kf.setImage(with: URL(string: book.imageURL))
}
}
enter code hereyou are using your array private var pages = [books]() in setViewControllers which expect a UIViewController
This is how setViewControllers is declared:
func setViewControllers(_ viewControllers: [UIViewController]?,
direction: UIPageViewController.NavigationDirection,
animated: Bool,
completion: ((Bool) -> Void)? = nil)
Parameters
viewControllers
The view controller or view controllers to be displayed.
direction
The navigation direction.
animated
A Boolean value that indicates whether the transition is to be animated.
I think you should create an instance of ImageViewController then set the image using setupImage(for book: books) (which you can access since is a public method and finally assign that view controller your the page controller:
guard let pagesFirst = pages.last else { return }
let imageViewController = ImageViewController()
imageViewController.setupImage(for book: pagesFirst)
setViewControllers([imageViewController], direction: .reverse, animated: true)
EDITED:
Sorry, as far as the second error you get, it should be
imageViewController.setupImage(for:pagesFirst)
as for is the name available outside the class.
For the first error, what init options does it give you. It should be correct.
Try also to change your code declaring imageView this way :
var imageView : UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false // required if you need to work with autolayout
return imageView
}()
removing:
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
lastly, you should name your struct book with a capital B.

Unit testing a viewController with constructor dependency

I want to write a simple unit test for the viewController described below that has constructor dependency. Unfortunately one of the parameters is of type NSCoder. How do I create the NSCoder argument while testing? As I get this error message.
caught "NSInvalidArgumentException", "*** -decodeObjectForKey: only defined for abstract class. Define -[NSKeyedArchiver decodeObjectForKey:]!"
online research: deprecated solution stackoverflow
ViewModel
class ViewModel {
private let response: DataResponse
private var name = "Character name"
init(reponse: Result) {
self.reponse = response
}
var fullName: String {
return name
}
fund getData() {
if self.result.name.count > 0 {
self.name = self.result.name
}
}
}
ViewController
class ViewController: UIViewController {
#IBOutlet weak var name: UILabel!
private var viewModel: ViewModel
init?(viewModel: ViewModel, coder: NSCoder) {
self.viewModel = viewModel
super.init(coder: coder)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel.getData()
characterNameLabel.text = viewModel.fullName
}
}
The method that shows viewController
#IBSegueAction func showViewController(coder: NSCoder) ->
ViewController? {
guard let selectedRow = tableView.indexPathForSelectedRow?.row else {
return nil
}
let singleResult = specialViewModel.results(at: selectedRow)
let viewModel = ViewModel(result: singleResult)
return ViewController(viewModel: viewModel, coder: coder)
}
Testcase snippet
class ViewControllerTest: XCTestCase {
var viewControllerUnderTest: ViewController!
var viewModel: ViewModel!
override func setUp() {
super.setUp()
let data = PagedResponse.stub().data.results[0]
self.viewModel = ViewModel(result: data)
self.viewModel.getData()
let coder = "??" // how do I create coder
self.viewControllerUnderTest = ViewControllerUnderTest(viewModel: viewModel, coder: coder)
self.viewControllerUnderTest.loadView()
self.viewControllerUnderTest.viewDidLoad()
}
override func tearDown() {
super.tearDown()
}
}

How do I draw text into an NSImage?

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)

context.fill() doesn't show up Swift 4 Xcode 10

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?()
}
}

iCarousel shows images only after resize

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.