iCarousel shows images only after resize - swift

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.

Related

Capture snapshot of unselected UITabBarItem?

I am writing some tutorial code for app, and would like to make screen shot of unselected tab bar item. Here is the code I have written so far:
extension UIView {
var globalFrame: CGRect {
superview?.convert(frame, to: nil) ?? .zero
}
}
extension UIView {
var snapshot: UIImage {
UIGraphicsImageRenderer(size: bounds.size).image { _ in
drawHierarchy(in: frame, afterScreenUpdates: false)
}
}
}
extension UITabBar {
func snapshotDataForTab(atIndex index: Int) -> (UIImage, CGRect) {
var tabs = subviews.compactMap { (view: UIView) -> UIControl? in
if let view = view as? UIControl {
return view
}
return nil
}
tabs.sort(by: { $0.frame.origin.x < $1.frame.origin.x })
return (tabs[index].snapshot, tabs[index].globalFrame)
}
}
Main controller:
class MainTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
selectedIndex = 0
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.presentTooltip(forTabAtIndex: 0)
}
}
private func presentTooltip(forTabAtIndex index: Int) {
let dimView = UIView(frame: view.frame)
dimView.backgroundColor = .red
view.addSubview(dimView)
let (image, frame) = tabBar.snapshotDataForTab(atIndex: index)
let imageView = UIImageView(image: image)
imageView.frame = frame
dimView.addSubview(imageView)
}
}
Interestingly enough everything works as expected when selectedIndex is 0 and I call presentTooltip for value 0. However if I call presentTooltip with 1, nothing is rendered. If I switch selectedIndex to 1, then it's reversed, and nothing gets rendered for presentTooltip with 0.
It seems I am unable to capture snapshot of inactive tab?
The snapshot function is buggy. I'm able to take snapshot of the unselected UITabbar item.
Below is the correct snapshot function:
var snapshot: UIImage {
let renderer = UIGraphicsImageRenderer(bounds: self.bounds)
return renderer.image { (context) in
self.layer.render(in: context.cgContext)
}
}
Check the project here Github Repo
This is the screenshot showing a snapshot of unselected UITabbaritem.

Can't generate PNG file from UIView snapshot

I want to generate a .png file from UIView snapshot but it's always nil. Could you give me a hint what's wrong with my code? I'm running Catalina 10.15.2 with Xcode 11.3 and iOS 13.3.
Here's my code's snippet:
extension UIView {
fileprivate func snapShot(view: UIView) -> UIView {
let snapshot = view.snapshotView(afterScreenUpdates: false)
self.addSubview(snapshot!)
return snapshot!
}
}
class ViewController: UIViewController {
var image: UIImage?
var imageView: UIImageView?
#IBAction func take(_ sender: UIButton) {
let oneThird = CGRect(x: 0,
y: 0,
width: view.frame.size.width / 3,
height: view.frame.size.height / 3)
let snap = view.snapShot(view: self.view)
snap.frame = oneThird
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let png = snap.largeContentImage?.pngData()
if let pngData = png {
self.image = UIImage(data: pngData)
}
self.imageView = UIImageView(image: self.image)
}
print(self.view.subviews.indices) // 0..<5
print(imageView?.image as Any) // NIL
}
}
Any help appreciated.
Try the following extension:
extension UIView {
func getImageFromCurrentContext(bounds: CGRect? = nil) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds?.size ?? self.bounds.size, false, 0.0)
self.drawHierarchy(in: bounds ?? self.bounds, afterScreenUpdates: true)
guard let currentImage = UIGraphicsGetImageFromCurrentImageContext() else {
return nil
}
UIGraphicsEndImageContext()
return currentImage
}
}
Pass the bounds if you would like 1/3 of the view or don't provide a bounds to get the image of the entire view.
Tested with sample code:
import UIKit
class ViewController: UIViewController {
#IBOutlet private(set) var testView: UIView!
#IBOutlet private(set) var testImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.testImageView.image = self.testView.getImageFromCurrentContext()
}
}
}
I made UIView on the storyboard have a sample label inside it and changed the background color just to verify it is in fact creating the image. Then added an UIImageView under that. Connected the UIView to testView and the UIImageView to testImageView.

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

Change search field's icon

I try to implement search behavior like in Xcode: if you enter something in search field, icon changes color.
I delegate both searchFieldDidStartSearching and searchFieldDidEndSearching to controller and change the image.
The problem is icon's image changes only when window lose it's focus.
class ViewController: NSViewController {
#IBOutlet weak var searchField: NSSearchField!
func searchFieldDidStartSearching(_ sender: NSSearchField) {
print("\(#function)")
(searchField.cell as! NSSearchFieldCell).searchButtonCell?.image = NSImage.init(named: "NSActionTemplate")
}
func searchFieldDidEndSearching(_ sender: NSSearchField) {
print("\(#function)")
(searchField.cell as! NSSearchFieldCell).searchButtonCell?.image = NSImage.init(named: "NSHomeTemplate")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Thanks in advance for any ideas/suggestions.
Although I don't know the reason, it works:
NSApp.mainWindow?.resignMain()
NSApp.mainWindow?.becomeMain()
Here is the whole code:
class MyViewController: NSViewController {
private lazy var searchField: NSSearchField = {
let searchField = NSSearchField(string: "")
if let searchButtonCell = searchField.searchButtonCell {
searchButtonCell.setButtonType(.toggle)
let filterImage = #imageLiteral(resourceName: "filter")
searchButtonCell.image = filterImage.tinted(with: .systemGray)
searchButtonCell.alternateImage = filterImage.tinted(with: .systemBlue)
}
searchField.focusRingType = .none
searchField.bezelStyle = .roundedBezel
searchField.delegate = self
return searchField
}()
...
}
extension MyViewController: NSSearchFieldDelegate {
func searchFieldDidStartSearching(_ sender: NSSearchField) {
sender.searchable = true
}
func searchFieldDidEndSearching(_ sender: NSSearchField) {
sender.searchable = false
}
}
extension NSSearchField {
var searchButtonCell: NSButtonCell? {
(self.cell as? NSSearchFieldCell)?.searchButtonCell
}
var searchable: Bool {
get {
self.searchButtonCell?.state == .on
}
set {
self.searchButtonCell?.state = newValue ? .on : .off
self.refreshSearchIcon()
}
}
private func refreshSearchIcon() {
NSApp.mainWindow?.resignMain()
NSApp.mainWindow?.becomeMain()
}
}
extension NSImage {
func tinted(with color: NSColor) -> NSImage? {
guard let image = self.copy() as? NSImage else { return nil }
image.lockFocus()
color.set()
NSRect(origin: NSZeroPoint, size: self.size).fill(using: .sourceAtop)
image.unlockFocus()
image.isTemplate = false
return image
}
}
I was having the same issue. A simple override fixed this issue for me
extension NSSearchField{
open override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
}
As you can see when you click inside the view it's still focussed on the search text field(as you can still type in it after you clicked underneath it). Since the change image is on when it loses focus, you should check if you clicked outside of the text field.
Solve problem by subclassing NSSearchFieldCell and assign this class to field's cell.
You don't even need to subclass NSSearchFieldCell.
When you create your NSSearchField from code, you can do something like this:
if let searchFieldCell = searchField.cell as? NSSearchFieldCell {
let image = NSImage(named: "YourImageName")
searchFieldCell.searchButtonCell?.image = image
searchFieldCell.searchButtonCell?.alternateImage = image // Optionally
}
If you're using storyboards, you can do the same in didSet of your #IBOutlet.