Unable to add constraints to my NSTableView in Cocoa Application - swift

Below is my code in view controller
import Cocoa
class ViewController : NSViewController, NSTableViewDelegate, NSTableViewDataSource {
let stepperBtn: NSSegmentedControl = {
let stepperBtn = NSSegmentedControl()
stepperBtn.segmentCount = 2
stepperBtn.setImage(NSImage(systemSymbolName: "plus.circle", accessibilityDescription: "plus"), forSegment: 0)
stepperBtn.setImage(NSImage(systemSymbolName: "minus.circle", accessibilityDescription: "minus"), forSegment: 1)
stepperBtn.translatesAutoresizingMaskIntoConstraints = false
return stepperBtn
}()
var tableView: NSTableView {
let tablview = NSTableView()
tablview.translatesAutoresizingMaskIntoConstraints = false
return tablview
}
override func loadView() {
self.view = NSView(frame: NSMakeRect(0.0, 0.0, 550.0, 300.0))
tableView.delegate = self
tableView.dataSource = self
self.view.addSubview(tableView)
self.view.addSubview(stepperBtn)
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
override func viewDidLayout() {
}
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfRows(in tableView: NSTableView) -> Int {
return 5
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
I get below error : "NSTableView:0x7f9beb851600.top"> and <NSLayoutYAxisAnchor:0x600000bcc400 "NSView:0x7f9beaa184e0.top"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.
How to add constraints programatically to NSTableView

Related

SWIFT UIPanGestureRecognizer failed only first time

My test app have 4 ScrollView.
UIScrollView which can scroll up/down.
UIPageViewController inside 1, which can scroll left/right.
UITableView inside one of viewController 2, can scroll up/down.
UIScrollView inside UITableViewCell on tableView 3, can scroll left/right.
https://i.stack.imgur.com/BexX3.jpg
First swipe gesture by UIScrollView 4 causes PageViewController 2 scroll instead. Second and more swipes work correct.
PanGestureRecognizer of UIScrollView 4 has a state "failed" on the first swipe.
<UIScrollViewPanGestureRecognizer: 0x7f826881fd50; state = Failed; delaysTouchesEnded = NO; view = <Tests.MyCellScroll 0x7f826802f600>; target= <(action=handlePan:, target=<Tests.MyCellScroll 0x7f826802f600>)>>
Any ideas what’s the problem or how could it be debugged?
Example
class ViewController: UIViewController {
private let vcs: [UIViewController] = [
MyVC(),
UIViewController()
]
private lazy var scroll: UIScrollView = {
let scroll = UIScrollView()
scroll.backgroundColor = .systemPink
return scroll
}()
private lazy var pageVC: UIPageViewController = {
let page = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
page.view.backgroundColor = .white
page.delegate = self
page.dataSource = self
return page
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scroll)
self.addChild(self.pageVC)
self.scroll.addSubview(self.pageVC.view)
self.pageVC.didMove(toParent: self)
self.pageVC.setViewControllers([self.vcs[0]], direction: .forward, animated: true, completion: nil)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.scroll.frame = self.view.frame
self.scroll.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + 50)
self.pageVC.view.frame = CGRect(x: self.scroll.bounds.minX, y: self.scroll.bounds.minY + 250, width: self.scroll.bounds.width, height: self.scroll.contentSize.height - 250)
}
}
extension ViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
self.vcs.firstIndex(of: viewController) == 1 ? self.vcs[0] : nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
self.vcs.firstIndex(of: viewController) == 0 ? self.vcs[1] : nil
}
}
final class MyVC: UIViewController {
private lazy var table: UITableView = {
let table = UITableView()
table.register(MyCell.self, forCellReuseIdentifier: String(describing: MyCell.self))
table.delegate = self
table.dataSource = self
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.table)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.table.frame = self.view.frame
}
}
extension MyVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 5 }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { MyCell() }
}
final class MyCell: UITableViewCell {
private lazy var subview: UIView = {
let view = UIView()
view.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.7)
return view
}()
private lazy var scrollView: MyCellScroll = {
let view = MyCellScroll()
return view
}()
init(){
super.init(style: .default, reuseIdentifier: nil)
self.contentView.backgroundColor = UIColor.systemOrange.withAlphaComponent(0.7)
self.contentView.addSubview(self.scrollView)
self.scrollView.addSubview(self.subview)
}
override func layoutSubviews() {
super.layoutSubviews()
let frame = self.contentView.frame
self.scrollView.frame = frame
self.scrollView.contentSize = CGSize(width: 500, height: frame.height)
self.subview.frame = CGRect(x: 16, y: 8, width: 500 - 32, height: frame.height - 16)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
final class MyCellScroll: UIScrollView, UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
print(gestureRecognizer)
return false // 5
}
}

