How do I get a layer backed NSView to write to PDF in the clipboard - layer

I am using a layer backed view with a CGImage and would like to use the NSView.writePDF(inside:to) API to copy the views image to the clipboard. The view has subviews that I would like included in the PDF.
I have created a Xcode playground to illustrate the problem.
//: A Cocoa based Playground to test NSView.writePDF()
import AppKit
import PlaygroundSupport
class BaseView: NSView {
var isSelected: Bool = false {
didSet {
self.needsDisplay = true
}
}
var fillColor = NSColor.yellow
var cgFillColor: CGColor {
return self.isSelected ? fillColor.cgColor : fillColor.withAlphaComponent(0.5).cgColor
}
var storeLayoutCGImageRef: CGImage? {
didSet {
self.layer?.contents = storeLayoutCGImageRef
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
context.setFillColor(cgFillColor)
context.fill(dirtyRect)
}
override func mouseDown(with theEvent: NSEvent) {
self.isSelected = !self.isSelected
}
#objc func copyImage(_ sender: Any){
let pb = NSPasteboard.general
pb.declareTypes([.pdf], owner: self)
self.writePDF(inside: self.bounds, to: pb)
}
#objc func doNothing(_ sender: Any){
}
// Load image in the background
func loadImage(completion: #escaping ()->Void){
DispatchQueue.global().async {
if let image = NSImage(named: "B-019969.jpg") {
// Lets use double the size ?
var rect = CGRect(x: 0, y: 0, width: image.size.width*3.0, height: image.size.height*3.0)
let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil)
DispatchQueue.main.async {
self.storeLayoutCGImageRef = cgImage
completion()
}
return
}
DispatchQueue.main.async {
//self.storeLayoutImage = nil
self.storeLayoutCGImageRef = nil
completion()
}
}
}
}
class ModuleView: BaseView {
let color = NSColor.purple
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
class ShelfView: BaseView {
let color = NSColor.yellow
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
self.loadImage {
print("Image loaded")
}
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
self.layer?.backgroundColor = self.cgFillColor
self.layer?.contents = self.storeLayoutCGImageRef
}
}
class view: BaseView
{
var module: ModuleView?
let color = NSColor.cyan
override init(frame: NSRect)
{
super.init(frame: frame)
self.fillColor = color
self.addModules()
self.addShelves()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addModules(){
let module = ModuleView()
module.frame = NSRect(x: 10.0, y: 10.0, width: 180.0, height: 180.0)
self.module = module
self.addSubview(module)
}
func addShelves(){
let shelf = ShelfView()
shelf.frame = NSRect(x: 10.0, y: 10.0, width:160.0, height: 160.0)
self.module?.addSubview(shelf)
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
var v = view(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v
Is there a way to make sure the layer gets included in the PDF output?

OK, I just figured it out - basically you have to implement the draw(_ dirtyRect) function which gets called during printing or when the NSView.writePDF() is called.
Note that the draw() function won't get called under certain conditions - e.g. selecting the item with the mouse will only result in the updateLayer() method being called.
Here is the updated playground, hope this saves someone else some time.
//: A Cocoa based Playground to test NSView.writePDF()
import AppKit
import PlaygroundSupport
class BaseView: NSView {
var isSelected: Bool = false {
didSet {
self.needsDisplay = true
}
}
var fillColor = NSColor.yellow
var cgFillColor: CGColor {
return self.isSelected ? fillColor.cgColor : fillColor.withAlphaComponent(0.5).cgColor
}
var storeLayoutCGImageRef: CGImage? {
didSet {
self.layer?.contents = storeLayoutCGImageRef
}
}
var image: CGImage?
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
// NB THIS PART !!
self.layer?.render(in:context)
}
override func mouseDown(with theEvent: NSEvent) {
self.isSelected = !self.isSelected
}
#objc func copyImage(_ sender: Any){
let pb = NSPasteboard.general
pb.declareTypes([.pdf], owner: self)
self.writePDF(inside: self.bounds, to: pb)
}
#objc func doNothing(_ sender: Any){
}
// Load image in the background
func loadImage(completion: #escaping ()->Void){
DispatchQueue.global().async {
if let image = NSImage(named: "Sample Image.png") {
// Lets use double the size ?
var rect = CGRect(x: 0, y: 0, width: image.size.width*3.0, height: image.size.height*3.0)
let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil)
DispatchQueue.main.async {
self.storeLayoutCGImageRef = cgImage
completion()
}
return
}
DispatchQueue.main.async {
//self.storeLayoutImage = nil
self.storeLayoutCGImageRef = nil
completion()
}
}
}
}
class ModuleView: BaseView {
let color = NSColor.purple
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
class ShelfView: BaseView {
let color = NSColor.green
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
self.loadImage {
print("Image loaded")
}
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
override func draw(_ dirtyRect: NSRect) {
print("ShelfView.draw() called")
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
if let image = self.storeLayoutCGImageRef {
context.draw(image, in: self.bounds)
}
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
print("updateLayer() called")
self.layer?.backgroundColor = self.cgFillColor
self.layer?.contents = self.storeLayoutCGImageRef
}
}
class view: BaseView
{
var module: ModuleView?
let color = NSColor.cyan
override init(frame: NSRect)
{
super.init(frame: frame)
self.fillColor = color
self.addModules()
self.addShelves()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addModules(){
let module = ModuleView()
module.frame = NSRect(x: 10.0, y: 10.0, width: 180.0, height: 180.0)
self.module = module
self.addSubview(module)
}
func addShelves(){
let shelf = ShelfView()
shelf.frame = NSRect(x: 10.0, y: 10.0, width:160.0, height: 160.0)
self.module?.addSubview(shelf)
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
var v = view(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v

Related

Why is my UICollectionView not loading cells?

I have a view controller in my app that plays user videos and you can scroll to the right to go to the next video. I have a cell set up that loads the video from the URL in firebase storage and loads the other data. Here is the cell code:
import UIKit
import AVFoundation
protocol ClipsCollectionViewCellDelegate: AnyObject {
func didTapProfile(with model: VideoModel)
func didTapShare(with model: VideoModel)
func didTapNewClip(with model: VideoModel)
}
class ClipsCollectionViewCell: UICollectionViewCell {
static let identifier = "ClipsCollectionViewCell"
// Labels
private let usernameLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.textColor = UIColor.systemPink.withAlphaComponent(0.5)
label.backgroundColor = UIColor.systemPink.withAlphaComponent(0.1)
label.clipsToBounds = true
label.layer.cornerRadius = 8
return label
}()
// Buttons
private let profileButton: UIButton = {
let button = UIButton()
button.setBackgroundImage(UIImage(systemName: "person.circle"), for: .normal)
button.tintColor = .white
button.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.1)
button.clipsToBounds = true
button.layer.cornerRadius = 32
button.isUserInteractionEnabled = true
return button
}()
private let shareButton: UIButton = {
let button = UIButton()
button.setBackgroundImage(UIImage(systemName: "square.and.arrow.down"), for: .normal)
button.tintColor = .white
button.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.1)
button.clipsToBounds = true
button.layer.cornerRadius = 4
button.isUserInteractionEnabled = true
return button
}()
private let newClipButton: UIButton = {
let button = UIButton()
button.setBackgroundImage(UIImage(systemName: "plus"), for: .normal)
button.tintColor = .systemOrange
button.backgroundColor = UIColor.systemOrange.withAlphaComponent(0.1)
button.clipsToBounds = true
button.layer.cornerRadius = 25
button.isUserInteractionEnabled = true
return button
}()
private let videoContainer = UIView()
// Delegate
weak var delegate: ClipsCollectionViewCellDelegate?
// Subviews
var player: AVPlayer?
private var model: VideoModel?
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .black
contentView.clipsToBounds = true
addSubviews()
}
private func addSubviews() {
contentView.addSubview(videoContainer)
contentView.addSubview(usernameLabel)
contentView.addSubview(profileButton)
contentView.addSubview(shareButton)
contentView.addSubview(newClipButton)
// Add actions
profileButton.addTarget(self, action: #selector(didTapProfileButton), for: .touchUpInside)
shareButton.addTarget(self, action: #selector(didTapShareButton), for: .touchUpInside)
newClipButton.addTarget(self, action: #selector(didTapNewClipButton), for: .touchUpInside)
videoContainer.clipsToBounds = true
contentView.sendSubviewToBack(videoContainer)
}
#objc private func didTapProfileButton() {
guard let model = model else {
return
}
delegate?.didTapProfile(with: model)
}
#objc private func didTapShareButton() {
guard let model = model else {
return
}
delegate?.didTapShare(with: model)
}
#objc private func didTapNewClipButton() {
guard let model = model else {
return
}
delegate?.didTapNewClip(with: model)
}
override func layoutSubviews() {
super.layoutSubviews()
videoContainer.frame = contentView.bounds
let size = contentView.frame.size.width/7
let width = contentView.frame.size.width
let height = contentView.frame.size.height
// Labels
usernameLabel.frame = CGRect(x: (width-(size*3))/2, y: height-880-(size/2), width: size*3, height: size)
// Buttons
profileButton.frame = CGRect(x: width-(size*7), y: height-850-size, width: size, height: size)
shareButton.frame = CGRect(x: width-size, y: height-850-size, width: size, height: size)
newClipButton.frame = CGRect(x: width-size-10, y: height-175-size, width: size/1.25, height: size/1.25)
}
override func prepareForReuse() {
super.prepareForReuse()
usernameLabel.text = nil
player?.pause()
player?.seek(to: CMTime.zero)
}
public func configure(with model: VideoModel) {
self.model = model
configureVideo()
// Labels
usernameLabel.text = model.username
}
private func configureVideo() {
guard let model = model else {
return
}
guard let url = URL(string: model.videoFileURL) else { return }
player = AVPlayer(url: url)
let playerView = AVPlayerLayer()
playerView.player = player
playerView.frame = contentView.bounds
playerView.videoGravity = .resizeAspect
videoContainer.layer.addSublayer(playerView)
player?.volume = 0
player?.play()
player?.actionAtItemEnd = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is the view controller code:
import UIKit
struct VideoModel {
let username: String
let videoFileURL: String
}
class BetaClipsViewController: UIViewController, UICollectionViewDelegate {
private var collectionView: UICollectionView?
private var data = [VideoModel]()
/// Notification observer
private var observer: NSObjectProtocol?
/// All post models
private var allClips: [(clip: Clip, owner: String)] = []
private var viewModels = [[ClipFeedCellType]]()
override func viewDidLoad() {
super.viewDidLoad()
title = ""
// for _ in 0..<10 {
// let model = VideoModel(username: "#CJMJM",
// videoFileURL: "https://firebasestorage.googleapis.com:443/v0/b/globe-e8b7f.appspot.com/o/clipvideos%2F1637024382.mp4?alt=media&token=c12d0481-f834-4a17-8eee-30595bdf0e8b")
// data.append(model)
// }
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: view.frame.size.width,
height: view.frame.size.height)
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.register(ClipsCollectionViewCell.self,
forCellWithReuseIdentifier: ClipsCollectionViewCell.identifier)
collectionView?.isPagingEnabled = true
collectionView?.delegate = self
collectionView?.dataSource = self
view.addSubview(collectionView!)
fetchClips()
observer = NotificationCenter.default.addObserver(
forName: .didPostNotification,
object: nil,
queue: .main
) { [weak self] _ in
self?.viewModels.removeAll()
self?.fetchClips()
}
self.collectionView?.reloadData()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView?.frame = view.bounds
}
private func fetchClips() {
guard let username = UserDefaults.standard.string(forKey: "username") else {
return
}
let userGroup = DispatchGroup()
userGroup.enter()
var allClips: [(clip: Clip, owner: String)] = []
DatabaseManager.shared.users(for: username) { usernames in
defer {
userGroup.leave()
}
let users = usernames + [username]
for current in users {
userGroup.enter()
DatabaseManager.shared.clips(for: current) { result in
DispatchQueue.main.async {
defer {
userGroup.leave()
}
switch result {
case .success(let clips):
allClips.append(contentsOf: clips.compactMap({
(clip: $0, owner: current)
}))
case .failure:
break
}
}
}
}
}
userGroup.notify(queue: .main) {
let group = DispatchGroup()
self.allClips = allClips
allClips.forEach { model in
group.enter()
self.createViewModel(
model: model.clip,
username: model.owner,
completion: { success in
defer {
group.leave()
}
if !success {
print("failed to create VM")
}
}
)
}
group.notify(queue: .main) {
self.collectionView?.reloadData()
}
}
}
}
extension BetaClipsViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return viewModels.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModels[section].count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellType = viewModels[indexPath.section][indexPath.row]
switch cellType {
case .clip(let viewModel):
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ClipsCollectionViewCell.identifier,
for: indexPath)
as? ClipsCollectionViewCell else {
fatalError()
}
cell.delegate = self
cell.configure(with: viewModel)
return cell
}
}
}
extension BetaClipsViewController: ClipsCollectionViewCellDelegate {
func didTapProfile(with model: VideoModel) {
print("profile tapped")
let owner = model.username
DatabaseManager.shared.findUser(username: owner) { [weak self] user in
DispatchQueue.main.async {
guard let user = user else {
return
}
let vc = ProfileViewController(user: user)
self?.navigationController?.pushViewController(vc, animated: true)
}
}
}
func didTapShare(with model: VideoModel) {
print("profile share")
}
func didTapNewClip(with model: VideoModel) {
let vc = RecordViewController()
navigationController?.pushViewController(vc, animated: true)
}
}
extension BetaClipsViewController {
func createViewModel(
model: Clip,
username: String,
completion: #escaping (Bool) -> Void
) {
StorageManager.shared.profilePictureURL(for: username) { [weak self] profilePictureURL in
guard let clipURL = URL(string: model.clipUrlString),
let profilePhotoUrl = profilePictureURL else {
return
}
let clipData: [ClipFeedCellType] = [
.clip(viewModel: VideoModel(username: username,
videoFileURL: model.clipUrlString))
]
self?.viewModels.append(clipData)
completion(true)
}
}
}
I think everything is set up properly in the code so why do no cells show in the collectionView? The view controller shows up as completely blank besides the background color.

