NSComboBox items misplaced if user drag an app window on mac OS - objective-c

I work at desktop application for OS X and macOS and have issue with native NSComboBox (not subclass).
It is the problem that comboBox items misplaces as you can see on screenshot below.
Also a have a warning in XCode console output:
This application is trying to draw a very large combo box, 28 points tall. Vertically resizable combo boxes are not supported, but it happens that 10.4 and previous drew something that looked kind of sort of okay. The art in 10.5 does not break up in a way that supports that drawing. This application should be revised to stop using large combo boxes. This warning will appear once per app launch.
Could this be a warning associated with this problem? I tried to fix warning but failed.
I use Xcode 8.2.1 and OS X El Capitan 10.11.6.
Edited:
Current ViewController contained in custom navigation controller and have combobox delegate/datasource implementation.
View added as below to navigation container:
addChildViewController(viewController)
viewController.view.frame.size = contentView != nil ? contentView!.frame.size : CGSize.zero
viewController.view.frame.origin = CGPoint.zero
viewController.view.autoresizingMask = [.viewWidthSizable, .viewHeightSizable]
if let animation = animation {
contentView?.layer?.add(animation, forKey: animation.type)
}
contentView?.addSubview(viewController.view)
ComboBox datasource implementation
public func numberOfItems(in comboBox: NSComboBox) -> Int {
switch comboBox {
case companyComboBox:
return filteredCompanies?.count ?? 0
case projectComboBox:
return filteredProjects?.count ?? 0
default:
return 0
}
}
public func comboBox(_ comboBox: NSComboBox, objectValueForItemAt index: Int) -> Any? {
switch comboBox {
case companyComboBox:
return company(at: index)?.name
case projectComboBox:
return project(at: index)?.name
default:
return nil
}
}
public func comboBox(_ comboBox: NSComboBox, indexOfItemWithStringValue string: String) -> Int {
switch comboBox {
case companyComboBox:
return selectedCompany == nil ? -1 : comboBox.indexOfSelectedItem
case projectComboBox:
return selectedProject == nil ? -1 : comboBox.indexOfSelectedItem
default:
return -1
}
}
public func comboBox(_ comboBox: NSComboBox, completedString string: String) -> String? {
switch comboBox {
case companyComboBox:
let bestMatched = bestMatchedCompany(for: string)
filteredCompanies = filterCompanies(by: string)
return bestMatched?.name
case projectComboBox:
let bestMatched = bestMatchedProject(for: string)
filteredProjects = filterProjects(by: string)
return bestMatched?.name
default:
return nil
}
}
ComboBox delegate implementation:
//MARK: NSComboBoxDelegate
func comboBoxSelectionDidChange(_ notification: Notification) {
let comboBox = notification.object as! NSComboBox
let selectedIndex = comboBox.indexOfSelectedItem
guard selectedIndex >= 0 else { return }
switch comboBox {
case companyComboBox:
selectedCompany = company(at: selectedIndex)
case projectComboBox:
selectedProject = project(at: selectedIndex)
default:
break
}
}
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
guard let comboBox = control as? NSComboBox, comboBox.stringValue.characters.count > 0 else { return false }
if (commandSelector == #selector(insertBacktab(_:))) {
switch comboBox {
case companyComboBox:
filteredCompanies = filterCompanies(by: comboBox.stringValue)
case projectComboBox:
filteredProjects = filterProjects(by: comboBox.stringValue)
default:
break
}
} else if commandSelector == #selector(deleteBackward(_:)) {
//Need to filter by substring of comboBox.stringValue because before deleteBackward comboBox has old text value.
let selectedTextRange = textView.selectedRanges.first as! NSRange
let endIndex = comboBox.stringValue.index(comboBox.stringValue.startIndex,
offsetBy: selectedTextRange.length > 0 ? selectedTextRange.location : selectedTextRange.location - 1)
let filterValue = comboBox.stringValue.substring(with: comboBox.stringValue.startIndex..<endIndex)
switch comboBox {
case companyComboBox:
filteredCompanies = filterCompanies(by: filterValue)
case projectComboBox:
filteredProjects = filterProjects(by: filterValue)
default:
break
}
} else if commandSelector == #selector(insertNewline(_:)) || commandSelector == #selector(insertTab(_:)) {
switch comboBox {
case companyComboBox:
let company = bestMatchedCompany(for: comboBox.stringValue)
if comboBox.stringValue == company?.name {
selectedCompany = company
}
case projectComboBox:
let project = bestMatchedProject(for: comboBox.stringValue)
if comboBox.stringValue == project?.name {
selectedProject = project
}
default:
break
}
}
return false
}
Do you have an idea how to fix it?
Thanks!