In keeping with the recent questions on closures used to pass data between VCs, how would I do the same when using a containerVC within the rootVC?

I saw a recent bountied question (can find the link if you wish to see it) about using closures to pass data between VCs where one VC was embedded in a navigation controller. While the use of a closure there was fairly easy since there was a direct point of contact between the two VCs (in the form a segue), I have been wondering how the same would work if this was not the case.
As an example, consider the following set up (similar to the OG question that inspired this post):
RootVC, which has a counter UILabel
A subContainer VC which takes up the lower half of RootVC, which has a button, pressing which should increment the UILabel on RootVC by one.
I have prepared the code as follows (with some code taken from the OG question):
RootVC:
class RootVC: UIViewController {
var tappedCount: Int = 0
let pagingContainer: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var label: UILabel = {
let label = UILabel()
label.text = "\(tappedCount)"
label.textAlignment = .center
label.font = UIFont(name: "Copperplate", size: 90)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(label)
view.addSubview(pagingContainer)
pagingContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
pagingContainer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1).isActive = true
pagingContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
pagingContainer.heightAnchor.constraint(equalToConstant: 500).isActive = true
let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
addChild(pageController)
pageController.didMove(toParent: self)
pageController.view.translatesAutoresizingMaskIntoConstraints = false
pagingContainer.addSubview(pageController.view)
pageController.view.heightAnchor.constraint(equalTo: pagingContainer.heightAnchor, multiplier: 1).isActive = true
pageController.view.widthAnchor.constraint(equalTo: pagingContainer.widthAnchor, multiplier: 1).isActive = true
pageController.view.topAnchor.constraint(equalTo: pagingContainer.topAnchor).isActive = true
pageController.view.bottomAnchor.constraint(equalTo: pagingContainer.bottomAnchor).isActive = true
pageController.view.leadingAnchor.constraint(equalTo: pagingContainer.leadingAnchor).isActive = true
pageController.view.trailingAnchor.constraint(equalTo: pagingContainer.trailingAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: pagingContainer.topAnchor).isActive = true
}
}
SubContainerVC:
class SubContainerVC: UIViewController {
var callback : (() -> Void)?
let button: UIButton = {
let button = UIButton()
button.setTitle("Button!", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.backgroundColor = .green
return button
}()
#objc func buttonPressed(_ sender: UIButton) {
print("Hello")
//Pressing this button should increment the label on RootVC by one.
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
And the PageViewController swift file:
class PageViewController: UIPageViewController {
lazy var subViewControllers:[UIViewController] = {
return [SubContainerVC()]
}()
init(transitionStyle style:
UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [String : Any]? = nil) {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
setViewControllerFromIndex(index: 0)
}
func setViewControllerFromIndex(index:Int) {
self.setViewControllers([subViewControllers[index]], direction: UIPageViewController.NavigationDirection.forward, animated: true, completion: nil)
}
}
extension PageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return subViewControllers.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0
if currentIndex <= 0 {
return nil
}
return subViewControllers[currentIndex-1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0
if currentIndex >= subViewControllers.count-1 {
return nil
}
return subViewControllers[currentIndex+1]
}
}
You can inject the closure downstream to SubContainerVC, this will result in the closure execution coming up upstream.
Something along the lines (kept only the relevant VC code):
class SubContainerVC {
var buttonCallback: () -> Void = { }
#objc func buttonPressed(_ sender: UIButton) {
print("Hello")
buttonCallback()
}
}
class PageViewController: UIViewController {
// Note that you don't need the extra closure call for lazy vars
lazy var subViewControllers = [SubContainerVC()] {
didSet {
// just in case the controllers might change later on
subViewControllers.forEach { $0.buttonCallback = buttonCallback }
}
}
var buttonCallback: () -> Void = { } {
didSet {
subViewControllers.forEach { $0.buttonCallback = buttonCallback }
}
}
}
class RootVC: UIViewController {
var tappedCount: Int = 0 {
didSet {
label.text = "\(tappedCount)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
// this will trigger the `didSet` from PageViewController, resulting
// in the callback being propagated downstream
pageController.buttonCallback = { self.tappedCount += 1 }
}
}

TextField rejected resignFirstResponder when being removed from hierarchy

I'm trying to make a custom textfield/sendbutton view that overrides inputAccessoryView, but I'm having 2 problems I can't seem to solve:
When the custom view becomes the first responder, I get this warning twice:
CustomKeyboardProject[5958:3107074] API error:
<_UIKBCompatInputView: 0x119e2bd70; frame = (0 0; 0 0);
layer = <CALayer: 0x283df9e00>> returned 0 width,
assuming UIViewNoIntrinsicMetric
Then when I try to resign the custom view as the first responder, Xcode throws this warning:
CustomKeyboardProject[5958:3107074] -[UIWindow
endDisablingInterfaceAutorotationAnimated:] called on <UITextEffectsWindow:
0x11a055400; frame = (0 0; 375 667); opaque = NO; autoresize = W+H; layer =
<UIWindowLayer: 0x283df78a0>> without matching
-beginDisablingInterfaceAutorotation. Ignoring.
Has anyone been able to silence these warnings?
Heres my code:
protocol CustomTextFieldDelegate: class {
func sendMessage()
}
class CustomTextField: UITextField {
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
}
class CustomKeyboardView: UIView {
let textField: CustomTextField = {
let textField = CustomTextField()
textField.backgroundColor = .green
textField.textColor = .white
return textField
}()
let attachButton: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setTitle("Attach", for: .normal)
return button
}()
let sendButton: UIButton = {
let button = UIButton()
button.backgroundColor = .blue
button.setTitle("Send", for: .normal)
button.addTarget(self, action: #selector(sendMessage), for: .touchUpInside)
return button
}()
#objc func sendMessage() {
delegate?.sendMessage()
}
weak var delegate: CustomTextFieldDelegate?
init() {
super.init(frame: .zero)
isUserInteractionEnabled = true
setViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setViews() {
let subviews = [attachButton, sendButton, textField]
subviews.forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
autoresizingMask = .flexibleHeight
subviews.forEach {
NSLayoutConstraint.activate([
$0.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
layoutSubviews()
[attachButton, sendButton].forEach {
NSLayoutConstraint.activate([
$0.widthAnchor.constraint(equalTo: $0.heightAnchor),
$0.heightAnchor.constraint(equalTo: textField.heightAnchor)
])
}
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: topAnchor),
attachButton.leftAnchor.constraint(equalTo: leftAnchor),
textField.leadingAnchor.constraint(equalTo: attachButton.trailingAnchor),
sendButton.leadingAnchor.constraint(equalTo: textField.trailingAnchor),
sendButton.trailingAnchor.constraint(equalTo: trailingAnchor),
])
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
class CustomTableView: UITableView {
let keyboardView = CustomKeyboardView()
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
keyboardDismissMode = .interactive
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
return keyboardView
}
}
private let reuseId = "MessageCellId"
class ExampleViewController: UITableViewController {
private let customTableView = CustomTableView()
var messages = [String]()
override func loadView() {
tableView = customTableView
view = tableView
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.becomeFirstResponder()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseId)
customTableView.keyboardView.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.customTableView.keyboardView.textField.becomeFirstResponder()
}
}
// tableView delegate methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseId)
cell?.textLabel?.text = messages[indexPath.row]
return cell!
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
customTableView.keyboardView.textField.resignFirstResponder()
}
}
extension ExampleViewController: CustomTextFieldDelegate {
func sendMessage() {
// double check something is in textField
let textField = customTableView.keyboardView.textField
guard let body = textField.text, body != "" else { return }
messages.append(body)
tableView.reloadData()
view.endEditing(true)
customTableView.keyboardView.textField.text = ""
}
}