CollectionView FlowLayout custom cell rendering issues

I have a collectionview with a custom flowlayout and a custom collectionview cell (no storyboards). The custom cell has a CAGradientLayer on a background view. When coming back from suspended state or on traitcollection change this layer is rendered incorrectly (see image:) ) It should be the full width of the cell.
Also when scrolling to off screen items below, the gradient layer isn't rendered at all?
Rotating the device once, or scrolling resolves the issue ...
I'm not sure if this is solvable in the custom cell class or in the collectionview viewcontroller. Reuse issue?
Any help greatly appreciated!
NOTE: Universal app, both ipad and iphone, also split screen compatible.
The cell class
class NormalProjectCell: UICollectionViewCell, SelfConfiguringProjectCell {
//MARK: - Properties
let titleLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .title3), andColor: .label)
let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .caption1), andColor: .secondaryLabel)
let imageView = ProjectImageView(frame: .zero)
var stackView = UIStackView()
var backgroundMaskedView = UIView()
//MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 35
let seperator = Separator(frame: .zero)
stackView = UIStackView(arrangedSubviews: [seperator, titleLabel, lastEditedLabel, imageView])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.spacing = 5
stackView.setCustomSpacing(10, after: lastEditedLabel)
stackView.insertSubview(backgroundMaskedView, at: 0)
contentView.addSubview(stackView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
NSLayoutConstraint.activate([
titleLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 20),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
backgroundMaskedView.translatesAutoresizingMaskIntoConstraints = false
backgroundMaskedView.backgroundColor = .tertiarySystemBackground
backgroundMaskedView.pinToEdges(of: stackView)
let gradientMaskLayer = CAGradientLayer()
gradientMaskLayer.frame = backgroundMaskedView.bounds
gradientMaskLayer.colors = [UIColor.systemPurple.cgColor, UIColor.clear.cgColor]
gradientMaskLayer.locations = [0, 0.4]
backgroundMaskedView.layer.mask = gradientMaskLayer
}
//MARK: - Configure
func configure(with project: ProjectsController.Project) {
titleLabel.text = project.title
lastEditedLabel.text = project.lastEdited.customMediumToString
imageView.image = Bundle.getProjectImage(project: project)
}
}
and the viewcontroller with the collectionView:
class ProjectsViewController: UIViewController {
//MARK: - Types
enum Section: CaseIterable {
case normal
}
//MARK: - Properties
let projectsController = ProjectsController()
var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<Section, Project>!
var lastScrollPosition: CGFloat = 0
var isSearching = false
let searchController = UISearchController()
//MARK: - ViewController Methods
override func viewDidLoad() {
super.viewDidLoad()
configureViewController()
configureSearchController()
configureCollectionView()
createDataSource()
updateData(on: projectsController.filteredProjects())
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isSearching {
isSearching.toggle()
searchController.searchBar.text = ""
searchController.resignFirstResponder()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString(string: "Title or details text ...",
attributes: [NSAttributedString.Key.foregroundColor: UIColor.secondaryLabel])
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
collectionView.collectionViewLayout = UICollectionView.createFlexibleFlowLayout(in: view)
}
//MARK: - DataSource
func createDataSource() {
dataSource = UICollectionViewDiffableDataSource<Section, Project>(collectionView: collectionView) { (collectionView, indexPath, project) in
return self.configure(NormalProjectCell.self, with: project, for: indexPath)
}
}
func updateData(on projects: [Project]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Project>()
snapshot.appendSections([Section.normal])
snapshot.appendItems(projects)
//apply() is safe to call from a background queue!
self.dataSource.apply(snapshot, animatingDifferences: true)
}
///Configure any type of cell that conforms to selfConfiguringProjectCell!
func configure<T: SelfConfiguringProjectCell>(_ cellType: T.Type, with project: Project, for indexPath: IndexPath) -> T {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else {
fatalError("Unable to dequeue \(cellType)")
}
cell.configure(with: project)
return cell
}
//MARK: - Actions
#objc func addButtonTapped() {
let project = Project()
let viewController = ProjectDetailsViewController(withProject: project)
viewController.delegate = self
navigationController?.pushViewController(viewController, animated: true)
}
#objc private func tapAndHoldCell(recognizer: UILongPressGestureRecognizer) {
if recognizer.state == .ended {
guard let indexPath = collectionView.indexPathForItem(at: recognizer.location(in: self.collectionView)),
let project = dataSource?.itemIdentifier(for: indexPath) else {
return
}
let viewController = ProjectDetailsViewController(withProject: project)
viewController.delegate = self
navigationController?.pushViewController(viewController, animated: true)
}
}
#objc private func swipeFromRightOnCell(recognizer: UISwipeGestureRecognizer) {
if recognizer.state == .ended {
guard let indexPath = collectionView.indexPathForItem(at: recognizer.location(in: self.collectionView)),
let cell = collectionView.cellForItem(at: indexPath),
let project = dataSource?.itemIdentifier(for: indexPath) else {
return
}
let overlay = ProjectCellDeletionOverlay(frame: CGRect(x: cell.bounds.width, y: 0, width: 0, height: cell.bounds.height))
cell.addSubview(overlay)
UIView.animate(withDuration: 0.70, animations: {
overlay.backgroundColor = UIColor.red.withAlphaComponent(0.60)
overlay.frame = CGRect(x: cell.bounds.width / 2, y: 0, width: cell.bounds.width / 2, height: cell.bounds.height)
}) { _ in
self.presentProjectAlertOnMainThread(withTitle: "Delete this Project?",
andMessage: "Are you sure?\nThis cannot be undone!\nAll associated notes will also be deleted!",
andDismissButtonTitle: "Cancel",
andConfirmButtonTitle: "Delete!",
completion: { success in
if success {
UIView.animate(withDuration: 1.40, animations: {
overlay.frame = CGRect(x: 0, y: 0, width: cell.bounds.width, height: cell.bounds.height)
cell.alpha = 0
}) { _ in
self.delete(project)
overlay.removeFromSuperview()
}
} else {
UIView.animate(withDuration: 1.5, animations: {
overlay.frame = CGRect(x: cell.bounds.width, y: 0, width: 0, height: cell.bounds.height)
overlay.alpha = 0
}) { _ in
overlay.removeFromSuperview()
}
}
})
}
}
}
///Will show an overlay view with help text on the app
#objc private func showHelpView() {
let helpViewController = AppHelpViewController(with: HelpViewDisplayTextFor.projects)
helpViewController.modalTransitionStyle = .flipHorizontal
helpViewController.modalPresentationStyle = .fullScreen
present(helpViewController, animated: true)
}
///Will show a menu with several options
#objc private func showMenu() {
}
//MARK: - UI & Layout
private func configureViewController() {
view.backgroundColor = .systemPurple
title = "Projects"
navigationController?.navigationBar.prefersLargeTitles = false
let menu = UIBarButtonItem(image: ProjectImages.BarButton.menu, style: .plain, target: self, action: #selector(showMenu))
let add = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped))
navigationItem.leftBarButtonItems = [menu, add]
let questionMark = UIBarButtonItem(image: ProjectImages.BarButton.questionmark, style: .plain, target: self, action: #selector(showHelpView))
navigationItem.rightBarButtonItem = questionMark
}
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UICollectionView.createFlexibleFlowLayout(in: view))
collectionView.delegate = self
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.backgroundColor = .clear
view.addSubview(collectionView)
collectionView.register(NormalProjectCell.self, forCellWithReuseIdentifier: NormalProjectCell.reuseIdentifier)
let tapAndHold = UILongPressGestureRecognizer(target: self, action: #selector(tapAndHoldCell))
tapAndHold.minimumPressDuration = 0.3
collectionView.addGestureRecognizer(tapAndHold)
let swipeFromRight = UISwipeGestureRecognizer(target: self, action: #selector(swipeFromRightOnCell) )
swipeFromRight.direction = UISwipeGestureRecognizer.Direction.left
collectionView.addGestureRecognizer(swipeFromRight)
}
private func configureSearchController() {
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
navigationItem.searchController = searchController
//CollectionView under searchbar fix ???
searchController.extendedLayoutIncludesOpaqueBars = true
// searchController.edgesForExtendedLayout = .top
}
}
//MARK: - Ext CollectionView Delegate
extension ProjectsViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let project = dataSource?.itemIdentifier(for: indexPath) else { return }
ProjectsController.activeProject = project
let loadingView = showLoadingView(for: project)
let viewController = SplitOrFlipContainerController()
UIView.animate(withDuration: 1.5, animations: {
loadingView.alpha = 1
}) { (complete) in
self.dismiss(animated: false) {
self.present(viewController, animated: false)
}
}
}
}
//MARK: - Ext Search Results & Bar
extension ProjectsViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let filter = searchController.searchBar.text, filter.isNotEmpty else {
isSearching = false
updateData(on: projectsController.filteredProjects())
return
}
isSearching = true
updateData(on: projectsController.filteredProjects(with: filter.lowercased()))
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
lastScrollPosition = scrollView.contentOffset.y
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if lastScrollPosition < scrollView.contentOffset.y {
navigationItem.hidesSearchBarWhenScrolling = true
} else if lastScrollPosition > scrollView.contentOffset.y {
navigationItem.hidesSearchBarWhenScrolling = false
}
}
}
//MARK: - ProjectHandler
extension ProjectsViewController: ProjectHandler {
internal func save(_ project: Project, withImage image: UIImage?) {
//call save and update the snapshot
projectsController.save(project, withImage: image)
updateData(on: projectsController.filteredProjects())
collectionView.reloadData()
}
internal func delete(_ project: Project) {
//call delete and update the snapshot
projectsController.delete(project)
updateData(on: projectsController.filteredProjects())
}
}
And the flow layout:
extension UICollectionView {
///Flow layout with minimum 2 items across, with padding and spacing
static func createFlexibleFlowLayout(in view: UIView) -> UICollectionViewFlowLayout {
let width = view.bounds.width
let padding: CGFloat
let minimumItemSpacing: CGFloat
let availableWidth: CGFloat
let itemWidth: CGFloat
if view.traitCollection.verticalSizeClass == .compact {
print("//iPhones landscape")
padding = 12
minimumItemSpacing = 12
availableWidth = width - (padding * 2) - (minimumItemSpacing * 3)
itemWidth = availableWidth / 4
} else if view.traitCollection.horizontalSizeClass == .compact && view.traitCollection.verticalSizeClass == .regular {
print("//iPhones portrait")
padding = 12
minimumItemSpacing = 12
availableWidth = width - (padding * 2) - (minimumItemSpacing)
itemWidth = availableWidth / 2
} else {
print("//iPads")
padding = 24
minimumItemSpacing = 24
availableWidth = width - (padding * 2) - (minimumItemSpacing * 3)
itemWidth = availableWidth / 4
}
let flowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
flowLayout.itemSize = CGSize(width: itemWidth, height: itemWidth + 40)
flowLayout.sectionHeadersPinToVisibleBounds = true
return flowLayout
}
}
Thanks to some help from #nemecek_filip at the HWS forums, I got it solved!
Different approach to the gradient using a custom gradient view!
Hereby the changed and working code:
collectionView cell:
//
// NormalProjectCell.swift
//
import UIKit
class NormalProjectCell: UICollectionViewCell, SelfConfiguringProjectCell {
//MARK: - Properties
let titleLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .title3), andColor: .label)
let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .caption1), andColor: .secondaryLabel)
let imageView = ProjectImageView(frame: .zero)
var stackView = UIStackView()
var backgroundMaskedView = GradientView()
//MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 35
let seperator = Separator(frame: .zero)
stackView = UIStackView(arrangedSubviews: [seperator, titleLabel, lastEditedLabel, imageView])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.spacing = 5
stackView.setCustomSpacing(10, after: lastEditedLabel)
stackView.insertSubview(backgroundMaskedView, at: 0)
contentView.addSubview(stackView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
NSLayoutConstraint.activate([
titleLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 20),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
backgroundMaskedView.translatesAutoresizingMaskIntoConstraints = false
backgroundMaskedView.pinToEdges(of: stackView)
}
//MARK: - Configure
func configure(with project: ProjectsController.Project) {
titleLabel.text = project.title
lastEditedLabel.text = project.lastEdited.customMediumToString
imageView.image = Bundle.getProjectImage(project: project)
}
}
And the GradientView:
//
// GradientView.swift
//
import UIKit
class GradientView: UIView {
var topColor: UIColor = UIColor.tertiarySystemBackground
var bottomColor: UIColor = UIColor.systemPurple
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override func layoutSubviews() {
(layer as! CAGradientLayer).colors = [topColor.cgColor, bottomColor.cgColor]
(layer as! CAGradientLayer).locations = [0.0, 0.40]
}
}