Related

SwiftUI: Is it possible to automatically move to the next textfield after 1 character is entered?

I trying to make a SwiftUI app where after entering one letter in a TextField the cursor automatically moves to the next TextField. The UI is pretty much like this.
In Swift/IB, it looks like this was done with delegates and adding a target like in this post:
How to move to the next UITextField automatically in Swift
But can't find any documentation for using delegates/targets in SwiftUI.
I tried following this post:
SwiftUI TextField max length
But this has not worked for me. Setting the .prefix(1) does not seem to make a difference. The TextField still accepts any amount of characters and when moved to the next TextField does not reduce the characters entered to only the first character.
In SwiftUI's current state, is it possible to automatically move to the next TextField after 1 character is entered?
Thanks for any help!
It can be done in iOS 15 with FocusState
import SwiftUI
///Sample usage
#available(iOS 15.0, *)
struct PinParentView: View {
#State var pin: Int = 12356
var body: some View {
VStack{
Text(pin.description)
PinView(pin: $pin)
}
}
}
#available(iOS 15.0, *)
struct PinView: View {
#Binding var pin: Int
#State var pinDict: [UniqueCharacter] = []
#FocusState private var focusedField: UniqueCharacter?
var body: some View{
HStack{
ForEach($pinDict, id: \.id, content: { $char in
TextField("pin digit", text:
Binding(get: {
char.char.description
}, set: { newValue in
let newest: Character = newValue.last ?? "0"
//This check is only needed if you only want numbers
if Int(newest.description) != nil{
char.char = newest
}
//Set the new focus
DispatchQueue.main.async {
setFocus()
}
})
).textFieldStyle(.roundedBorder)
.focused($focusedField, equals: char)
})
}.onAppear(perform: {
//Set the initial value of the text fields
//By using unique characters you can keep the order
pinDict = pin.description.uniqueCharacters()
})
}
func setFocus(){
//Default to the first box when focus is not set or the user reaches the last box
if focusedField == nil || focusedField == pinDict.last{
focusedField = pinDict.first
}else{
//find the index of the current character
let idx = pinDict.firstIndex(of: focusedField!)
//Another safety check for the index
if idx == nil || pinDict.last == pinDict[idx!]{
focusedField = pinDict.first
}else{
focusedField = pinDict[idx! + 1]
}
}
//Update the Binding that came from the parent
setPinBinding()
}
///Updates the binding from the parent
func setPinBinding(){
var newPinInt = 0
for n in pinDict{
if n == pinDict.first{
newPinInt = Int(n.char.description) ?? 0
}else{
newPinInt = Int(String(newPinInt) + n.char.description) ?? 0
}
}
pin = newPinInt
}
}
//Convert String to Unique characers
extension String{
func uniqueCharacters() -> [UniqueCharacter]{
let array: [Character] = Array(self)
return array.uniqueCharacters()
}
func numberOnly() -> String {
self.trimmingCharacters(in: CharacterSet(charactersIn: "-0123456789.").inverted)
}
}
extension Array where Element == Character {
func uniqueCharacters() -> [UniqueCharacter]{
var array: [UniqueCharacter] = []
for char in self{
array.append(UniqueCharacter(char: char))
}
return array
}
}
//String/Characters can be repeating so yu have to make them a unique value
struct UniqueCharacter: Identifiable, Equatable, Hashable{
var char: Character
var id: UUID = UUID()
}
#available(iOS 15.0, *)
struct PinView_Previews: PreviewProvider {
static var previews: some View {
PinParentView()
}
}

Change highlight color of NSTableView selected row

how to change NSTable selected row background color?
here is good answer, but it is for uitable view .
For now,what I see is that I can change selected hilight style :
MyTAble.SelectionHighlightStyle = NSTableViewSelectionHighlightStyle.Regular;
But here is only 3 options;
None = -1L,
Regular,
SourceList
I have tried following solution :
patientListDelegate.SelectionChanged += (o, e) => {
var r = PatientTableView.SelectedRow;
var v = PatientTableView.GetRowView (r, false);
v.Emphasized = false;
};
It works normally , but if I minimize and then open application again , still shows blue color
I found answer in objective-c
Change selection color on view-based NSTableView
Here is c# implementation:
inside delegate :
public override NSTableRowView CoreGetRowView (NSTableView tableView, nint row)
{
var rowView = tableView.MakeView ("row", this);
if (rowView == null) {
rowView = new PatientTableRow ();
rowView.Identifier = "row";
}
return rowView as NSTableRowView;
}
and custom row :
public class PatientTableRow : NSTableRowView
{
public override void DrawSelection (CGRect dirtyRect)
{
if (SelectionHighlightStyle != NSTableViewSelectionHighlightStyle.None) {
NSColor.FromCalibratedWhite (0.65f, 1.0f).SetStroke ();
NSColor.FromCalibratedWhite (0.82f, 1.0f).SetFill ();
var selectionPath = NSBezierPath.FromRoundedRect (dirtyRect, 0, 0);
selectionPath.Fill ();
selectionPath.Stroke ();
}
}
}

How disable rest of the buttons from an array of buttons when button is pressed

