Tag Archives: frames

Where is Update Frames in Xcode 8.1?

A tech author’s work is never done. As soon as he or she completes manuscript and gets it published, the manuscript almost immediately becomes obsolete. In my case, Practical Autolayout for Xcode 8 went obsolete  a day before I published, but I had no idea about a major change in Xcode 8.1.

Until Xcode 8.1, if you wanted to update a frame with new constraints, you had two possibilities. The first was in the pinpinMenuButton and align alignment iconmenu to update as you were setting the constraints.

2016-11-29_06-05-32

The second was a selection in the resolver resolver button 2016-10-01_13-27-48

It seems everyone, including me was not ready for a change Apple made in Xcode 8.1. If you go to look for Update Frames in the resolver resolver button, it is missing:

2016-11-29_06-11-52

So where did it go?

Apple moved this to an icon on the auto layout toolbar and deleted it from the menus.

2016-11-28_07-29-22

If it were me, I wouldn’t have deleted it from the menus in such an abrupt way. Apple did. This Update Frame button  has some different behaviors  from its predecessor on the menu, and I’d like to explain that using some examples from Chapter 3 of Practical Autolayout for Xcode 8

Set up a storyboard that looks something like this with a label Hello Pizza, a text view, and  three  buttons, Pepperoni, Cheese, and Done:

2016-11-29_05-54-14

Select the Hello Pizza label.  Click the pin buttonpinMenuButton in the auto layout toolbar. In the popup, set the top to 0 points, the left to 0 points and the left to 0 points.  Leave Update Frames as None

2016-11-29_05-56-06

Add the 3 constraints. The Hello Pizza Label will show misplacement constraints.

2016-11-29_05-56-32

Press the Update Frames button update frames  and the frame updates.

2016-11-29_06-37-41

This is not always the result. You must have all constraints satisfied before the button will update frames. For example, select the text view. Press the align button alignment iconand center the text view by checking on Horizontally in Container and Vertically in Container.

2016-11-29_05-54-38

Again don’t update frames, but click  Add 2 constraints. You’ll see an ambiguous constraint in red.

2016-11-29_05-55-13

If you click the update frames button nothing happens. Until a frame has no ambiguity(i.e. no red constraint errors), you cannot update it. Most often that is setting a size. For the text box, set an absolute size in the pin menu pinMenuButton  of 175 points in both directions.

2016-11-29_05-57-41

Add the constraints. The errors all turn to misplacements.

 2016-11-29_05-58-39

Once all misplacements, you can update the frame with update frames.

2016-11-29_07-15-35

Priorities are not assumed with the new update frames button. When there is an ambiguity in size between two frames that depend on each other for size, you must specify the a priority for them or set a size.  Take for example these two buttons.

2016-11-29_05-45-35

Pepperoni is pinned to the left margin, the label above it and the text view below it. Cheese is pinned 10 points from Pepperoni, aligned to the top of Pepperoni, and pinned 10 points from the right margin. We’d like to have two buttons that fill the available space.

The option used in Practical Auto Layout for these buttons is to make them the same size. Control drag from Pepperoni to Cheese. A menu appears.

2016-11-29_06-56-57

Shift select Equal Width and Equal Heights, then hit the Add Constraints selection. The ambiguity changes to misplacements.

2016-11-29_06-57-14

Select both the Pepperoni and Cheese buttons. Hit the Update Frame button update frames and two equally sized buttons appear

2016-11-29_06-58-00

The other, more advanced option is to change priority of one of the buttons so they are not equal. Both are by default 250.  Going back to the original ambiguous layout,

2016-11-29_05-45-35

changing the content hugging priority of Pepperoni from 250 to 251 tells auto layout for Pepperoni to keep its size and Cheese to stretch to make up the difference.

2016-11-29_06-56-19

Priorities are covered in detail in Chapter 12 of Practical Autolayout for Xcode 8.

I’ll be updating the book shortly. Until then or if you cannot update your book,  consider this an errata to the versions now available.

practical-autolayout-x8-newsletterPurchase the book for  Kindle and iTunes  here:

get_it_on_ibooks_badge_us_1114

From Apple to Raspberry Pi: Adding Scroll Bars and Frames

In our last post we found that the list box was a  waste of time for its task, unless you want one-word selections. We hacked our way around it for the meantime, using the underscore character. We left off needing a scroll bar to scroll through our survey results. There was also a bug in the layout. The window looked like this:

Screenshot 2014-06-03 10.49.20

The combo boxes with the type and action are on the bottom of the window. Our code reads:

        penguinType_combobox.grid(row =1, column = 0)
        penguinAction_combobox.grid(row=1, column = 4)

They are supposed to be between the buttons and the label, but are on the bottom instead. The penguin action Combobox is also out in right field all by itself. We really need to clean up this layout.

Adding the Scroll Bar

The Scrollbar widget is a bit tricky to add, though it is only two lines. Add this just above the code for the Listbox in our view’s loadView() method:

        dataList_yScroll = Scrollbar(self.frame, orient=VERTICAL)
        dataList_yScroll.grid(row=5,column=1,sticky = NS)

In line one, we set the orientation to vertical on our scroll bar. In line two we set the bar to a north-south stickiness. Always remember to make a scroll bar sticky in the proper direction. If you don’t, it will collapse itself into a little ball and won’t work. Save and run, and you see nothing new.

Screenshot 2014-06-03 10.49.20

It turns out we have redundant code causing problems:

        dataList_listbox = Listbox(self.frame, listvariable= self.dataList, width = 40)
        dataList_listbox.grid(row=5, column = 0, columnspan = 4,rowspan = 4, sticky=NSEW)

In line 1 the code defines the Listbox attribute width as 40 characters wide. In line 2, the columnspan attribute extends the column to four columns wide. This extends the list box over several columns, including the one with the scroll bar. We set the width when we instantiated the list box. We only need one column. Remove the spans:

        dataList_listbox = Listbox(self.frame, listvariable= self.dataList, width = 40)
        dataList_listbox.grid(row=5, column = 0, sticky=NSEW)

Save and Run:

Listbox with the scroll bar
Listbox with the scroll bar

We now have a scroll bar that does nothing. Bind it to the list box. Add this code after the code for the list box:

        dataList_listbox.configure(yscrollcommand = dataList_yScroll.set)
        dataList_yScroll.configure(command=  dataList_listbox.yview)

If working with a horizontal scroll bar, change all the y’s for x’s. Save and run this code. Fill the list box with more entries than you can see. Now click the scroll bar and scroll to the end.

A working scroll bar with the textbox
A working scroll bar with the list box

Fix the Combo Boxes

Why are the combo boxes in the wrong place? The code for the combo boxes is this:

        penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy']
        penguinType_combobox = ttk.Combobox(values = penguinType_values, textvariable = self.penguinType)
        penguinType_combobox.grid(row =1, column = 0)

        penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute']
        penguinAction_combobox = ttk.Combobox(values = penguinAction_values, textvariable = self.penguinAction)
        penguinAction_combobox.grid(row=1, column = 1)

Looking carefully, I find my mistake. I forgot to add self.frame to both combo boxes. It’s placing the combo box on the window, not the frame. That’s an easy fix:

        penguinType_combobox = ttk.Combobox( self.frame,values = penguinType_values, textvariable = self.penguinType)

For the second one, change:

        penguinAction_combobox = ttk.Combobox( self.frame, values = penguinAction_values, textvariable =

Save and run:

Combo boxes in the correct position
Combo boxes in the correct place

More about Grids and Sizing

The grid() method has a simple way of sizing the gird. It finds the biggest vertical and horizontal dimension for the row and column, and sets it to those values. That, and some bad layout is messing us up. Remove the columnspan on the label.

        status_label.grid(row=0,column=0,sticky=NSEW)

Save and run. Add Oswald Cobblepot Plotting Evil, which is the longest phrase we have:

Alignment problems on the grid.
Holy Alignment Batman! Alignment problems on the grid.

Let’s take note of our layout problems.

  • The quit button moves over the accommodate the extra size.
  • The Add button centers itself with the list box
  • The Combo Boxes spread out too much
  • Getting to the first combo box, we can accidentally press Add instead

The two buttons and the two combo boxes would work better in a frame the width of the listbox, then organized in a grid within that frame. Add this code just under def loadView():

        buttonFrame = ttk.Frame(self.frame)
        buttonFrame.grid(row = 1, column = 0, sticky = NSEW)

Now change the widgets to use this as their frame, changing their grid locations as indicated:

        add_button = ttk.Button(buttonFrame,command= self.vc.addPressed,text = 'Add')
        add_button.grid(row = 0, column = 0,sticky = NSEW)
        quit_button = ttk.Button(buttonFrame, command = self.vc.quitPressed, text = 'Quit')
        quit_button.grid(row = 0, column = 1,sticky = NSEW)

        penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy']
        penguinType_combobox = ttk.Combobox(buttonFrame,values = penguinType_values, textvariable = self.penguinType)
        penguinType_combobox.grid(row =1, column = 0,sticky = EW)

        penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute']
        penguinAction_combobox = ttk.Combobox(buttonFrame, values = penguinAction_values, textvariable = self.penguinAction)
        penguinAction_combobox.grid(row=1, column = 1,sticky = EW)

Save and run:

That looks a lot nicer.
That looks a lot nicer.

The frame button frame is a 2×2 grid, and with everything sticky, lines up perfectly. — almost. The scroll bar is out in its  own column. Let’s make another frame and place the list box and scroll bar inside of it.

        listFrame = ttk.Frame(self.frame)
        listFrame.grid(row =2,column = 0, sticky = NSEW)
        
        dataList_yScroll = Scrollbar(listframe, orient=VERTICAL)
        dataList_yScroll.grid(row=0,column=1,sticky = NS)
    
        dataList_listbox = Listbox(listframe, listvariable= self.dataList, width = 40)
        dataList_listbox.grid(row=0, column = 0, sticky=NSEW)
#bind the listbox and scrollbar
        dataList_listbox.configure(yscrollcommand = dataList_yScroll.set)
        dataList_yScroll.configure(command=  dataList_listbox.yview)

We changed the grid so instead of row 5 we are in row 0 of the new frame. Save and run:

Two frames and a label -- in one column
Two frames and a label — in one column

We have scroll bars and have lined everything up correctly. We’ll revisit that annoying list box and use something else to get a better list box. That will be next time.

The Whole Code
The code is also available for download at Github.

# poppers_penguins_MVC_03
#MVC Version 2014 May 28
#Change: Implements the Model according to the MVC template

from tkinter import *
from tkinter import ttk
from tkinter import messagebox

class MyViewController():
    def __init__(self,parent):
        self.parent = parent;      

        self.view = MyView(self)
        self.model = MyModel(self)

    #Handlers -- target action
    def addPressed(self):
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
        self.model.addRecordToList(self.view.getPenguinType(),self.view.getPenguinAction())
    def quitPressed(self):
        self.view.setLabelText('Quitting')
        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
        if answer==True:            self.parent.destroy()

#Add list to string processing for the scrollbox.
    def spaceToUnderscore(self,aList, width):
        # convert spaces to underscores in list items
        # with a triple underscore between list items
        listString = str()
        for  element in aList:
            elementString = '{:{width}}'.format(element, width=width)
            elementString = elementString.replace(' ','_')
            listString = listString + '___' + elementString
        return listString
    def elementListToString(self,aList):
        listString = str()
        for  element in aList:
            elementString = element
            listString = listString + '\t' + elementString
        return listString
    def listToListString(self,aList):
        #a temporary method for making lists compatible
        #with listbox till I find something better
        #returns a string we can place in a StringVar
        listString = str()
        for record in aList:
            elementString = self.spaceToUnderscore(record,20)
 #            elementString = self.elementListToString(record)
            listString = listString + '\n' + elementString
        return listString

#delegate for the model
    def modelDidChangeDelegate(self):
        aList = self.model.getList()
        aListString = self.listToListString(aList)
        self.view.setDataList(aListString)

class MyView(Frame):
#Change parent to vc and add a frame 2014 May 26 a
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row=0,column=0)
        self.vc = vc

        #properties of the view
        self.labelText = StringVar()
        self.labelText.set("Popper's Penguins Ready")
        self.penguinType = StringVar()
        self.penguinType.set('Penguin Type')
        self.penguinAction = StringVar()
        self.penguinAction.set('Penguin Action')

#Add the control variable
        self.dataList = StringVar()
        self.dataList.set ('')

        self.loadView()
 #       self.makeStyle()

#Setters and getters for the properties 2014-May 26
    def setLabelText(self,newText):
        self.labelText.set(newText)
    def getLabelText(self):
        return self.labelText.get()
    def setPenguinType(self,newType):
        self.penguinType.set(newType)
    def getPenguinType(self):
        return self.penguinType.get()
    def setPenguinAction(self,newAction):
        self.penguinAction.set(newAction)
    def getPenguinAction(self):
        return self.penguinAction.get()
#Add setters and getter for data list
    def getDataList(self):
        #returns a string
        return self.dataList.get()
    def setDataList(self,listString):
        #needs a list string delimtied by a space
        self.dataList.set(listString)      

#Style Sheet
    def makeStyle(self):
        self.s = ttk.Style()
        self.s.configure('TFrame',background = '#5555ff')
        self.s.configure('TButton',background = 'blue', foreground = '#eeeeff', font = ('Sans','14','bold'), sticky = EW)
        self.s.configure('TLabel',font=('Sans','16','bold'),background = '#5555ff', foreground = '#eeeeff')
        self.s.map('TButton', foreground = [('hover','#5555ff'), ('focus', 'yellow')])
        self.s.map('TButton', background = [('hover', '#eeeeff'),('focus','orange')])
        self.s.configure('TCombobox',background = '#5555ff',foreground ='#3333ff',font = ('Sans',18))

#loading the view
#changed the command= to refer to the view controller 2014-May-26
    # self.addPressed now is self.vc.addPressed
    # self.quitPressed now is self.vc.quitPressed
    def loadView(self):
        #label
        buttonFrame = ttk.Frame(self.frame)
        buttonFrame.grid(row = 1, column = 0)

        status_label = ttk.Label(self.frame, textvariable = self.labelText)
        #status_label.configure(font=('Sans','16','bold'),background = 'blue', foreground = '#eeeeff')
        status_label.grid(row=0,column=0,sticky=NSEW)

        add_button = ttk.Button(buttonFrame,command= self.vc.addPressed,text = 'Add')
        add_button.grid(row = 0, column = 0,sticky = NSEW)
        quit_button = ttk.Button(buttonFrame, command = self.vc.quitPressed, text = 'Quit')
        quit_button.grid(row = 0, column = 1,sticky = NSEW)

        penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy']
        penguinType_combobox = ttk.Combobox(buttonFrame,values = penguinType_values, textvariable = self.penguinType)
        penguinType_combobox.grid(row =1, column = 0,sticky = EW)

        penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute']
        penguinAction_combobox = ttk.Combobox(buttonFrame, values = penguinAction_values, textvariable = self.penguinAction)
        penguinAction_combobox.grid(row=1, column = 1,sticky = EW)
       
        listFrame = ttk.Frame(self.frame)
        listFrame.grid(row =2,column = 0, sticky = NSEW)
        
        dataList_yScroll = Scrollbar(listframe, orient=VERTICAL)
        dataList_yScroll.grid(row=0,column=1,sticky = NSEW)
    
        dataList_listbox = Listbox(listframe, listvariable= self.dataList, width = 40)
        dataList_listbox.grid(row=0, column = 0, sticky=NSEW)
#bind the listbox and scrollbar
        dataList_listbox.configure(yscrollcommand = dataList_yScroll.set)
        dataList_yScroll.configure(command=  dataList_listbox.yview)


#(1)adding the model 2014-May-28 with convenience method
class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = []
        self.count = 0
#(2)Delegate goes here. Model would call this on internal change
    def modelDidChange(self):
        self.vc.modelDidChangeDelegate()
#Setters and getters for the model
    def getList(self):
        return self.myList
    def addToList(self,item):
        myItem = item
        myList = self.myList
        myList.append(myItem)
        self.myList=myList
        self.modelDidChange()
    def getItemAtIndex(self,index):
        return myList[index]
#other methods
    def addRecordToList(self,penguin,action):
        self.addToList([penguin,action])

def main():
    root = Tk()
#(8) Set up the main loop 2014 May 26
    frame = Frame(root, background="#5555ff", takefocus = 0)
    root.title("Popper's Penguins")
    app = MyViewController(root)
    root.mainloop() 

if __name__ == '__main__':
    main()