IBDesignable for UIButton, IBInspectable vars are nil

I am having a strange problem with UIButton. I have the following custom class:
import UIKit
#IBDesignable class ToggleButton: UIButton {
#IBInspectable var state1Image: UIImage = UIImage()
#IBInspectable var state2Image: UIImage = UIImage()
#IBInspectable var someString: String = ""
private var toogleOn: Bool = false {
didSet {
if toogleOn {
isSelected = true
} else {
isSelected = false
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
print("test")
print("some string is \(someString)")
}
#objc func didToggleButton() {
toogleOn = !toogleOn
}
}
In the interface builder I set the inspectable vars, let's say I set someString to hello. Now when I run the app and view the log the print for the var is "". Also I am unable to set the images. It only uses the default values and will not use the new value that I set. What am I doing wrong here?
Try this:
#IBDesignable class ToggleButton: UIButton {
#IBInspectable var state1Image: UIImage = UIImage() {
didSet {
setup()
}
}
#IBInspectable var state2Image: UIImage = UIImage() {
didSet {
setup()
}
}
#IBInspectable var someString: String = "" {
didSet {
setup()
}
}
override func prepareForInterfaceBuilder() {
setup()
}
private func setup() {
print("test")
// Updating title label as someString to see the update
self.titleLabel?.text = someString
}
private var toogleOn: Bool = false {
didSet {
if toogleOn {
isSelected = true
} else {
isSelected = false
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
#objc func didToggleButton() {
toogleOn = !toogleOn
}
}

How to search for location and display results with MapKit Swift 4

I am developing an application with Swift 4 (Xcode 9.2).
I followed this tutorial. I get my location my location but when I search for a place, the map was hidden: Search a place and it reappear just when I delete all what I write in the search bar as the original one(just My location).
Here is my code :
protocol HandleMapSearch: class {
func dropPinZoomIn(placemark:MKPlacemark)
}
class ViewController: UIViewController {
var selectedPin: MKPlacemark?
var resultSearchController: UISearchController!
let locationManager = CLLocationManager()
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
let locationSearchTable = storyboard!.instantiateViewController(withIdentifier: "LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController.searchResultsUpdater = locationSearchTable as UISearchResultsUpdating
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for places"
navigationItem.titleView = resultSearchController?.searchBar
resultSearchController.hidesNavigationBarDuringPresentation = false
resultSearchController.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
locationSearchTable.mapView = mapView
locationSearchTable.handleMapSearchDelegate = self
}
#objc func getDirections(){
guard let selectedPin = selectedPin else { return }
let mapItem = MKMapItem(placemark: selectedPin)
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
mapItem.openInMaps(launchOptions: launchOptions)
}
}
extension ViewController : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("error:: \(error.localizedDescription)")
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locations.first != nil {
print("location:: (location)")
}
}
}
extension ViewController: HandleMapSearch {
func dropPinZoomIn(placemark: MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
mapView.removeAnnotations(mapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "\(city) \(state)"
}
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegionMake(placemark.coordinate, span)
mapView.setRegion(region, animated: true)
}
}
extension ViewController : MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView?{
guard !(annotation is MKUserLocation) else { return nil }
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
}
pinView?.pinTintColor = UIColor.orange
pinView?.canShowCallout = true
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare))
//button.setBackgroundImage(UIImage(named: "car"), for: .Normal)
button.addTarget(self, action: #selector(ViewController.getDirections), for: .touchUpInside)
pinView?.leftCalloutAccessoryView = button
return pinView
}
}
and this is the LocationSearchTable :
class LocationSearchTable: UITableViewController {
weak var handleMapSearchDelegate: HandleMapSearch?
var matchingItems: [MKMapItem] = []
var mapView: MKMapView?
func parseAddress(selectedItem:MKPlacemark) -> String {
// put a space between "4" and "Melrose Place"
let firstSpace = (selectedItem.subThoroughfare != nil &&
selectedItem.thoroughfare != nil) ? " " : ""
// put a comma between street and city/state
let comma = (selectedItem.subThoroughfare != nil || selectedItem.thoroughfare != nil) &&
(selectedItem.subAdministrativeArea != nil || selectedItem.administrativeArea != nil) ? ", " : ""
// put a space between "Washington" and "DC"
let secondSpace = (selectedItem.subAdministrativeArea != nil &&
selectedItem.administrativeArea != nil) ? " " : ""
let addressLine = String(
format:"%#%#%#%#%#%#%#",
// street number
selectedItem.subThoroughfare ?? "",
firstSpace,
// street name
selectedItem.thoroughfare ?? "",
comma,
// city
selectedItem.locality ?? "",
secondSpace,
// state
selectedItem.administrativeArea ?? ""
)
return addressLine
}
}
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, _ in
guard let response = response else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
}
}
extension LocationSearchTable {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchingItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let selectedItem = matchingItems[indexPath.row].placemark
cell.textLabel?.text = selectedItem.name
cell.detailTextLabel?.text = parseAddress(selectedItem: selectedItem)
return cell
}
}
extension LocationSearchTable {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedItem = matchingItems[indexPath.row].placemark
handleMapSearchDelegate?.dropPinZoomIn(placemark: selectedItem)
dismiss(animated: true, completion: nil)
}
}
Please I need your help to solve this issue.
You are using the wrong delegate func updateSearchResultsForSearchController. Use the following code.
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, _ in
guard let response = response else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
}
}
Also, if you like to search the city only, you can use locality only. See the example below.Otherwise, keep your code intact.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let selectedItem = matchingItems[indexPath.row].placemark
cell.textLabel?.text = selectedItem.locality
cell.detailTextLabel?.text = "" //parseAddress(selectedItem: selectedItem)
return cell
}
[EDIT 1]
Another deprecated delegate needs to be changed for getting direction:
extension ViewController : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
guard !(annotation is MKUserLocation) else { return nil }
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
}
pinView?.pinTintColor = UIColor.orange
pinView?.canShowCallout = true
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare))
button.setBackgroundImage(UIImage(named: "car"), for: .normal)
button.addTarget(self, action: #selector(ViewController.getDirections), for: .touchUpInside)
pinView?.leftCalloutAccessoryView = button
return pinView
}
}