Im having some trouble with method .disable in swift code. I have an array of Buttons and I want to disable the rest of Buttons when the correct Button (Target Button) is pressed. My array is call Buttons! Here are the action for the Buttons.
I have to have different names in the Buttons? or I can use .count method?
Thank you and appreciate everything.
#IBAction func btn1(sender: AnyObject) {
if answerNumber == 0 {
cwLabel.text = "You are Right!"
pickQuestion()
Buttons.count
} else {
cwLabel.text = "You are Wrong!"
pickQuestion()
}
}
#IBAction func btn2(sender: AnyObject) {
if answerNumber == 1 {
cwLabel.text = "You are Right!"
pickQuestion()
} else {
cwLabel.text = "You are Wrong!"
pickQuestion()
}
}
#IBAction func btn3(sender: AnyObject) {
if answerNumber == 2 {
cwLabel.text = "You are Right!"
pickQuestion()
} else {
cwLabel.text = "You are Wrong!"
pickQuestion()
}
}
#IBAction func btn4(sender: AnyObject) {
if answerNumber == 3 {
cwLabel.text = "You are Right!"
pickQuestion()
} else {
cwLabel.text = "You are Wrong!"
pickQuestion()
}
}
Use below line of code. It may help you...
for (var index = 0; index < arrayButton.count; index += 1) {
let btn : UIButton = arrayButton[index] as UIButton
btn.addTarget(self, action: #selector(self.toggleButtons toggleButtons(_:)), forControlEvents: .TouchUpInside)
}
func toggleButtons(button: UIButton) {
for (var index = 0; index < arrayButton.count; index += 1) {
if arrayButton[index] != button {
arrayButton[index].enabled = false
}
}
}
Do you mean to disable all of buttons or upressed ones? You can use toggle
func toggleButtons(button: UIButton) {
for (var index = 0; index < arrayButton.count; index += 1) {
if arrayButton[index] != button {
arrayButton[index].enabled = false
}
}
}

Calculator in Swift a method [rangeOfString]

I am making calculator in Swift for IOS 8. All operand and operator works very well. Only problem is with "."
For example, 192.168.0.1 is invalid. The "." should only display once. Instead 192.16801 is valid.
See below code -
//Assume user has entered 192.168
//User cannot press "." button again now, "." should not appear again
var display.text 192.168`
if display.text.rangeOfString(".") != nil{
println("exists")
}
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.delegate = self
}
//Textfield delegates
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { // return NO to not change text
switch string {
case "0","1","2","3","4","5","6","7","8","9":
return true
case ".":
let array = Array(textField.text)
var decimalCount = 0
for character in array {
if character == "." {
decimalCount++
}
}
if decimalCount == 1 {
return false
} else {
return true
}
default:
let array = Array(string)
if array.count == 0 {
return true
}
return false
}
}
}
the textField functions will return a value of true if you have entered a valid number, it will return a value of false if you input anything else than a number or if you enter a second “.”. If this functions returns false it will not change the value of the text field. Example: if a user (tries to) input 3.14a9 the a will be ignored and the textfield text will be 3.149. If a user inputs 3.45.12 the textfield will have a text value of 3.45.12
I’m not sure this is the question, but if you’re looking for a second “.” you can use the find function with a where for this:
let decimal: Character = "."
if let first = find(display.text, decimal)
where (find(display.text[first.successor()..<display.text.endIndex], decimal) != nil) {
println("second exists")
}
else {
println("none or one")
}
You can use regular expression to validate your textfield whenever its value changes.
Example of a regulator
/*
^ //begin string
([0-9]+)? //optional: has 0 or more digits
(\\.)? //optional: has a point
([0-9]+)? //optional: has 0 or more digits
$ //end string
*/
extension String {
func isValidNumber() -> Bool {
let regex = NSRegularExpression(pattern: "^([0-9]+)?(\\.)?([0-9]+)?$", options: .CaseInsensitive, error: nil)
return regex?.firstMatchInString(self, options: nil, range: NSMakeRange(0, countElements(self))) != nil
}
}
Used in the call-back of your UITextField:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return textField.text.isValidNumber()
}
var text = "192.168.2"
var isValid = true
let range = text.rangeOfString(".")
if let range = range {
let subText = text.substringFromIndex(range.endIndex)
let range2 = subText.rangeOfString(".")
if range2 != nil {
isValid = false
}
}
println("isValid: \(isValid)") // isValid: false

Treeviews QueueDraw doesn't render current row?

I'm working with a treeview, which contains several columns, also one displaying a pixbuf, if audio is playing or paused. If the user double clicks on one row, audio playback starts and the row needs to be rerendered in order to display the pixbuf icon. I used QueueDraw for this, but that does only work, if the cursor leaves the current row. How can I update the pixbuf directly?
CODE:
protected void trvMainCuesheetRowActivated (object o, RowActivatedArgs args)
{
log.debug("trvMainCuesheetRowActivated called");
TreeIter ti = TreeIter.Zero;
this.lsCuesheetData.GetIter(out ti,args.Path);
if (this.lsCuesheetData.GetValue(ti,0) != null)
{
Track tCurTrack = (Track)this.lsCuesheetData.GetValue(ti,0);
if (this.objProgram.getAudioManager().getPlayState() == AudioCuesheetEditor.AudioBackend.PlayState.Stopped)
{
this.objProgram.getAudioManager().play(tCurTrack);
this.refresh();
}
else
{
if (this.objProgram.getAudioManager().getPlayState() == AudioCuesheetEditor.AudioBackend.PlayState.Playing)
{
this.objProgram.getAudioManager().seek(tCurTrack);
this.refresh();
}
}
}
}
private void renderPlaying(TreeViewColumn _tvcColumn, CellRenderer _crCell, TreeModel _tmModel, TreeIter _tiIter)
{
Track tCurTrack = (Track)_tmModel.GetValue (_tiIter, 0);
//Just display an icon, if we are playing
if (this.objProgram.getAudioManager().getPlayState() == AudioCuesheetEditor.AudioBackend.PlayState.Playing)
{
if (this.objProgram.getAudioManager().getCurrentlyPlayingTrack() == tCurTrack)
{
Gdk.Pixbuf icon = this.RenderIcon(Stock.MediaPlay, IconSize.SmallToolbar, null);
(_crCell as CellRendererPixbuf).Pixbuf = icon;
}
else
{
(_crCell as CellRendererPixbuf).Pixbuf = null;
}
}
else
{
if (this.objProgram.getAudioManager().getPlayState() == AudioCuesheetEditor.AudioBackend.PlayState.Paused)
{
if (this.objProgram.getAudioManager().getCurrentlyPlayingTrack() == tCurTrack)
{
Gdk.Pixbuf icon = this.RenderIcon(Stock.MediaPause, IconSize.SmallToolbar, null);
(_crCell as CellRendererPixbuf).Pixbuf = icon;
}
else
{
(_crCell as CellRendererPixbuf).Pixbuf = null;
}
}
else
{
(_crCell as CellRendererPixbuf).Pixbuf = null;
}
}
}
//Purpose: Function used to refresh the MainWindow depending on new options set.
public void refresh()
{
//QueueDraw is needed since it fires a signal to cellrenderers to update
this.trvMainCuesheet.QueueDraw();
this.sbMainWindow.Visible = this.objProgram.getObjOption().getBShowStatusbar();
this.mwToolbar.Visible = this.objProgram.getObjOption().getBToolbarVisible();
}
Greetings
Sven
Found the error myself.
this.objProgram.getAudioManager().getCurrentlyPlayingTrack()
didn't always return a track, where I expected one, so the renderer worked right. Bug is fixed, thanks anyway ;).