Swift - Size of content view does not match with UITableViewCell

I have an iOS app with a TableView, all UI of the app are done programmingly with autolayout using Swift.
All UI are working great until recently, I have to add a new component (custom UIView) inside the UITableViewCell which cell height will be changed when the new component is shown or hidden. The height of the cell is not correct so my views inside the UITableViewCell become a mess.
After checking the Debug View Hierarchy, I found that the height of the UITableViewCell is different than then UITableViewCellContentView.
When the component should display:
Table view cell has correct height (Longer height)
Content View of UITableViewCell is shorter then expected (the height is correct if component is hidden)
When the component should hidden:
Table view cell has correct height (Shorter height)
Content View of UITableViewCell is longer then expected (the height is correct if component is display)
I am not really sure what is the real issue. When I toggle the component to be displayed or not, I do the followings:
// Update the constraints status
var componentIsShown: Bool = .....
xxxConstraints?.isActive = componentIsShown
yyyConstraints?.isActive = !componentIsShown
// Update UI
layoutIfNeeded()
view.setNeedsUpdateConstraints()
tableView.beginUpdates()
tableView.endUpdates()
It seems to me that when I toggle the component to be displayed or not, the UITableViewCell is updated immediately, but content view used previous data to update the height. If this is the issue, how could I update the content view height also?
If this is not the issue, any suggestion to fix it or do further investigation?
Thanks
====================
Updated in 2018-08-29:
Attached are the codes for the issue.
Clicking on the topMainContainerView in MyBaseView (the view with red alpha bg) will toggle the hiddenView display or not.
In ViewController.swift:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell: MyViewCell
if let theCell = tableView.dequeueReusableCell(withIdentifier: "cell") as? MyViewCell
{
cell = theCell
}
else
{
cell = MyViewCell(reuseIdentifier: "cell")
}
cell.setViewCellDelegate(delegate: self)
return cell
}
MyViewCell.swift
class MyViewCell: MyViewParentCell
{
var customView: MyView
required init?(coder aDecoder: NSCoder)
{
return nil
}
override init(reuseIdentifier: String?)
{
customView = MyView()
super.init(reuseIdentifier: reuseIdentifier)
selectionStyle = .none
}
override func initViews()
{
contentView.addSubview(customView)
}
override func initLayout()
{
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
customView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
}
override func setViewCellDelegate(delegate: MyViewCellDelegate)
{
super.setViewCellDelegate(delegate: delegate)
customView.delegate = delegate
customView.innerDelegate = self
}
}
MyViewParentCell.swift:
protocol MyViewCellDelegate
{
func reloadTableView()
}
protocol MyViewCellInnerDelegate
{
func viewCellLayoutIfNeeded()
}
class MyViewParentCell: UITableViewCell
{
private var delegate: MyViewCellDelegate?
var innerDelegate: MyViewCellInnerDelegate?
required init?(coder aDecoder: NSCoder)
{
return nil
}
init(reuseIdentifier: String?)
{
super.init(style: UITableViewCellStyle.default, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
initViews()
initLayout()
}
func initViews()
{
}
func initLayout()
{
}
override func layoutSubviews()
{
}
func setViewCellDelegate(delegate: MyViewCellDelegate)
{
self.delegate = delegate
}
}
extension MyViewParentCell: MyViewCellInnerDelegate
{
func viewCellLayoutIfNeeded()
{
print("MyViewParentCell viewCellLayoutIfNeeded")
setNeedsUpdateConstraints()
layoutIfNeeded()
}
}
MyView.swift
class MyView: MyParentView
{
private var mainView: UIView
// Variables
var isViewShow = true
// Constraint
private var mainViewHeightConstraint: NSLayoutConstraint?
private var hiddenViewHeightConstraint: NSLayoutConstraint?
private var hiddenViewPosYHideViewConstraint: NSLayoutConstraint?
private var hiddenViewPosYShowViewConstraint: NSLayoutConstraint?
// Constant:
let viewSize = UIScreen.main.bounds.width
// Init
override init()
{
mainView = UIView(frame: CGRect(x: 0, y: 0, width: viewSize, height: viewSize))
super.init()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
override func initViews()
{
super.initViews()
topMainContainerView.addSubview(mainView)
}
override func initLayout()
{
super.initLayout()
//
mainView.translatesAutoresizingMaskIntoConstraints = false
mainView.topAnchor.constraint(equalTo: topMainContainerView.topAnchor).isActive = true
mainView.bottomAnchor.constraint(equalTo: topMainContainerView.bottomAnchor).isActive = true
mainView.leadingAnchor.constraint(equalTo: topMainContainerView.leadingAnchor).isActive = true
mainView.widthAnchor.constraint(equalToConstant: viewSize).isActive = true
mainView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true
mainViewHeightConstraint = mainView.heightAnchor.constraint(equalToConstant: viewSize)
mainViewHeightConstraint?.isActive = true
}
override func toggle()
{
isViewShow = !isViewShow
print("toggle: isViewShow is now (\(isViewShow))")
setViewHidden()
}
private func setViewHidden()
{
UIView.animate(withDuration: 0.5) {
if self.isViewShow
{
self.hiddenViewBottomConstraint?.isActive = false
self.hiddenViewTopConstraint?.isActive = true
}
else
{
self.hiddenViewTopConstraint?.isActive = false
self.hiddenViewBottomConstraint?.isActive = true
}
self.layoutIfNeeded()
self.needsUpdateConstraints()
self.innerDelegate?.viewCellLayoutIfNeeded()
self.delegate?.reloadTableView()
}
}
}
MyParentView.swift
class MyParentView: MyBaseView
{
var delegate: MyViewCellDelegate?
var innerDelegate: MyViewCellInnerDelegate?
override init()
{
super.init()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
override func initViews()
{
super.initViews()
}
override func initLayout()
{
super.initLayout()
translatesAutoresizingMaskIntoConstraints = false
}
}
MyBaseView.swift
class MyBaseView: UIView
{
var topMainContainerView: UIView
var hiddenView: UIView
var bottomActionContainerView: UIView
var bottomSeparator: UIView
var hiddenViewTopConstraint: NSLayoutConstraint?
var hiddenViewBottomConstraint: NSLayoutConstraint?
// Layout constratint
var descriptionWidthConstraint: NSLayoutConstraint?
var moreMainTopAnchorConstraint: NSLayoutConstraint?
var moreMainBottomAnchorConstraint: NSLayoutConstraint?
var separatorTopAnchorToActionBarConstraint: NSLayoutConstraint?
var separatorTopAnchorToPartialCommentConstraint: NSLayoutConstraint?
// Constant
let paddingX: CGFloat = 10
let InnerPaddingY: CGFloat = 9
init()
{
topMainContainerView = UIView()
hiddenView = UIView()
bottomActionContainerView = UIView()
bottomSeparator = UIView()
super.init(frame: .zero)
initViews()
initLayout()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
func initViews()
{
let borderColor = UIColor.gray
backgroundColor = UIColor(red: 211/255.0, green: 211/255.0, blue: 1, alpha: 1)
topMainContainerView.backgroundColor = UIColor.red.withAlphaComponent(0.7)
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
topMainContainerView.addGestureRecognizer(gesture)
// Hidden View
hiddenView.backgroundColor = UIColor.yellow
hiddenView.layer.cornerRadius = 50
// Action
bottomActionContainerView.backgroundColor = UIColor.blue
bottomSeparator.backgroundColor = borderColor
// Add hiddenView first, so it will hide behind main view
addSubview(hiddenView)
addSubview(topMainContainerView)
addSubview(bottomActionContainerView)
addSubview(bottomSeparator)
}
func initLayout()
{
// MARK: Main
topMainContainerView.translatesAutoresizingMaskIntoConstraints = false
topMainContainerView.topAnchor.constraint(equalTo: topAnchor, constant: 30).isActive = true
topMainContainerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
topMainContainerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
topMainContainerView.heightAnchor.constraint(equalToConstant: 150).isActive = true
// Hidden View
hiddenView.translatesAutoresizingMaskIntoConstraints = false
hiddenViewTopConstraint = hiddenView.topAnchor.constraint(equalTo: topMainContainerView.bottomAnchor)
hiddenViewTopConstraint?.isActive = true
hiddenView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
hiddenView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
hiddenViewBottomConstraint = hiddenView.bottomAnchor.constraint(equalTo: topMainContainerView.bottomAnchor)
hiddenViewBottomConstraint?.isActive = false
hiddenView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// MARK: Bottom
bottomActionContainerView.translatesAutoresizingMaskIntoConstraints = false
bottomActionContainerView.topAnchor.constraint(equalTo: hiddenView.bottomAnchor, constant: InnerPaddingY).isActive = true
bottomActionContainerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
bottomActionContainerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
bottomActionContainerView.heightAnchor.constraint(equalToConstant: 32).isActive = true
// MARK: Separator
bottomSeparator.translatesAutoresizingMaskIntoConstraints = false
separatorTopAnchorToPartialCommentConstraint = bottomSeparator.topAnchor.constraint(equalTo: bottomActionContainerView.bottomAnchor, constant: InnerPaddingY)
separatorTopAnchorToActionBarConstraint = bottomSeparator.topAnchor.constraint(equalTo: bottomActionContainerView.bottomAnchor, constant: InnerPaddingY)
separatorTopAnchorToPartialCommentConstraint?.isActive = false
separatorTopAnchorToActionBarConstraint?.isActive = true
bottomSeparator.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
bottomSeparator.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
bottomSeparator.heightAnchor.constraint(equalToConstant: 10).isActive = true
bottomSeparator.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
self.hiddenViewBottomConstraint?.isActive = false
self.hiddenViewTopConstraint?.isActive = true
}
#objc func toggle()
{
}
}
The layout of contentView is not updating. You should try
cell.contentView.layoutIfNeeded()
Try and share results.
I finally found that I should call super.layoutSubviews() in MyViewParentCell.swift or simply remove the function to fix the issue.
override func layoutSubviews()
{
super.layoutSubviews()
}

Add textfiled and Send Button (mail to) in xcode

i'm build a simple app that allows you to reserve information equipment or a seat in a classroom. In my table view i insert an image and some text. How can i add a text field and a button in the bottom that send me a mail with the summary of the booking?
This is the code that i build until now.
TableViewController
import UIKit
struct CellData {
let image : UIImage?
let message : String?
}
class TableViewController: UITableViewController {
var data = [CellData] ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
data = [CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D"),CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D"),CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D")]
self.tableView.register(CustomCell.self, forCellReuseIdentifier: "custom")
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 200
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "custom") as! CustomCell
cell.mainImage = data[indexPath.row].image
cell.message = data[indexPath.row].message
cell.layoutSubviews()
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
}
Custom Cell
import Foundation
import UIKit
class CustomCell: UITableViewCell {
var message : String?
var mainImage : UIImage?
var messageView : UITextView = {
var textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isScrollEnabled = false
return textView
}()
var mainImageView : UIImageView = {
var imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(mainImageView)
self.addSubview(messageView)
mainImageView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
mainImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
mainImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
mainImageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
mainImageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
messageView.leftAnchor.constraint(equalTo: self.mainImageView.rightAnchor).isActive = true
messageView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
messageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
messageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
if let message = message {
messageView.text = message
}
if let image = mainImage{
mainImageView.image = image
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Thanks!
I think you are referring to create a chat like window, if this is correct, one way to resolve this is add handlers for the keyboard events, in order to move the top views. In this case you can start with the following ones:
First you need to add some observers to the Notification center to listen when the keyboard is shown or when is hidden.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
Then, you need to create the functions to be triggered when the events occurs. As you can see in the following code, the view frame of the view is modified accordingly to the keyboardSize.
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y -= keyboardSize.height
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y += keyboardSize.height
}
}
So, just for clarification, you need to create a second view below the table, in which you will add the textfield and the send button.