custom search bar using UISearchController in iOS 8

I have a custom search bar class, and I used interface builder to insert a search bar as an instance of this custom class. How do I use this search bar is search bar for the UISearchController? Its searchBar property is read-only so I can't assign my search bar to it.
You have to set your already initialized custom search bar to be returned in the overriden search bar getter for you custom UISearchController class and setup its properties after the searchcontroller init, this way all the UISearchController functionality are retained:
public class DSearchController: UISearchController {
private var customSearchBar = DSearchBar()
override public var searchBar: UISearchBar {
get {
return customSearchBar
}
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
public init(searchResultsController: UIViewController?,
searchResultsUpdater: UISearchResultsUpdating?,
delegate: UISearchControllerDelegate?,
dimsBackgroundDuringPresentation: Bool,
hidesNavigationBarDuringPresentation: Bool,
searchBarDelegate: UISearchBarDelegate?,
searchBarFrame: CGRect?,
searchBarStyle: UISearchBarStyle,
searchBarPlaceHolder: String,
searchBarFont: UIFont?,
searchBarTextColor: UIColor?,
searchBarBarTintColor: UIColor?, // Bar background
searchBarTintColor: UIColor) { // Cursor and bottom line
super.init(searchResultsController: searchResultsController)
self.searchResultsUpdater = searchResultsUpdater
self.delegate = delegate
self.dimsBackgroundDuringPresentation = dimsBackgroundDuringPresentation
self.hidesNavigationBarDuringPresentation = hidesNavigationBarDuringPresentation
customSearchBar.setUp(searchBarDelegate,
frame: searchBarFrame,
barStyle: searchBarStyle,
placeholder: searchBarPlaceHolder,
font: searchBarFont,
textColor: searchBarTextColor,
barTintColor: searchBarBarTintColor,
tintColor: searchBarTintColor)
}
}
And this is my custom searchBar:
public class DSearchBar: UISearchBar {
var preferredFont: UIFont?
var preferredTextColor: UIColor?
init(){
super.init(frame: CGRect.zero)
}
func setUp(delegate: UISearchBarDelegate?,
frame: CGRect?,
barStyle: UISearchBarStyle,
placeholder: String,
font: UIFont?,
textColor: UIColor?,
barTintColor: UIColor?,
tintColor: UIColor?) {
self.delegate = delegate
self.frame = frame ?? self.frame
self.searchBarStyle = searchBarStyle
self.placeholder = placeholder
self.preferredFont = font
self.preferredTextColor = textColor
self.barTintColor = barTintColor ?? self.barTintColor
self.tintColor = tintColor ?? self.tintColor
self.bottomLineColor = tintColor ?? UIColor.clearColor()
sizeToFit()
// translucent = false
// showsBookmarkButton = false
// showsCancelButton = true
// setShowsCancelButton(false, animated: false)
// customSearchBar.backgroundImage = UIImage()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let bottomLine = CAShapeLayer()
var bottomLineColor = UIColor.clearColor()
override public func layoutSubviews() {
super.layoutSubviews()
for view in subviews {
if let searchField = view as? UITextField { setSearchFieldAppearance(searchField); break }
else {
for sView in view.subviews {
if let searchField = sView as? UITextField { setSearchFieldAppearance(searchField); break }
}
}
}
bottomLine.path = UIBezierPath(rect: CGRectMake(0.0, frame.size.height - 1, frame.size.width, 1.0)).CGPath
bottomLine.fillColor = bottomLineColor.CGColor
layer.addSublayer(bottomLine)
}
func setSearchFieldAppearance(searchField: UITextField) {
searchField.frame = CGRectMake(5.0, 5.0, frame.size.width - 10.0, frame.size.height - 10.0)
searchField.font = preferredFont ?? searchField.font
searchField.textColor = preferredTextColor ?? searchField.textColor
//searchField.backgroundColor = UIColor.clearColor()
//backgroundImage = UIImage()
}
}
Init example:
searchController = DSearchController(searchResultsController: ls,
searchResultsUpdater: self,
delegate: self,
dimsBackgroundDuringPresentation: true,
hidesNavigationBarDuringPresentation: true,
searchBarDelegate: ls,
searchBarFrame: CGRectMake(0.0, 0.0, SCREEN_WIDTH, 44.0),
searchBarStyle: .Minimal,
searchBarPlaceHolder: NSLocalizedString("Search a location...", comment: ""),
searchBarFont: nil,
searchBarTextColor: nil,
searchBarBarTintColor: UIColor.whiteColor(),
searchBarTintColor: iconsColor)
searchController.searchBar.keyboardAppearance = .Dark
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar